zoukankan      html  css  js  c++  java
  • [RN] React Native中使用 react-native-scrollable-tab-view嵌套在ScrollView里,导致 子内容 在安卓上无法显示

    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}}>
                    <ItemMoment navigation={this.props.navigation} item={item} showComment={false}/>
    
                    <View style={{height: 0.5, color: "#ccc", marginTop: 6, marginBottom: 6}}/>
    
                    <ScrollableTabView
                        tabBarPosition='top'
                        initialPage={0} //默认为第一页
                        locked={false} //表示手指是否能拖动视图,默认为false(表示可以拖动)。设为true的话,我们只能点击Tab来切换视图。
                        renderTabBar={() => <ScrollableTabBar/>}
                        tabBarUnderlineColor={'red'}
                        scrollWithoutAnimation={true}
    
                        tabBarBackgroundColor='#fff'
                        tabBarActiveTextColor='#2c2c2c'
                        tabBarInactiveTextColor='#666'
                    >
    
                        <CommentTab navigation={this.props.navigation} style={styles.textTab} tabLabel='评论(129)'/>
                        <CommentTab navigation={this.props.navigation} style={styles.textTab} tabLabel='转发(209)'/>
                        <CommentTab navigation={this.props.navigation} style={styles.textTab} tabLabel='赞(17)'/>
    
                    </ScrollableTabView>
    
                </ScrollView>

    解决办法:

    查看源码后发现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 <DefaultTabBar {...props} />;
        }
      },
    
      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}
          </Animated.ScrollView>;
        // } else {
        //   const scenes = this._composeScenes();
        //   return <AnimatedViewPagerAndroid
        //     key={this._children().length}
        //     style={styles.scrollableContentAndroid}
        //     initialPage={this.props.initialPage}
        //     onPageSelected={this._updateSelectedPage}
        //     keyboardDismissMode="on-drag"
        //     scrollEnabled={!this.props.locked}
        //     onPageScroll={Animated.event(
        //       [{
        //         nativeEvent: {
        //           position: this.state.positionAndroid,
        //           offset: this.state.offsetAndroid,
        //         },
        //       }, ],
        //       {
        //         useNativeDriver: true,
        //         listener: this._onScroll,
        //       },
        //     )}
        //     ref={(scrollView) => { this.scrollView = scrollView; }}
        //     {...this.props.contentProps}
        //   >
        //     {scenes}
        //   </AnimatedViewPagerAndroid>;
        // }
      },
    
      _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={{ this.state.containerWidth, }}
          >
            {this._keyExists(this.state.sceneKeys, key) ? child : <View tabLabel={child.props.tabLabel}/>}
          </SceneComponent>;
        });
      },
    
      _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 <View style={[styles.container, this.props.style, ]} onLayout={this._handleLayout}>
          {this.props.tabBarPosition === 'top' && this.renderTabBar(tabBarProps)}
          {this.renderScrollableContent()}
          {(this.props.tabBarPosition === 'bottom' || overlayTabs) && this.renderTabBar(tabBarProps)}
        </View>;
      },
    });
    
    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

    转载请著名出处!谢谢~~

     
     
  • 相关阅读:
    ContentProvider
    铃声设置
    TTS技术
    http://www.w3cschool.cc/jqueryui/jqueryui-tutorial.html
    HttpHelper
    .net面试题
    函数和原型
    关于递增运算符
    CSS学习笔记
    CSS/CSS3 如何实现元素水平居中
  • 原文地址:https://www.cnblogs.com/wukong1688/p/10904017.html
Copyright © 2011-2022 走看看