Skip to content

Commit c6ef5fc

Browse files
committed
Fix transition issues in TabController
1 parent 4031493 commit c6ef5fc

File tree

5 files changed

+121
-111
lines changed

5 files changed

+121
-111
lines changed

demo/src/screens/incubatorScreens/TabControllerScreen/index.js

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import Tab1 from './tab1';
77
import Tab2 from './tab2';
88
import Tab3 from './tab3';
99

10-
const USE_CAROUSEL = true;
1110
const TABS = ['Home', 'Me', 'Dashboard', 'account', 'groups', 'blog'];
1211

1312
class TabControllerScreen extends Component {
1413
state = {
14+
asCarousel: true,
1515
selectedIndex: 0,
1616
items: [
1717
..._.map(TABS, tab => ({label: tab, key: tab})),
@@ -72,6 +72,17 @@ class TabControllerScreen extends Component {
7272
// ];
7373
// }
7474

75+
toggleCarouselMode = () => {
76+
this.setState({
77+
asCarousel: !this.state.asCarousel,
78+
key: this.state.asCarousel ? 'asCarousel' : 'staticPages'
79+
});
80+
};
81+
82+
onChangeIndex = selectedIndex => {
83+
this.setState({selectedIndex});
84+
};
85+
7586
renderLoadingPage() {
7687
return (
7788
<View flex center>
@@ -84,8 +95,9 @@ class TabControllerScreen extends Component {
8495
}
8596

8697
renderTabPages() {
87-
const Container = USE_CAROUSEL ? Incubator.TabController.PageCarousel : View;
88-
const containerProps = USE_CAROUSEL ? {} : {flex: true};
98+
const {asCarousel} = this.state;
99+
const Container = asCarousel ? Incubator.TabController.PageCarousel : View;
100+
const containerProps = asCarousel ? {} : {flex: true};
89101
return (
90102
<Container {...containerProps}>
91103
<Incubator.TabController.TabPage index={0}>
@@ -111,33 +123,38 @@ class TabControllerScreen extends Component {
111123
}
112124

113125
render() {
114-
const {key, selectedIndex} = this.state;
126+
const {key, selectedIndex, asCarousel} = this.state;
115127
return (
116-
<View flex bg-dark80>
117-
<View flex>
118-
<Incubator.TabController
119-
key={key}
120-
asCarousel={USE_CAROUSEL}
121-
selectedIndex={selectedIndex}
122-
onChangeIndex={index => console.warn('tab index is', index)}
128+
<View flex bg-grey70>
129+
<Incubator.TabController
130+
key={key}
131+
asCarousel={asCarousel}
132+
selectedIndex={selectedIndex}
133+
onChangeIndex={this.onChangeIndex}
134+
>
135+
<Incubator.TabController.TabBar
136+
items={this.getItems()}
137+
// key={key}
138+
// uppercase
139+
// indicatorStyle={{backgroundColor: 'green', height: 3}}
140+
// labelColor={'green'}
141+
// selectedLabelColor={'red'}
142+
// labelStyle={{fontSize: 20}}
143+
// iconColor={'green'}
144+
// selectedIconColor={'blue'}
145+
activeBackgroundColor={Colors.blue60}
123146
>
124-
<Incubator.TabController.TabBar
125-
items={this.getItems()}
126-
// key={key}
127-
// uppercase
128-
// indicatorStyle={{backgroundColor: 'green', height: 3}}
129-
// labelColor={'green'}
130-
// selectedLabelColor={'red'}
131-
// labelStyle={{fontSize: 20}}
132-
// iconColor={'green'}
133-
// selectedIconColor={'blue'}
134-
activeBackgroundColor={Colors.blue60}
135-
>
136-
{/* {this.renderTabItems()} */}
137-
</Incubator.TabController.TabBar>
138-
{this.renderTabPages()}
139-
</Incubator.TabController>
140-
</View>
147+
{/* {this.renderTabItems()} */}
148+
</Incubator.TabController.TabBar>
149+
{this.renderTabPages()}
150+
</Incubator.TabController>
151+
<Button
152+
bg-grey20={!asCarousel}
153+
bg-green30={asCarousel}
154+
label={`Carousel:${asCarousel ? 'ON' : 'OFF'}`}
155+
style={{position: 'absolute', bottom: 100, right: 20}}
156+
onPress={this.toggleCarouselMode}
157+
/>
141158
</View>
142159
);
143160
}

src/incubator/TabController/PageCarousel.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, {Component} from 'react';
1+
import React, {PureComponent} from 'react';
22
import TabBarContext from './TabBarContext';
33
import Animated from 'react-native-reanimated';
44
import {Constants} from '../../helpers';
55

66
const {Code, block, call} = Animated;
77

8-
class PageCarousel extends Component {
8+
class PageCarousel extends PureComponent {
99
static contextType = TabBarContext;
1010
carousel = React.createRef();
1111

@@ -28,10 +28,15 @@ class PageCarousel extends Component {
2828
scrollToPage = (pageIndex, animated) => {
2929
const node = this.carousel.current.getNode();
3030
node.scrollTo({x: pageIndex * Constants.screenWidth, animated});
31-
}
31+
};
32+
33+
renderCodeBlock = () => {
34+
const {currentPage} = this.context;
35+
return block([Animated.onChange(currentPage, call([currentPage], this.onTabChange))]);
36+
};
3237

3338
render() {
34-
const {selectedIndex, currentPage} = this.context;
39+
const {selectedIndex} = this.context;
3540
return (
3641
<>
3742
<Animated.ScrollView
@@ -45,13 +50,7 @@ class PageCarousel extends Component {
4550
contentOffset={{x: selectedIndex * Constants.screenWidth}} // iOS only
4651
/>
4752

48-
<Code>
49-
{() => {
50-
return block([
51-
Animated.onChange(currentPage, call([currentPage], this.onTabChange))
52-
]);
53-
}}
54-
</Code>
53+
<Code>{this.renderCodeBlock}</Code>
5554
</>
5655
);
5756
}

src/incubator/TabController/TabBar.js

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,6 @@ class TabBar extends PureComponent {
115115
this.tabBar = React.createRef();
116116

117117
this._itemsWidths = _.times(itemsCount, () => null);
118-
// this._indicatorOffset = new ReanimatedObject({duration: 300, easing: Easing.bezier(0.23, 1, 0.32, 1)});
119-
// this._indicatorWidth = new ReanimatedObject({duration: 300, easing: Easing.bezier(0.23, 1, 0.32, 1)});
120-
121118
this._indicatorOffset = new Value(0);
122119
this._indicatorWidth = new Value(0);
123120

@@ -178,7 +175,6 @@ class TabBar extends PureComponent {
178175
}
179176

180177
onItemLayout = (itemWidth, itemIndex) => {
181-
const {asCarousel} = this.context;
182178
this._itemsWidths[itemIndex] = itemWidth;
183179
if (!_.includes(this._itemsWidths, null)) {
184180
const {selectedIndex} = this.context;
@@ -188,14 +184,6 @@ class TabBar extends PureComponent {
188184

189185
this.setState({itemsWidths, itemsOffsets});
190186
this.tabBar.current.scrollTo({x: itemsOffsets[selectedIndex], animated: false});
191-
192-
if (!asCarousel) {
193-
this._indicatorOffset = runIndicatorTimer(new Clock(), this.context.currentPage, itemsOffsets);
194-
this._indicatorWidth = runIndicatorTimer(new Clock(), this.context.currentPage, itemsWidths);
195-
196-
this._indicatorTransitionStyle.left = this._indicatorOffset;
197-
this._indicatorTransitionStyle.width = this._indicatorWidth;
198-
}
199187
}
200188
};
201189

@@ -275,10 +263,35 @@ class TabBar extends PureComponent {
275263
}
276264
}
277265

278-
render() {
266+
renderCodeBlock = () => {
279267
const {carouselOffset, asCarousel} = this.context;
268+
const {itemsWidths, itemsOffsets} = this.state;
269+
const nodes = [];
270+
271+
if (asCarousel) {
272+
nodes.push(set(this._indicatorOffset,
273+
interpolate(carouselOffset, {
274+
inputRange: itemsOffsets.map((value, index) => index * Constants.screenWidth),
275+
outputRange: itemsOffsets,
276+
extrapolate: Extrapolate.CLAMP
277+
})),
278+
set(this._indicatorWidth,
279+
interpolate(carouselOffset, {
280+
inputRange: itemsWidths.map((value, index) => index * Constants.screenWidth),
281+
outputRange: itemsWidths,
282+
extrapolate: Extrapolate.CLAMP
283+
})));
284+
} else {
285+
nodes.push(set(this._indicatorOffset, runIndicatorTimer(new Clock(), this.context.currentPage, itemsOffsets)),
286+
set(this._indicatorWidth, runIndicatorTimer(new Clock(), this.context.currentPage, itemsWidths)));
287+
}
288+
289+
return block(nodes);
290+
};
291+
292+
render() {
280293
const {height, enableShadow, containerStyle} = this.props;
281-
const {itemsWidths, itemsOffsets, scrollEnabled} = this.state;
294+
const {itemsWidths, scrollEnabled} = this.state;
282295
return (
283296
<View
284297
style={[styles.container, enableShadow && styles.containerShadow, {width: this.containerWidth}, containerStyle]}
@@ -295,26 +308,7 @@ class TabBar extends PureComponent {
295308
<View style={[styles.tabBar, height && {height}]}>{this.renderTabBarItems()}</View>
296309
{this.renderSelectedIndicator()}
297310
</ScrollView>
298-
<Code>
299-
{() => {
300-
if (asCarousel && _.size(itemsWidths) > 1) {
301-
return block([
302-
set(this._indicatorOffset,
303-
Reanimated.interpolate(carouselOffset, {
304-
inputRange: itemsOffsets.map((offset, index) => index * Constants.screenWidth),
305-
outputRange: itemsOffsets
306-
})),
307-
set(this._indicatorWidth,
308-
Reanimated.interpolate(carouselOffset, {
309-
inputRange: itemsWidths.map((width, index) => index * Constants.screenWidth),
310-
outputRange: itemsWidths
311-
}))
312-
]);
313-
}
314-
315-
return block([]);
316-
}}
317-
</Code>
311+
{_.size(itemsWidths) > 1 && <Code>{this.renderCodeBlock}</Code>}
318312
</View>
319313
);
320314
}

src/incubator/TabController/TabPage.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,27 +63,27 @@ export default class TabPage extends PureComponent {
6363
}, this.props.lazyLoadTime); // tab bar indicator transition time
6464
};
6565

66-
render() {
66+
renderCodeBlock = () => {
6767
const {currentPage} = this.context;
68-
const {index, lazy, renderLoading, testID} = this.props;
68+
const {index, lazy} = this.props;
69+
return block([
70+
cond(and(eq(currentPage, index), lazy, eq(this._loaded, 0)), [set(this._loaded, 1), call([], this.lazyLoad)]),
71+
cond(eq(currentPage, index),
72+
[set(this._opacity, 1), set(this._zIndex, 1)],
73+
[set(this._opacity, 0), set(this._zIndex, 0)])
74+
]);
75+
};
76+
77+
render() {
78+
const {renderLoading, testID} = this.props;
6979
const {loaded} = this.state;
7080

7181
return (
7282
<Reanimated.View style={this._pageStyle} testID={testID}>
7383
{!loaded && renderLoading()}
7484
{loaded && this.props.children}
7585
<Code>
76-
{() => {
77-
return block([
78-
cond(and(eq(currentPage, index), lazy, eq(this._loaded, 0)), [
79-
set(this._loaded, 1),
80-
call([], this.lazyLoad)
81-
]),
82-
cond(eq(currentPage, index),
83-
[set(this._opacity, 1), set(this._zIndex, 1)],
84-
[set(this._opacity, 0), set(this._zIndex, 0)])
85-
]);
86-
}}
86+
{this.renderCodeBlock}
8787
</Code>
8888
</Reanimated.View>
8989
);

src/incubator/TabController/index.js

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class TabController extends Component {
5252
};
5353

5454
state = {
55+
selectedIndex: this.props.selectedIndex,
5556
itemStates: []
5657
};
5758

@@ -60,8 +61,8 @@ class TabController extends Component {
6061
_carouselOffset = new Value(this.props.selectedIndex * Math.round(Constants.screenWidth));
6162

6263
getProviderContextValue = () => {
63-
const {itemStates} = this.state;
64-
const {onChangeIndex, selectedIndex, asCarousel} = this.props;
64+
const {itemStates, selectedIndex} = this.state;
65+
const {onChangeIndex, asCarousel} = this.props;
6566
return {
6667
selectedIndex,
6768
currentPage: this._currentPage,
@@ -80,7 +81,7 @@ class TabController extends Component {
8081

8182
onPageChange = ([index]) => {
8283
_.invoke(this.props, 'onChangeIndex', index);
83-
}
84+
};
8485

8586
getCarouselPageChangeCode() {
8687
const {asCarousel} = this.props;
@@ -100,33 +101,32 @@ class TabController extends Component {
100101
return [];
101102
}
102103

103-
render() {
104+
renderCodeBlock = () => {
104105
const {itemStates, ignoredItems} = this.state;
106+
return block([
107+
// Carousel Page change
108+
...this.getCarouselPageChangeCode(),
109+
// TabBar Page change
110+
..._.map(itemStates, (state, index) => {
111+
return [
112+
cond(and(eq(state, State.BEGAN), !_.includes(ignoredItems, index)), set(this._targetPage, index)),
113+
cond(and(eq(this._targetPage, index), eq(state, State.END), !_.includes(ignoredItems, index)), [
114+
set(this._currentPage, index),
115+
set(this._targetPage, -1)
116+
])
117+
];
118+
}),
119+
onChange(this._currentPage, call([this._currentPage], this.onPageChange))
120+
]);
121+
};
122+
123+
render() {
124+
const {itemStates} = this.state;
105125

106126
return (
107127
<TabBarContext.Provider value={this.getProviderContextValue()}>
108128
{this.props.children}
109-
{!_.isEmpty(itemStates) && (
110-
<Code>
111-
{() =>
112-
block([
113-
// Carousel Page change
114-
...this.getCarouselPageChangeCode(),
115-
// TabBar Page change
116-
..._.map(itemStates, (state, index) => {
117-
return [
118-
cond(and(eq(state, State.BEGAN), !_.includes(ignoredItems, index)), set(this._targetPage, index)),
119-
cond(and(eq(this._targetPage, index), eq(state, State.END), !_.includes(ignoredItems, index)), [
120-
set(this._currentPage, index),
121-
set(this._targetPage, -1)
122-
])
123-
];
124-
}),
125-
onChange(this._currentPage, call([this._currentPage], this.onPageChange))
126-
])
127-
}
128-
</Code>
129-
)}
129+
{!_.isEmpty(itemStates) && <Code>{this.renderCodeBlock}</Code>}
130130
</TabBarContext.Provider>
131131
);
132132
}

0 commit comments

Comments
 (0)