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

2020-03-17  本文已影响0人  一天前_73b6

此功能是在 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>
上一篇下一篇

猜你喜欢

热点阅读