UNPKG

tuya-panel-kit

Version:

a functional component library for developing tuya device panels!

237 lines (234 loc) 6.82 kB
/* eslint-disable react-native/split-platform-components */ /* eslint-disable react/require-default-props */ /* eslint-disable prettier/prettier */ import React from 'react'; import PropTypes from 'prop-types'; import { ScrollView, Animated, Platform, ViewPagerAndroid, View, ViewPropTypes, } from 'react-native'; import Utils from './utils'; const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); class TabContent extends React.Component { static defaultProps = { distanceToChangeTab: 0.3, swipeable: true, animated: true, useViewPagerOnAndroid: true, }; static propTypes = { /** * 切换tab的距离 */ distanceToChangeTab: PropTypes.number, /** * 内容宽度 */ containerWidth: PropTypes.number, /** * 滚动改变回调函数 */ onScrollValueChange: PropTypes.func, /** * 切换视图的回调 */ onChange: PropTypes.func, /** * 是否可滑动视图 */ swipeable: PropTypes.bool, /** * 所激活视图的key */ activeKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), /** * tab内容源 */ panels: PropTypes.oneOfType([PropTypes.element, PropTypes.array]), /** * 切换视图是否有动画 */ animated: PropTypes.bool, /** * 是否在安卓上使用viewPager */ useViewPagerOnAndroid: PropTypes.bool, /** * 包裹tab的容器样式 */ style: ViewPropTypes.style, }; constructor(props) { super(props); const activeIndex = Utils.getActiveIndex(props.panels, props.activeKey); const width = props.containerWidth; this.state = { scrollX: new Animated.Value((activeIndex !== -1 ? activeIndex : 0) * width), activeIndex, }; this.nextTab = activeIndex; this.distance = 0; } componentDidMount() { this.prevTab = this.state.activeIndex; this.state.scrollX.addListener(({ value }) => { const scrollValue = value / this.props.containerWidth; this.props.onScrollValueChange(scrollValue); }); } componentWillReceiveProps(nextProps) { if (this.props.activeKey !== nextProps.activeKey && nextProps.activeKey !== undefined) { this.goToTab(Utils.getActiveIndex(nextProps.panels, nextProps.activeKey), true); } } componentDidUpdate() { this.prevTab = this.state.activeIndex; } onScroll = e => { if (e) { if (Platform.OS === 'android') { const { position, offset } = e.nativeEvent; requestAnimationFrame(() => { this.state.scrollX.setValue((position + offset) * this.props.containerWidth); }); } } }; onMomentumScrollEnd = e => { const offsetX = e.nativeEvent.contentOffset.x; const page = this.getOffsetIndex(offsetX, this.props.containerWidth); if (this.state.activeIndex !== page) { this.goToTab(page, false); } }; onPageSelected = e => { const index = e.nativeEvent.position; this.setState( { activeIndex: index, }, () => { this.props.onChange && this.props.onChange(index); } ); this.nextTab = index; }; setScrollView = scrollView => { this.scrollView = scrollView; this.scrollTo(this.state.activeIndex); }; getTabPanes = () => { const { panels, activeKey } = this.props; return React.Children.map(panels, (child, index) => { if (!child) return; const isActive = activeKey === child.key; const pane = React.cloneElement(child, { isActive, children: child.props.children, style: [child.props.style, { width: this.props.containerWidth }], }); return <View key={child.key || `tab_${index}`}>{pane}</View>; }); }; getOffsetIndex = (current, width, threshold = this.props.distanceToChangeTab) => { const ratio = Math.abs(current / width); const direction = ratio > this.state.activeIndex ? 'right' : 'left'; const index = Math.floor(ratio); switch (direction) { case 'right': return ratio - index > threshold ? index + 1 : index; case 'left': return 1 - ratio + index > threshold ? index : index + 1; default: return Math.round(ratio); } }; goToTab = (index, force) => { if (!force && this.nextTab === index) return; this.nextTab = index; const { panels, onChange } = this.props; if (index >= 0 && index < panels.length) { if (!force) { onChange && onChange(index); } this.setState( { activeIndex: index, }, () => { requestAnimationFrame(() => { this.scrollTo(this.state.activeIndex, this.props.animated); }); } ); } }; scrollTo = (index, animated = true) => { if (Platform.OS === 'android' && this.props.useViewPagerOnAndroid) { if (this.viewPager) { if (animated) { this.viewPager.setPage(index); } else { this.viewPager.setPageWithoutAnimation(index); } return; } } const { containerWidth } = this.props; if (containerWidth) { const offset = index * containerWidth; if (this.scrollView && this.scrollView._component && this.scrollView._component.scrollTo) { this.scrollView._component.scrollTo({ x: offset, animated }); } } }; render() { const { swipeable, style, useViewPagerOnAndroid } = this.props; if (Platform.OS === 'android' && useViewPagerOnAndroid) { return ( <ViewPagerAndroid key="$content" keyboardDismissMode="on-drag" initialPage={this.state.activeIndex} scrollEnabled={swipeable} onPageScroll={this.onScroll} style={[{ flex: 1 }, style]} onPageSelected={this.onPageSelected} ref={ref => { this.viewPager = ref; }} > {this.getTabPanes()} </ViewPagerAndroid> ); } return ( <AnimatedScrollView ref={this.setScrollView} key="scrollContent" horizontal={true} onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: this.state.scrollX } } }], { useNativeDriver: true, })} automaticallyAdjustContentInsets={false} onMomentumScrollEnd={this.onMomentumScrollEnd} scrollEventThrottle={5} scrollsToTop={false} showsHorizontalScrollIndicator={false} scrollEnabled={swipeable} alwaysBounceVertical={false} directionalLockEnabled={true} keyboardDismissMode="on-drag" pagingEnabled={true} contentContainerStyle={style} > {this.getTabPanes()} </AnimatedScrollView> ); } } export default TabContent;