Skip to content

Fix/tab controller transition #1089

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/components/tabController/PageCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,20 @@ class PageCarousel extends PureComponent {
}
};

renderCodeBlock = () => {
renderCodeBlock = _.memoize(() => {
const {targetPage, containerWidth} = this.context;
return block([
Animated.onChange(targetPage, [call([targetPage], this.onTabChange)]),
Animated.onChange(containerWidth, [call([targetPage], this.onTabChange)])
]);
};

return (
<Code>
{() =>
block([
Animated.onChange(targetPage, [call([targetPage], this.onTabChange)]),
Animated.onChange(containerWidth, [call([targetPage], this.onTabChange)])
])
}
</Code>
);
});

render() {
const {selectedIndex, pageWidth} = this.context;
Expand All @@ -63,7 +70,7 @@ class PageCarousel extends PureComponent {
contentOffset={{x: selectedIndex * pageWidth, y: 0}} // iOS only
/>

<Code>{this.renderCodeBlock}</Code>
{this.renderCodeBlock()}
</>
);
}
Expand Down
36 changes: 14 additions & 22 deletions src/components/tabController/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ class TabBar extends PureComponent<Props, State> {
const {itemsWidths} = this.state;
const {indicatorStyle} = this.props;
if (itemsWidths.length > 0) {
return <Reanimated.View style={[styles.selectedIndicator, indicatorStyle, this._indicatorTransitionStyle]} />;
return <Reanimated.View style={[styles.selectedIndicator, indicatorStyle, this._indicatorTransitionStyle]}/>;
}
}

Expand Down Expand Up @@ -428,31 +428,23 @@ class TabBar extends PureComponent<Props, State> {
}
}

renderCodeBlock = () => {
renderCodeBlock = _.memoize(() => {
const {currentPage, targetPage} = this.context;
const {itemsWidths, itemsOffsets} = this.state;
const nodes = [];

nodes.push(
set(
this._indicatorOffset,
interpolate(currentPage, {
inputRange: itemsOffsets.map((_v, i) => i),
outputRange: itemsOffsets
})
)
);
nodes.push(
set(
this._indicatorWidth,
interpolate(currentPage, {inputRange: itemsWidths.map((_v, i) => i), outputRange: itemsWidths})
)
);
nodes.push(set(this._indicatorOffset,
interpolate(currentPage, {
inputRange: itemsOffsets.map((_v, i) => i),
outputRange: itemsOffsets
})));
nodes.push(set(this._indicatorWidth,
interpolate(currentPage, {inputRange: itemsWidths.map((_v, i) => i), outputRange: itemsWidths})));

nodes.push(Reanimated.onChange(targetPage, Reanimated.call([targetPage], this.focusSelected)));

return block(nodes);
};
return <Code>{() => block(nodes)}</Code>;
});

getShadowStyle() {
const {enableShadow, shadowStyle} = this.props;
Expand Down Expand Up @@ -487,9 +479,9 @@ class TabBar extends PureComponent<Props, State> {
</View>
{this.renderSelectedIndicator()}
</ScrollView>
{_.size(itemsWidths) > 1 && <Code>{this.renderCodeBlock}</Code>}
<ScrollBarGradient left visible={fadeLeft} />
<ScrollBarGradient visible={fadeRight} />
{_.size(itemsWidths) > 1 && this.renderCodeBlock()}
<ScrollBarGradient left visible={fadeLeft}/>
<ScrollBarGradient visible={fadeRight}/>
</View>
);
}
Expand Down
29 changes: 18 additions & 11 deletions src/components/tabController/TabPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,25 @@ export default class TabPage extends PureComponent<TabPageProps> {
}, this.props.lazyLoadTime); // tab bar indicator transition time
};

renderCodeBlock = () => {
renderCodeBlock = _.memoize(() => {
const {targetPage} = this.context;
const {index, lazy} = this.props;
return block([
cond(and(eq(targetPage, index), Number(lazy), eq(this._loaded, 0)), [set(this._loaded, 1), call([], this.lazyLoad)]),
cond(eq(targetPage, index),
[set(this._opacity, 1), set(this._zIndex, 1)],
[set(this._opacity, 0), set(this._zIndex, 0)])
]);
};
return (
<Code>
{() =>
block([
cond(and(eq(targetPage, index), Number(lazy), eq(this._loaded, 0)), [
set(this._loaded, 1),
call([], this.lazyLoad)
]),
cond(eq(targetPage, index),
[set(this._opacity, 1), set(this._zIndex, 1)],
[set(this._opacity, 0), set(this._zIndex, 0)])
])
}
</Code>
);
});

render() {
const {renderLoading, testID} = this.props;
Expand All @@ -87,9 +96,7 @@ export default class TabPage extends PureComponent<TabPageProps> {
<Reanimated.View style={this._pageStyle} testID={testID}>
{!loaded && renderLoading?.()}
{loaded && this.props.children}
<Code>
{this.renderCodeBlock}
</Code>
{this.renderCodeBlock()}
</Reanimated.View>
);
}
Expand Down
113 changes: 59 additions & 54 deletions src/components/tabController/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const {
diff,
eq,
floor,
greaterThan,
lessThan,
neq,
not,
Expand Down Expand Up @@ -159,75 +160,79 @@ class TabController extends Component<TabControllerProps, StateProps> {
_.invoke(this.props, 'onChangeIndex', index);
};

renderCodeBlock = () => {
renderCodeBlock = _.memoize(() => {
const {itemStates, ignoredItems, currentPage, targetPage, carouselOffset, containerWidth} = this.state;
const {selectedIndex} = this.props;
const clock = new Clock();
const fromPage = new Value(selectedIndex);
const toPage = new Value(selectedIndex);
const isAnimating = new Value(0);
const isScrolling = new Value(0);
// temps
const _carouselOffsetDiff = new Value(0);

return block([
/* Page change by TabBar */
..._.map(itemStates, (state, index) => {
const ignoredItem = _.includes(ignoredItems, index);
return [
onChange(
state,
// @ts-ignore TODO: typescript?
cond(and(eq(state, State.END), !ignoredItem), [
set(fromPage, toPage),
set(toPage, index),
set(targetPage, index)
])
)
];
}),

// Animate currentPage to its target
cond(neq(currentPage, toPage), [
set(isAnimating, 1),
set(
currentPage,
timing({clock, from: fromPage, to: toPage, duration: 280, easing: Easing.bezier(0.34, 1.3, 0.64, 1)})
)
]),
// Set isAnimating flag off
cond(and(eq(isAnimating, 1), not(clockRunning(clock))), set(isAnimating, 0)),

/* Page change by Carousel scroll */
onChange(carouselOffset, [
set(isScrolling, lessThan(round(abs(diff(carouselOffset))), round(containerWidth))),
cond(not(isAnimating), [
set(
currentPage,
interpolate(round(carouselOffset), {
inputRange: itemStates.map((_v, i) => round(multiply(i, containerWidth))),
outputRange: itemStates.map((_v, i) => i)
})
),
set(toPage, currentPage)
])
]),
// Update/Sync target page when scrolling is done
cond(and(eq(isScrolling, 1), eq(floor(abs(diff(carouselOffset))), 0)), [
set(isScrolling, 0),
cond(not(between(fract(currentPage), 0.1, 0.9, true)), set(targetPage, round(currentPage)))
]),

/* Invoke index change */
onChange(targetPage, call([targetPage], this.onPageChange))
]);
};
return (
<Code>
{() =>
block([
/* Page change by TabBar */
..._.map(itemStates, (state, index) => {
const ignoredItem = _.includes(ignoredItems, index);
return [
onChange(state,
// @ts-ignore TODO: typescript?
cond(and(eq(state, State.END), !ignoredItem), [
set(fromPage, toPage),
set(toPage, index),
set(targetPage, index)
]))
];
}),

// Animate currentPage to its target
cond(neq(currentPage, toPage), [
set(isAnimating, 1),
set(currentPage,
timing({clock, from: fromPage, to: toPage, duration: 280, easing: Easing.bezier(0.34, 1.3, 0.64, 1)}))
]),
// Set isAnimating flag off
cond(and(eq(isAnimating, 1), not(clockRunning(clock))), set(isAnimating, 0)),

/* Page change by Carousel scroll */
onChange(carouselOffset, [
set(_carouselOffsetDiff, round(abs(diff(carouselOffset)))),
set(isScrolling,
and(lessThan(_carouselOffsetDiff, round(containerWidth)), greaterThan(_carouselOffsetDiff, 0))),
cond(not(isAnimating), [
set(currentPage,
interpolate(round(carouselOffset), {
inputRange: itemStates.map((_v, i) => round(multiply(i, containerWidth))),
outputRange: itemStates.map((_v, i) => i)
})),
set(toPage, currentPage)
])
]),
// Update/Sync target page when scrolling is done
cond(and(eq(isScrolling, 1), eq(floor(abs(diff(carouselOffset))), 0)), [
set(isScrolling, 0),
cond(not(between(fract(currentPage), 0.1, 0.9, true)), set(targetPage, round(currentPage)))
]),

/* Invoke index change */
onChange(targetPage, call([targetPage], this.onPageChange))
])
}
</Code>
);
});

render() {
const {itemStates} = this.state;

return (
<TabBarContext.Provider value={this.state}>
{this.props.children}
{!_.isEmpty(itemStates) && <Code>{this.renderCodeBlock}</Code>}
{!_.isEmpty(itemStates) && this.renderCodeBlock()}
</TabBarContext.Provider>
);
}
Expand Down