Skip to content

Commit 1b2dcb0

Browse files
authored
Fix/tab controller minor issues (#758)
* meature TabBar items text to optimize loading time * speed up tab transition * add guesstimate center value for centerSelected feature * add experimental optimize prop to control the new measurement behavior
1 parent d54050c commit 1b2dcb0

File tree

3 files changed

+79
-40
lines changed

3 files changed

+79
-40
lines changed

src/components/tabController/TabBar.js

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,26 @@ import TabBarItem from './TabBarItem';
1111
// import ReanimatedObject from './ReanimatedObject';
1212
import {asBaseComponent, forwardRef} from '../../commons';
1313
import View from '../../components/view';
14-
import {Colors, Spacings} from '../../style';
14+
import {Colors, Spacings, Typography} from '../../style';
1515
import {Constants} from '../../helpers';
1616
import {LogService} from '../../services';
1717

18+
const {Code, Value, interpolate, block, set} = Reanimated;
19+
1820
const DEFAULT_HEIGHT = 48;
1921
const INDICATOR_INSET = Spacings.s4;
20-
const {Code, Value, interpolate, block, set} = Reanimated;
22+
23+
const DEFAULT_LABEL_STYLE = {
24+
...Typography.text80,
25+
fontWeight: '400',
26+
letterSpacing: 0
27+
};
28+
29+
const DEFAULT_SELECTED_LABEL_STYLE = {
30+
...Typography.text80,
31+
fontWeight: '700',
32+
letterSpacing: 0
33+
};
2134

2235
/**
2336
* @description: TabController's TabBar component
@@ -88,10 +101,17 @@ class TabBar extends PureComponent {
88101
/**
89102
* Pass to center selected item
90103
*/
91-
centerSelected: PropTypes.bool
104+
centerSelected: PropTypes.bool,
105+
/**
106+
* (Experimental) Pass to optimize loading time by measuring tab bar items text
107+
* instead of waiting for onLayout
108+
*/
109+
optimize: PropTypes.bool
92110
};
93111

94112
static defaultProps = {
113+
labelStyle: DEFAULT_LABEL_STYLE,
114+
selectedLabelStyle: DEFAULT_SELECTED_LABEL_STYLE
95115
// containerWidth: Constants.screenWidth
96116
};
97117

@@ -123,6 +143,9 @@ class TabBar extends PureComponent {
123143
};
124144

125145
this.registerTabItems();
146+
if (props.items && props.optimize) {
147+
this.measureItems();
148+
}
126149
}
127150

128151
get containerWidth() {
@@ -144,7 +167,29 @@ class TabBar extends PureComponent {
144167

145168
get centerOffset() {
146169
const {centerSelected} = this.props;
147-
return centerSelected ? Constants.screenWidth / 2 : 0;
170+
const guesstimateCenterValue = 60;
171+
return centerSelected ? Constants.screenWidth / 2 - guesstimateCenterValue : 0;
172+
}
173+
174+
measureItems = async () => {
175+
const {labelStyle} = this.props;
176+
const measuring = _.map(this.props.items, (item) => {
177+
return Typography.measureTextSize(item.label, labelStyle);
178+
});
179+
const results = await Promise.all(measuring);
180+
const widths = _.map(results, item => item.width + Spacings.s4 * 2);
181+
const offsets = [];
182+
_.forEach(widths, (width, index) => {
183+
if (index === 0) {
184+
offsets[index] = this.centerOffset;
185+
} else {
186+
offsets[index] = widths[index - 1] + offsets[index - 1];
187+
}
188+
});
189+
this._itemsWidths = widths;
190+
this._itemsOffsets = offsets;
191+
// TODO: consider saving this setState and ride registerTabItems setState
192+
this.setItemsLayouts();
148193
}
149194

150195
registerTabItems() {
@@ -174,40 +219,42 @@ class TabBar extends PureComponent {
174219
}
175220

176221
// TODO: move this logic into a ScrollPresenter or something
177-
focusSelected = ([index]) => {
222+
focusSelected = ([index], animated = true) => {
178223
const {centerSelected} = this.props;
179224
const itemOffset = this._itemsOffsets[index];
180225
const itemWidth = this._itemsWidths[index];
226+
const screenCenter = Constants.screenWidth / 2;
181227

182228
if (itemOffset && itemWidth) {
183229
if (centerSelected) {
184-
this.tabBar.current.scrollTo({x: itemOffset - this.centerOffset + itemWidth / 2});
230+
this.tabBar.current.scrollTo({x: itemOffset - screenCenter + itemWidth / 2, animated});
185231
} else if (itemOffset < this.tabBarScrollOffset) {
186-
this.tabBar.current.scrollTo({x: itemOffset - itemWidth});
232+
this.tabBar.current.scrollTo({x: itemOffset - itemWidth, animated});
187233
} else if (itemOffset + itemWidth > this.tabBarScrollOffset + this.containerWidth) {
188234
const offsetChange = Math.max(0, itemOffset - (this.tabBarScrollOffset + this.containerWidth));
189-
this.tabBar.current.scrollTo({x: this.tabBarScrollOffset + offsetChange + itemWidth});
235+
this.tabBar.current.scrollTo({x: this.tabBarScrollOffset + offsetChange + itemWidth, animated});
190236
}
191237
}
192238
};
193239

240+
// TODO: replace with measureItems logic
194241
onItemLayout = ({width, x}, itemIndex) => {
195242
this._itemsWidths[itemIndex] = width;
196243
this._itemsOffsets[itemIndex] = x;
197244
if (!_.includes(this._itemsWidths, null)) {
198-
const {selectedIndex} = this.context;
199-
const itemsOffsets = _.map(this._itemsOffsets, offset => offset + INDICATOR_INSET);
200-
const itemsWidths = _.map(this._itemsWidths, (width) => width - INDICATOR_INSET * 2);
201-
202-
this.setState({itemsWidths, itemsOffsets});
203-
const selectedItemOffset = itemsOffsets[selectedIndex] - INDICATOR_INSET;
204-
205-
if (selectedItemOffset + this._itemsWidths[selectedIndex] > Constants.screenWidth) {
206-
this.tabBar.current.scrollTo({x: selectedItemOffset, animated: true});
207-
}
245+
this.setItemsLayouts();
208246
}
209247
};
210248

249+
setItemsLayouts = () => {
250+
const {selectedIndex} = this.context;
251+
const itemsOffsets = _.map(this._itemsOffsets, offset => offset + INDICATOR_INSET);
252+
const itemsWidths = _.map(this._itemsWidths, (width) => width - INDICATOR_INSET * 2);
253+
254+
this.setState({itemsWidths, itemsOffsets});
255+
this.focusSelected([selectedIndex], false);
256+
}
257+
211258
onScroll = ({nativeEvent: {contentOffset}}) => {
212259
this.tabBarScrollOffset = contentOffset.x;
213260
};
@@ -229,6 +276,7 @@ class TabBar extends PureComponent {
229276
renderTabBarItems() {
230277
const {itemStates} = this.context;
231278
const {
279+
optimize,
232280
items,
233281
labelColor,
234282
selectedLabelColor,
@@ -257,11 +305,12 @@ class TabBar extends PureComponent {
257305
selectedIconColor={selectedIconColor}
258306
activeBackgroundColor={activeBackgroundColor}
259307
key={item.label}
308+
width={this._itemsWidths[index]}
260309
{...item}
261310
{...this.context}
262311
index={index}
263312
state={itemStates[index]}
264-
onLayout={this.onItemLayout}
313+
onLayout={optimize ? undefined : this.onItemLayout}
265314
/>
266315
);
267316
});

src/components/tabController/TabBarItem.js

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,6 @@ const {cond, eq, call, block, event, and, defined} = Reanimated;
1515
const DEFAULT_LABEL_COLOR = Colors.black;
1616
const DEFAULT_SELECTED_LABEL_COLOR = Colors.blue30;
1717

18-
const DEFAULT_LABEL_STYLE = {
19-
...Typography.text80,
20-
fontWeight: '400',
21-
letterSpacing: 0
22-
};
23-
24-
const DEFAULT_SELECTED_LABEL_STYLE = {
25-
...Typography.text80,
26-
fontWeight: '700',
27-
letterSpacing: 0
28-
};
29-
3018
/**
3119
* @description: TabController's TabBarItem
3220
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/TabControllerScreen/index.js
@@ -137,8 +125,10 @@ export default class TabBarItem extends PureComponent {
137125
const {index, onLayout} = this.props;
138126
const {itemWidth} = this.state;
139127
if (!itemWidth) {
140-
this.setState({itemWidth: width});
141-
onLayout({width, x}, index);
128+
if (onLayout) {
129+
this.setState({itemWidth: width});
130+
onLayout({width, x}, index);
131+
}
142132
}
143133
};
144134

@@ -179,15 +169,15 @@ export default class TabBarItem extends PureComponent {
179169
ignore
180170
} = this.props;
181171

182-
const labelStyle = {...DEFAULT_LABEL_STYLE, ...this.props.labelStyle};
183-
const selectedLabelStyle = {...DEFAULT_SELECTED_LABEL_STYLE, ...this.props.selectedLabelStyle};
172+
const labelStyle = this.props.labelStyle;
173+
const selectedLabelStyle = this.props.selectedLabelStyle;
184174

185175
const fontWeight = cond(and(eq(targetPage, index), defined(itemWidth)),
186-
selectedLabelStyle.fontWeight,
187-
labelStyle.fontWeight);
176+
selectedLabelStyle.fontWeight || 'normal',
177+
labelStyle.fontWeight || 'normal');
188178
const letterSpacing = cond(and(eq(targetPage, index), defined(itemWidth)),
189-
selectedLabelStyle.letterSpacing,
190-
labelStyle.letterSpacing);
179+
selectedLabelStyle.letterSpacing || 0,
180+
labelStyle.letterSpacing || 0);
191181

192182
const inactiveColor = labelColor || DEFAULT_LABEL_COLOR;
193183
const activeColor = !ignore ? selectedLabelColor || DEFAULT_SELECTED_LABEL_COLOR : inactiveColor;

src/components/tabController/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class TabController extends Component {
128128
cond(neq(currentPage, toPage), [
129129
set(isAnimating, 1),
130130
set(currentPage,
131-
timing({clock, from: fromPage, to: toPage, duration: 300, easing: Easing.bezier(0.34, 1.3, 0.64, 1)}))
131+
timing({clock, from: fromPage, to: toPage, duration: 280, easing: Easing.bezier(0.34, 1.3, 0.64, 1)}))
132132
]),
133133
// Set isAnimating flag off
134134
cond(and(eq(isAnimating, 1), not(clockRunning(clock))), set(isAnimating, 0)),

0 commit comments

Comments
 (0)