UNPKG

react-native-scrollable-tab-view-forked

Version:

forked react-native-scrollable-tab-view ,you can set line width

335 lines (307 loc) 9.58 kB
const React = require("react"); const { ViewPropTypes } = (ReactNative = require("react-native")); const PropTypes = require("prop-types"); const createReactClass = require("create-react-class"); const { View, Animated, StyleSheet, ScrollView, Text, Platform, Dimensions } = ReactNative; const Button = require("./Button"); const ScrollableTabBar = createReactClass({ propTypes: { goToPage: PropTypes.func, activeTab: PropTypes.number, tabs: PropTypes.array, backgroundColor: PropTypes.string, activeTextColor: PropTypes.string, inactiveTextColor: PropTypes.string, scrollOffset: PropTypes.number, style: ViewPropTypes.style, tabStyle: ViewPropTypes.style, tabsContainerStyle: ViewPropTypes.style, textStyle: Text.propTypes.style, renderTab: PropTypes.func, underlineStyle: ViewPropTypes.style, onScroll: PropTypes.func, activeTextStyle: Text.propTypes.style, width: PropTypes.number }, getDefaultProps() { return { scrollOffset: 52, activeTextColor: "navy", inactiveTextColor: "black", backgroundColor: null, style: {}, tabStyle: {}, tabsContainerStyle: {}, underlineStyle: {}, width: Dimensions.get("window").width }; }, getInitialState() { this._tabsMeasurements = []; return { _leftTabUnderline: new Animated.Value(0), _widthTabUnderline: new Animated.Value(0), _containerWidth: null, _lineWidth: 0 // 底部下划线的宽度 }; }, componentDidMount() { this.props.scrollValue.addListener(this.updateView); console.log(); }, hasSetLineWidth() { if ( !this.props.underlineStyle || !typeof this.props.underlineStyle.width != "number" ) { return false; } return true; }, updateView(offset) { 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, isLastTab) { return ( this._tabsMeasurements[position] && (isLastTab || this._tabsMeasurements[position + 1]) && this._tabContainerMeasurements && this._containerMeasurements ); }, updateTabPanel(position, pageOffset) { 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; // center tab and smooth tab change (for when tabWidth changes a lot between two tabs) 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, pageOffset, tabCount) { const linewidth = (this._tabsMeasurements[position].width - this.state._lineWidth) / 2; const lineLeft = this._tabsMeasurements[position].left + linewidth; const lineRight = this._tabsMeasurements[position].right + linewidth; if (position < tabCount - 1) { const nextTabLeft = this._tabsMeasurements[position + 1].left; const nextTabRight = this._tabsMeasurements[position + 1].right; const newLineLeft = pageOffset * nextTabLeft + (1 - pageOffset) * lineLeft; const newLineRight = pageOffset * nextTabRight + (1 - pageOffset) * lineRight; this.state._leftTabUnderline.setValue(newLineLeft); !this.hasSetLineWidth() && this.state._widthTabUnderline.setValue(newLineRight - newLineLeft); } else { this.state._leftTabUnderline.setValue(lineLeft); !this.hasSetLineWidth() && this.state._widthTabUnderline.setValue(lineRight - lineLeft); } }, renderTab(child, page, isTabActive, onPressHandler, onLayoutHandler) { const { activeTextColor, inactiveTextColor, textStyle, activeTextStyle = {} } = this.props; const textColor = isTabActive ? activeTextColor : inactiveTextColor; const fontWeight = isTabActive ? "bold" : "normal"; const atextStyle = isTabActive ? activeTextStyle : {}; return ( <Button key={`${child.tabLabel}_${page}`} accessible accessibilityLabel={child.tabLabel} accessibilityTraits="button" onPress={() => onPressHandler(page)} onLayout={onLayoutHandler} > <View style={[styles.tab, this.props.tabStyle]}> <Text style={[{ color: textColor, fontWeight }, textStyle, atextStyle]} > {child.tabLabel} </Text> </View> </Button> ); }, measureTab(page, event) { const { x, width, height } = event.nativeEvent.layout; this._tabsMeasurements[page] = { left: x, right: x + width, width, height }; this.updateView({ value: this.props.scrollValue.__getValue() }); }, render() { const tabUnderlineStyle = { position: "absolute", height: 4, backgroundColor: "navy", bottom: 0 }; const dynamicTabUnderline = { left: this.state._leftTabUnderline }; /** * 如果没有设置宽度就用默认的宽度 */ if (!this.hasSetLineWidth()) { dynamicTabUnderline.width = this.state._widthTabUnderline; } return ( <View style={[ styles.container, { backgroundColor: this.props.backgroundColor }, this.props.style ]} onLayout={this.onContainerLayout} > <ScrollView ref={scrollView => { this._scrollView = scrollView; }} horizontal showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} directionalLockEnabled bounces={false} scrollsToTop={false} onScroll={this.props.onScroll} scrollEventThrottle={16} > <View style={[ styles.tabs, { width: this.state._containerWidth }, this.props.tabsContainerStyle ]} ref="tabContainer" onLayout={this.onTabContainerLayout} > {this.props.tabs.map((child, page) => { const isTabActive = this.props.activeTab === page; const renderTab = this.props.renderTab || this.renderTab; return renderTab( child, page, isTabActive, this.props.goToPage, this.measureTab.bind(this, page) ); })} <Animated.View style={[ tabUnderlineStyle, dynamicTabUnderline, this.props.underlineStyle ]} ref={r => { this._tabline = r; }} onLayout={({ nativeEvent: e }) => { that = this; this.setState({ _lineWidth: e.layout.width }, function() { that.updateView({ value: that.props.scrollValue.__getValue() }); }); }} /> </View> </ScrollView> </View> ); }, componentWillReceiveProps(nextProps) { // If the tabs change, force the width of the tabs container to be recalculated if ( JSON.stringify(this.props.tabs) !== JSON.stringify(nextProps.tabs) && this.state._containerWidth ) { this.setState({ _containerWidth: null }); } }, onTabContainerLayout(e) { this._tabContainerMeasurements = e.nativeEvent.layout; let width = this._tabContainerMeasurements.width; if (width < this.props.width) { width = this.props.width; } this.setState({ _containerWidth: width }); this.updateView({ value: this.props.scrollValue.__getValue() }); }, onContainerLayout(e) { this._containerMeasurements = e.nativeEvent.layout; this.updateView({ value: this.props.scrollValue.__getValue() }); } }); module.exports = ScrollableTabBar; const styles = StyleSheet.create({ tab: { height: 49, alignItems: "center", justifyContent: "center", paddingLeft: 20, paddingRight: 20 }, container: { height: 50, borderWidth: 1, borderTopWidth: 0, borderLeftWidth: 0, borderRightWidth: 0, borderColor: "#ccc" }, tabs: { flexDirection: "row", justifyContent: "space-around" } });