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

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

     
     
  • 相关阅读:
    eclipse中文乱码问题解决方案
    修改Tomcat的JDK目录
    Tomcat 5.5 修改服务器的侦听端口
    HTML DOM教程 27HTML DOM Button 对象
    HTML DOM教程 24HTML DOM Frameset 对象
    Navicat for MySQL v8.0.27 的注册码
    HTML DOM教程 25HTML DOM IFrame 对象
    Tomcat 5.5 的下载和安装
    android manifest相关属性
    ubuntu10.04 下 eclipse 小结
  • 原文地址:https://www.cnblogs.com/wukong1688/p/10904017.html
Copyright © 2011-2022 走看看