React Native中使用 react-native-scrollable-tab-view嵌套在ScrollView里,导致 子内容 在安卓上无法显示
问题:
0.9.0 或 0.8.0 版本的react-native-scrollable-tab-view
如果外层使用了一个ScrollView,在Android端上是无法显示的,iOS上显示无问题。
类似代码如下:
<ScrollView style={{flex: 1}}>this.props.navigation} item={item} showComment={false}/> 0.5, color: "#ccc", marginTop: 6, marginBottom: 6}}/> <ScrollableTabView tabBarPosition='top' initialPage={0} //默认为第一页 locked={false} //表示手指是否能拖动视图,默认为false(表示可以拖动)。设为true的话,我们只能点击Tab来切换视图。 renderTabBar={() => } tabBarUnderlineColor={'red'} scrollWithoutAnimation={true} tabBarBackgroundColor='#fff' tabBarActiveTextColor='#2c2c2c' tabBarInactiveTextColor='#666' > this.props.navigation} style={styles.textTab} tabLabel='评论(129)'/> this.props.navigation} style={styles.textTab} tabLabel='转发(209)'/> this.props.navigation} style={styles.textTab} tabLabel='赞(17)'/>
解决办法:
查看源码后发现Android端使用的是AnimatedViewPagerAndroid
,和iOS的实现有区别,可以把react-native-scrollable-tab-view/index.js
中所有Android的判断给去掉,全部使用iOS的实现方式,即可解决此问题。
例如,注释掉 node_modules/react-native-scrollable-tab-view/index.js 完后的代码如下:
const React = require('react'); const { Component } = React; const { ViewPropTypes } = ReactNative = require('react-native'); const createReactClass = require('create-react-class'); const PropTypes = require('prop-types'); const { Dimensions, View, Animated, ScrollView, Platform, StyleSheet, ViewPagerAndroid, InteractionManager, } = ReactNative; const TimerMixin = require('react-timer-mixin'); const SceneComponent = require('./SceneComponent'); const DefaultTabBar = require('./DefaultTabBar'); const ScrollableTabBar = require('./ScrollableTabBar'); const AnimatedViewPagerAndroid = Platform.OS === 'android' ? Animated.createAnimatedComponent(ViewPagerAndroid) : undefined; const ScrollableTabView = createReactClass({ mixins: [TimerMixin, ], statics: { DefaultTabBar, ScrollableTabBar, }, scrollOnMountCalled: false, propTypes: { tabBarPosition: PropTypes.oneOf(['top', 'bottom', 'overlayTop', 'overlayBottom', ]), initialPage: PropTypes.number, page: PropTypes.number, onChangeTab: PropTypes.func, onScroll: PropTypes.func, renderTabBar: PropTypes.any, style: ViewPropTypes.style, contentProps: PropTypes.object, scrollWithoutAnimation: PropTypes.bool, locked: PropTypes.bool, prerenderingSiblingsNumber: PropTypes.number, }, getDefaultProps() { return { tabBarPosition: 'top', initialPage: 0, page: -1, onChangeTab: () => {}, onScroll: () => {}, contentProps: {}, scrollWithoutAnimation: false, locked: false, prerenderingSiblingsNumber: 0, }; }, getInitialState() { const containerWidth = Dimensions.get('window').width; let scrollValue; let scrollXIOS; let positionAndroid; let offsetAndroid; if (Platform.OS === 'ios') { scrollXIOS = new Animated.Value(this.props.initialPage * containerWidth); const containerWidthAnimatedValue = new Animated.Value(containerWidth); // Need to call __makeNative manually to avoid a native animated bug. See // https://github.com/facebook/react-native/pull/14435 containerWidthAnimatedValue.__makeNative(); scrollValue = Animated.divide(scrollXIOS, containerWidthAnimatedValue); const callListeners = this._polyfillAnimatedValue(scrollValue); scrollXIOS.addListener( ({ value, }) => callListeners(value / this.state.containerWidth) ); } else { positionAndroid = new Animated.Value(this.props.initialPage); offsetAndroid = new Animated.Value(0); scrollValue = Animated.add(positionAndroid, offsetAndroid); const callListeners = this._polyfillAnimatedValue(scrollValue); let positionAndroidValue = this.props.initialPage; let offsetAndroidValue = 0; positionAndroid.addListener(({ value, }) => { positionAndroidValue = value; callListeners(positionAndroidValue + offsetAndroidValue); }); offsetAndroid.addListener(({ value, }) => { offsetAndroidValue = value; callListeners(positionAndroidValue + offsetAndroidValue); }); } return { currentPage: this.props.initialPage, scrollValue, scrollXIOS, positionAndroid, offsetAndroid, containerWidth, sceneKeys: this.newSceneKeys({ currentPage: this.props.initialPage, }), }; }, componentWillReceiveProps(props) { if (props.children !== this.props.children) { this.updateSceneKeys({ page: this.state.currentPage, children: props.children, }); } if (props.page >= 0 && props.page !== this.state.currentPage) { this.goToPage(props.page); } }, componentWillUnmount() { if (Platform.OS === 'ios') { this.state.scrollXIOS.removeAllListeners(); } else { this.state.positionAndroid.removeAllListeners(); this.state.offsetAndroid.removeAllListeners(); } }, goToPage(pageNumber) { if (Platform.OS === 'ios') { const offset = pageNumber * this.state.containerWidth; if (this.scrollView) { this.scrollView.getNode().scrollTo({x: offset, y: 0, animated: !this.props.scrollWithoutAnimation, }); } } else { if (this.scrollView) { if (this.props.scrollWithoutAnimation) { this.scrollView.getNode().setPageWithoutAnimation(pageNumber); } else { this.scrollView.getNode().setPage(pageNumber); } } } const currentPage = this.state.currentPage; this.updateSceneKeys({ page: pageNumber, callback: this._onChangeTab.bind(this, currentPage, pageNumber), }); }, renderTabBar(props) { if (this.props.renderTabBar === false) { return null; } else if (this.props.renderTabBar) { return React.cloneElement(this.props.renderTabBar(props), props); } else { return; } }, updateSceneKeys({ page, children = this.props.children, callback = () => {}, }) { let newKeys = this.newSceneKeys({ previousKeys: this.state.sceneKeys, currentPage: page, children, }); this.setState({currentPage: page, sceneKeys: newKeys, }, callback); }, newSceneKeys({ previousKeys = [], currentPage = 0, children = this.props.children, }) { let newKeys = []; this._children(children).forEach((child, idx) => { let key = this._makeSceneKey(child, idx); if (this._keyExists(previousKeys, key) || this._shouldRenderSceneKey(idx, currentPage)) { newKeys.push(key); } }); return newKeys; }, // Animated.add and Animated.divide do not currently support listeners so // we have to polyfill it here since a lot of code depends on being able // to add a listener to `scrollValue`. See https://github.com/facebook/react-native/pull/12620. _polyfillAnimatedValue(animatedValue) { const listeners = new Set(); const addListener = (listener) => { listeners.add(listener); }; const removeListener = (listener) => { listeners.delete(listener); }; const removeAllListeners = () => { listeners.clear(); }; animatedValue.addListener = addListener; animatedValue.removeListener = removeListener; animatedValue.removeAllListeners = removeAllListeners; return (value) => listeners.forEach(listener => listener({ value, })); }, _shouldRenderSceneKey(idx, currentPageKey) { let numOfSibling = this.props.prerenderingSiblingsNumber; return (idx < (currentPageKey + numOfSibling + 1) && idx > (currentPageKey - numOfSibling - 1)); }, _keyExists(sceneKeys, key) { return sceneKeys.find((sceneKey) => key === sceneKey); }, _makeSceneKey(child, idx) { return child.props.tabLabel + '_' + idx; }, renderScrollableContent() { // if (Platform.OS === 'ios') { const scenes = this._composeScenes(); return <Animated.ScrollView horizontal pagingEnabled automaticallyAdjustContentInsets={false} contentOffset={{ x: this.props.initialPage * this.state.containerWidth, }} ref={(scrollView) => { this.scrollView = scrollView; }} onScroll={Animated.event( [{ nativeEvent: { contentOffset: { x: this.state.scrollXIOS, }, }, }, ], { useNativeDriver: true, listener: this._onScroll, } )} onMomentumScrollBegin={this._onMomentumScrollBeginAndEnd} onMomentumScrollEnd={this._onMomentumScrollBeginAndEnd} scrollEventThrottle={16} scrollsToTop={false} showsHorizontalScrollIndicator={false} scrollEnabled={!this.props.locked} directionalLockEnabled alwaysBounceVertical={false} keyboardDismissMode="on-drag" {...this.props.contentProps} > {scenes} ; // } else { // const scenes = this._composeScenes(); // return [{ // nativeEvent: { // position: this.state.positionAndroid, // offset: this.state.offsetAndroid, // }, // }, ], // { // useNativeDriver: true, // listener: this._onScroll, // }, // )} // ref={(scrollView) => { this.scrollView = scrollView; }} // {...this.props.contentProps} // > // {scenes} // ; // } }, _composeScenes() { return this._children().map((child, idx) => { let key = this._makeSceneKey(child, idx); return <SceneComponent key={child.key} shouldUpdated={this._shouldRenderSceneKey(idx, this.state.currentPage)} style={{width: this.state.containerWidth, }} > {this._keyExists(this.state.sceneKeys, key) ? child :} ; }); }, _onMomentumScrollBeginAndEnd(e) { const offsetX = e.nativeEvent.contentOffset.x; const page = Math.round(offsetX / this.state.containerWidth); if (this.state.currentPage !== page) { this._updateSelectedPage(page); } }, _updateSelectedPage(nextPage) { let localNextPage = nextPage; if (typeof localNextPage === 'object') { localNextPage = nextPage.nativeEvent.position; } const currentPage = this.state.currentPage; this.updateSceneKeys({ page: localNextPage, callback: this._onChangeTab.bind(this, currentPage, localNextPage), }); }, _onChangeTab(prevPage, currentPage) { this.props.onChangeTab({ i: currentPage, ref: this._children()[currentPage], from: prevPage, }); }, _onScroll(e) { if (Platform.OS === 'ios') { const offsetX = e.nativeEvent.contentOffset.x; if (offsetX === 0 && !this.scrollOnMountCalled) { this.scrollOnMountCalled = true; } else { this.props.onScroll(offsetX / this.state.containerWidth); } } else { const { position, offset, } = e.nativeEvent; this.props.onScroll(position + offset); } }, _handleLayout(e) { const { width, } = e.nativeEvent.layout; if (!width || width <= 0 || Math.round(width) === Math.round(this.state.containerWidth)) { return; } if (Platform.OS === 'ios') { const containerWidthAnimatedValue = new Animated.Value(width); // Need to call __makeNative manually to avoid a native animated bug. See // https://github.com/facebook/react-native/pull/14435 containerWidthAnimatedValue.__makeNative(); scrollValue = Animated.divide(this.state.scrollXIOS, containerWidthAnimatedValue); this.setState({ containerWidth: width, scrollValue, }); } else { this.setState({ containerWidth: width, }); } this.requestAnimationFrame(() => { this.goToPage(this.state.currentPage); }); }, _children(children = this.props.children) { return React.Children.map(children, (child) => child); }, render() { let overlayTabs = (this.props.tabBarPosition === 'overlayTop' || this.props.tabBarPosition === 'overlayBottom'); let tabBarProps = { goToPage: this.goToPage, tabs: this._children().map((child) => child.props.tabLabel), activeTab: this.state.currentPage, scrollValue: this.state.scrollValue, containerWidth: this.state.containerWidth, }; if (this.props.tabBarBackgroundColor) { tabBarProps.backgroundColor = this.props.tabBarBackgroundColor; } if (this.props.tabBarActiveTextColor) { tabBarProps.activeTextColor = this.props.tabBarActiveTextColor; } if (this.props.tabBarInactiveTextColor) { tabBarProps.inactiveTextColor = this.props.tabBarInactiveTextColor; } if (this.props.tabBarTextStyle) { tabBarProps.textStyle = this.props.tabBarTextStyle; } if (this.props.tabBarUnderlineStyle) { tabBarProps.underlineStyle = this.props.tabBarUnderlineStyle; } if (overlayTabs) { tabBarProps.style = { position: 'absolute', left: 0, right: 0, [this.props.tabBarPosition === 'overlayTop' ? 'top' : 'bottom']: 0, }; } return this.props.style, ]} onLayout={this._handleLayout}> {this.props.tabBarPosition === 'top' && this.renderTabBar(tabBarProps)} {this.renderScrollableContent()} {(this.props.tabBarPosition === 'bottom' || overlayTabs) && this.renderTabBar(tabBarProps)} ; }, }); module.exports = ScrollableTabView; const styles = StyleSheet.create({ container: { flex: 1, }, scrollableContentAndroid: { flex: 1, }, });
其中,注释掉的代码 如图显示即可!
附注:
笔者在实践中发现,虽然这种修改确实可以让子内容显示,但同时又产生了另外一个bug,将导致 Tab 点击就报错了。
所以,笔者最终的设计,还是 取消 ScrollView 中嵌套 ScrollableTabView
本博客地址: wukong1688
本文原文地址:https://www.cnblogs.com/wukong1688/p/10904017.html
转载请著名出处!谢谢~~