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
JavaScript
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"
}
});