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

批处理
• 阅读 2470

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

效果展示

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

代码

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>
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这