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',
},
});
- 调用方式
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>