美文网首页
react native tab 指示器动画(参考网易云音乐)

react native tab 指示器动画(参考网易云音乐)

作者: 一天前_73b6 | 来源:发表于2020-03-17 10:36 被阅读0次

    此功能是在 react native 第三方组件 react-native-scrollable-tab-view 使用自定义 tabBar 做出的效果

    效果展示

    Mar-17-2020 10-35-10.gif

    代码

    1.自定义的 ScrollableTabBar
    新建文件 SongCustomTabBar.js

    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 WINDOW_WIDTH = Dimensions.get('window').width;
    
    let beforePageOffset = 0
    
    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,
     },
    
     getDefaultProps() {
       return {
         scrollOffset: 52,
         activeTextColor: 'navy',
         inactiveTextColor: 'black',
         backgroundColor: null,
         style: {},
         tabStyle: {},
         tabsContainerStyle: {},
         underlineStyle: {},
       };
     },
    
    
    
     getInitialState() {
       this._tabsMeasurements = [];
       return {
         _leftTabUnderline: new Animated.Value(0),
         _widthTabUnderline: new Animated.Value(0),
         _containerWidth: null,
       };
     },
    
     componentDidMount() {
       this.props.scrollValue.addListener(this.updateView);
     },
    
     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 lineLeft = this._tabsMeasurements[position].left;
       const lineRight = this._tabsMeasurements[position].right;
    
    
       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);
    
         let width = nextTabLeft-lineLeft
         let rate  = pageOffset/1
         let addW = 0
    
         if(width*rate < width*(1-rate)){
           addW = width*rate
         }else{
           addW =width*(1-rate)
         }
    
         if(pageOffset<beforePageOffset){
           this.state._leftTabUnderline.setValue(newLineLeft + ((newLineRight-newLineLeft-20)/2) - addW);
         }else{
           this.state._leftTabUnderline.setValue(newLineLeft + ((newLineRight-newLineLeft-20)/2));
         }
         this.state._widthTabUnderline.setValue(20+addW);
       } else {
         this.state._leftTabUnderline.setValue(lineLeft+(lineRight-lineLeft-20)/2);
         this.state._widthTabUnderline.setValue(20);
       }
    
       beforePageOffset = pageOffset
    
     },
    
     renderTab(name, page, isTabActive, onPressHandler, onLayoutHandler) {
       const { activeTextColor, inactiveTextColor, textStyle, } = this.props;
       const textColor = isTabActive ? activeTextColor : inactiveTextColor;
       const fontWeight = isTabActive ? 'bold' : 'normal';
    
       return <Button
         key={`${name}_${page}`}
         accessible={true}
         accessibilityLabel={name}
         accessibilityTraits='button'
         onPress={() => onPressHandler(page)}
         onLayout={onLayoutHandler}
       >
         <View style={[styles.tab, this.props.tabStyle, ]}>
           <Text style={[{color: textColor, fontWeight, }, textStyle, ]}>
             {name}
           </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,
         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={true}
           showsHorizontalScrollIndicator={false}
           showsVerticalScrollIndicator={false}
           directionalLockEnabled={true}
           bounces={false}
           scrollsToTop={false}
         >
           <View
             style={[styles.tabs, {width: this.state._containerWidth, }, this.props.tabsContainerStyle, ]}
             ref={'tabContainer'}
             onLayout={this.onTabContainerLayout}
           >
             {this.props.tabs.map((name, page) => {
               const isTabActive = this.props.activeTab === page;
               const renderTab = this.props.renderTab || this.renderTab;
               return renderTab(name, page, isTabActive, this.props.goToPage, this.measureTab.bind(this, page));
             })}
             <Animated.View style={[tabUnderlineStyle, dynamicTabUnderline, this.props.underlineStyle, ]} />
           </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 < WINDOW_WIDTH) {
         width = WINDOW_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',
     },
    });
    
    
    1. 调用方式
    import CustomTabBar from '../Component/SongCustomTabBar'
    //其它代码省略
            <ScrollableTabView
              tabBarActiveTextColor={'red'}
                tabBarUnderlineStyle={Styles.lineStyle}
                renderTabBar={() => <CustomTabBar/>}>
                {
                  this.state.catList.map((item, index)=>{
                    return(
                      <View tabLabel={item.name} key={index}>
                        <SongPlayList cat={item.name} key={index}/>
                      </View>
                    )
                  })
                }
              </ScrollableTabView>
    

    相关文章

      网友评论

          本文标题:react native tab 指示器动画(参考网易云音乐)

          本文链接:https://www.haomeiwen.com/subject/izpzehtx.html