import React, {isValidElement} from 'react'; import {Animated, Dimensions, LayoutChangeEvent, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View, ViewStyle} from 'react-native'; import {Theme, WithTheme, WithThemeStyles} from '../style'; import {TabBarPropsType, TabData} from './PropsType'; import TabBarStyles, {TabBarStyle} from './style'; const WINDOW_WIDTH = Dimensions.get('window').width; export interface PropsType extends TabBarPropsType, WithThemeStyles { scrollValue?: any; tabStyle?: ViewStyle; tabsContainerStyle?: ViewStyle; /** default: false */ dynamicTabUnderlineWidth?: boolean; keyboardShouldPersistTaps?: boolean; } export interface StateType { _leftTabUnderline: Animated.Value; _widthTabUnderline: Animated.Value; _containerWidth: number; _tabContainerWidth: number; } export class DefaultTabBar extends React.PureComponent { static defaultProps = { animated: true, tabs: [], goToTab: () => {}, activeTab: 0, page: 5, tabBarUnderlineStyle: {}, tabBarBackgroundColor: '#fff', tabBarActiveTextColor: '', tabBarInactiveTextColor: '', tabBarTextStyle: {}, dynamicTabUnderlineWidth: false, }; _tabsMeasurements: any[] = []; _tabContainerMeasurements: any; _containerMeasurements: any; // @ts-ignore _scrollView: ScrollView; constructor(props: PropsType) { super(props); this.state = { _leftTabUnderline: new Animated.Value(0), _widthTabUnderline: new Animated.Value(0), _containerWidth: WINDOW_WIDTH, _tabContainerWidth: WINDOW_WIDTH, }; } componentDidMount() { this.props.scrollValue.addListener(this.updateView); } updateView = (offset: any) => { const position = Math.floor(offset.value); const pageOffset = offset.value % 1; const tabCount = this.props.tabs.length; const lastTabPosition = tabCount - 1; if (tabCount === 0 || offset.value < 0 || offset.value > lastTabPosition) { return; } if (this.necessarilyMeasurementsCompleted(position, position === lastTabPosition)) { this.updateTabPanel(position, pageOffset); this.updateTabUnderline(position, pageOffset, tabCount); } }; necessarilyMeasurementsCompleted(position: number, isLastTab: boolean) { return this._tabsMeasurements[position] && (isLastTab || this._tabsMeasurements[position + 1]) && this._tabContainerMeasurements && this._containerMeasurements; } updateTabPanel(position: number, pageOffset: number) { const containerWidth = this._containerMeasurements.width; const tabWidth = this._tabsMeasurements[position].width; const nextTabMeasurements = this._tabsMeasurements[position + 1]; const nextTabWidth = (nextTabMeasurements && nextTabMeasurements.width) || 0; const tabOffset = this._tabsMeasurements[position].left; const absolutePageOffset = pageOffset * tabWidth; let newScrollX = tabOffset + absolutePageOffset; newScrollX -= (containerWidth - (1 - pageOffset) * tabWidth - pageOffset * nextTabWidth) / 2; newScrollX = newScrollX >= 0 ? newScrollX : 0; if (Platform.OS === 'android') { this._scrollView.scrollTo({x: newScrollX, y: 0, animated: false}); } else { const rightBoundScroll = this._tabContainerMeasurements.width - this._containerMeasurements.width; newScrollX = newScrollX > rightBoundScroll ? rightBoundScroll : newScrollX; this._scrollView.scrollTo({x: newScrollX, y: 0, animated: false}); } } updateTabUnderline(position: number, pageOffset: number, tabCount: number) { const {dynamicTabUnderlineWidth} = this.props; if (0 <= position && position <= tabCount - 1) { if (dynamicTabUnderlineWidth) { const nowLeft = this._tabsMeasurements[position].left; const nowRight = this._tabsMeasurements[position].right; const nextTabLeft = this._tabsMeasurements[position + 1].left; const nextTabRight = this._tabsMeasurements[position + 1].right; const newLineLeft = pageOffset * nextTabLeft + (1 - pageOffset) * nowLeft; const newLineRight = pageOffset * nextTabRight + (1 - pageOffset) * nowRight; this.state._leftTabUnderline.setValue(newLineLeft); this.state._widthTabUnderline.setValue(newLineRight - newLineLeft); } else { const nowLeft = (position * this.state._tabContainerWidth) / tabCount; const nextTabLeft = ((position + 1) * this.state._tabContainerWidth) / tabCount; const newLineLeft = pageOffset * nextTabLeft + (1 - pageOffset) * nowLeft; this.state._leftTabUnderline.setValue(newLineLeft); } } } onPress = (index: number) => { const {goToTab, onTabClick, tabs} = this.props; // tslint:disable-next-line:no-unused-expression onTabClick && onTabClick(tabs[index], index); // tslint:disable-next-line:no-unused-expression goToTab && goToTab(index); }; renderTab = (tab: TabData, index: number, width: number, onLayoutHandler: any, styles: ReturnType, theme: Theme) => { const {tabBarActiveTextColor: activeTextColor, tabBarInactiveTextColor: inactiveTextColor, tabBarTextStyle: textStyle, activeTab, renderTab} = this.props; const isTabActive = activeTab === index; const textColor = isTabActive ? activeTextColor || theme.activeTextColor : inactiveTextColor || theme.inactiveTextColor; return ( this.onPress(index)} onLayout={onLayoutHandler}> {renderTab ? ( renderTab(tab) ) : isValidElement(tab.title) ? ( tab.title ) : ( {tab.title} )} {isTabActive ? ( ) : null} ); }; measureTab = (page: number, event: any) => { const {x, width, height} = event.nativeEvent.layout; this._tabsMeasurements[page] = {left: x, right: x + width, width, height}; this.updateView({value: this.props.scrollValue._value}); }; render() { const {tabs, page = 0, tabBarUnderlineStyle, tabBarBackgroundColor, tabsContainerStyle, renderUnderline, keyboardShouldPersistTaps} = this.props; return ( {(styles, theme) => { const tabUnderlineStyle = { position: 'absolute', bottom: 0, ...StyleSheet.flatten(styles.underline), ...StyleSheet.flatten(tabBarUnderlineStyle), }; const tabWidth = this.state._containerWidth / Math.min(page, tabs.length); return ( { this._scrollView = scrollView; }} horizontal showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} directionalLockEnabled bounces={false} scrollsToTop={false} scrollEnabled={tabs.length > page} keyboardShouldPersistTaps={keyboardShouldPersistTaps} renderToHardwareTextureAndroid> {tabs.map((name, index) => { let tab = {title: name} as TabData; if (tabs.length - 1 >= index) { tab = tabs[index]; } return this.renderTab(tab, index, tabWidth, this.measureTab.bind(this, index), styles, theme); })} ); }} ); } onTabContainerLayout = (e: LayoutChangeEvent) => { this._tabContainerMeasurements = e.nativeEvent.layout; const width = this._tabContainerMeasurements.width; // fix: https://github.com/ant-design/ant-design-mobile-rn/issues/162 // if (width < WINDOW_WIDTH) { // width = WINDOW_WIDTH; // } this.setState({_tabContainerWidth: width}); if (!this.props.dynamicTabUnderlineWidth) { this.state._widthTabUnderline.setValue(width / this.props.tabs.length); } this.updateView({value: this.props.scrollValue._value}); }; onContainerLayout = (e: LayoutChangeEvent) => { this._containerMeasurements = e.nativeEvent.layout; this.setState({_containerWidth: this._containerMeasurements.width}); this.updateView({value: this.props.scrollValue._value}); }; }