RN中的TabView和Swiper手势冲突之初探解决方案

抛出问题:

react-native-tab-view中的TabView和react-native-swiper的Swiper滚动冲突,版本如下:

"react-native-swiper": "^1.5.14",
"react-native-tab-view": "^2.15.2",
注意:加上这个 ^ 貌似会向上自动升级,所以实际上可能下载的是github上最新的版本。

以上两个库是在"react-native": "0.63.4"的版本下测试的。

假设我要实现如下图所示功能,TabView嵌套了轮播图,滚动轮播图和切换TabView互不干扰,但事与愿违,滚动轮播图会导致切换了Tab。


示例图.jpg

探索解决方案:

1、牺牲TabView 的滑动切换功能,TabView有一个swipeEnabled属性,设置为false禁止滑动切换TabView。当然这种用户体验不好。

  

2、能否通过swipeEnabled的开启与禁用来解决滚动冲突?怎么监听手指触摸的是哪块视图?带着问题我们来看下源码:

  • 打开react-native-swiper/src/index.js文件,轮播图的滑动主要是通过ScrollView来实现的,如下这段代码:
renderScrollView = pages => {
    return (
      
        {pages}
      
    )
  }
  • 打开react-native-tab-view/src/TabView.tsx文件,从106-213行代码是主要的实现,代码有点长,这里就不贴了。
    顺着代码下来能够找到Pager.tsx文件,如下贴个核心代码:

          
            
              {children}
            
          

TabView的滑动切换功能是通过 'react-native-gesture-handler'的PanGestureHandler来实现的,通过enabled来控制是否能切换Tab。

  • 尝试解决冲突,代码如下(这里展示不是完整代码,只为了说明):
import { TabView, TabBar, SceneMap } from 'react-native-tab-view'
export class HomeScreen extends Component {
    constructor(props) {
        super(props)
        this.state = {
            index: 0,
            routes: [
                { key: 'first', title: '猜你喜欢', onChage: this.onChage },
                { key: 'second', title: '今日特价', onChage: this.onChage },
                { key: 'third', title: '发现好店', onChage: this.onChage }
            ],
            swipeEnabled: true
        }
    }
    onChage = (e) => {
        console.log(e);
        if (e == 'onTouchStartCapture' ) {
            if(this.state.swipeEnabled){
                this.setState({
                    swipeEnabled: false
                })
            }
        } else {
            if(!this.state.swipeEnabled){
                this.setState({
                    swipeEnabled: true
                })
            }
        }
    }
    render() {
         return(
                )
    }
}

export function SwiperView(props) {
    const { data, onChage } = props
    // console.log(onChage);
    return (
         {
                onChage && onChage('onTouchStartCapture')
            }}
            onMomentumScrollEnd={(e)=>{
                onChage && onChage('onMomentumScrollEnd')
            }}
            style={{
                height: 230,
                backgroundColor: colors.bgColorfa,
                paddingHorizontal: 10,
            }}
            paginationStyle={{ bottom: 5 }}
            loop={false}
            dotStyle={{ backgroundColor: colors.dotunsel }}
            activeDotStyle={{ backgroundColor: colors.theme }}>
            {
                data.map((arrData, index) => {
                    return (
                        
                        
                    )
                })
            }
        
    )
}

按照以上代码的效果变好了一点,不过出现个问题:onMomentumScrollEnd概率性没回调,如下所示:

LOG      onTouchStartCapture -- swipeEnabled=false
LOG      onMomentumScrollEnd -- swipeEnabled=true
LOG      onTouchStartCapture -- swipeEnabled?false

这个时候TabView进行了切换,也就是说当onMomentumScrollEnd回调完成,onTouchStartCapture也回调了,但是this.setState()的时候swipeEnabled还没变成false,导致Tab切换了,所以onMomentumScrollEnd就没有回调。

综上所述:当你手速比较快地不停切换,通过swipeEnabled的禁用和开启还是概率性出现手势冲突问题。此方案不可行。

3、能不能通过官方提供的Panresponder来解决手势冲突呢?毕竟它有提供onPanResponderGrant、onPanResponderRelease、onPanResponderTerminate这些方法让我们去做一些操作。按道理TabView和Swiper的实质都是两个滚动View,我们可以监听手势是否放在Swiper上,从而禁止TabView的滚动;当手势完全释放的时候,两个View就都开启滚动。
这种思路后面会着重研究下,有空再写一篇博文分享。

4、RN还有一个比较好用的TabView组件react-native-scrollable-tab-view,在满足以下两种情况,TabView和Swiper就不会冲突了:

  • 安装以下版本:
"react-native": "0.57.7",
"react-native-scrollable-tab-view": "0.10.0",
"react-native-swiper": "1.5.14",
注意要固定版本号,不要加^

你可能会问:react-native为啥不用上0.60+的版本?因为新的react-native版本去掉了ViewPagerAndroid,把它抽离出一个单独的库(@react-native-community/viewpager)。而react-native-scrollable-tab-view和react-native-swiper的旧版本都用上了react-native提供的ViewPagerAndroid。

你可能会问:不能升级这两个库到最新版本来适配RN的最新版本吗?你去试试就知道了,报错是肯定有的。@react-native-community/viewpager的最新版本用ts写的,估计项目要支持ts才行,或者去找一个js版本的试试。

  • Swiper的loop要设置为true,也就是轮播滚动循环,如果为false,则当你滚动到最后一个的时候,再滑动就是切换Tab了。

这里演示个例子,代码如下所示:

import React, { Component } from 'react'
import {
    StyleSheet,
    View,
    Text
} from 'react-native'
import NavbarView from '../../component/public/navbarView/navbarViewWhite'
import theme from '../../common/theme';
import ScrollableTabView from 'react-native-scrollable-tab-view';
import Swiper from 'react-native-swiper'
export default class TestDemo extends Component {
    render() {
        return (
            
                
                 {
                        console.log(tab.i)
                    }}
                    locked={false}
                    style={{}}
                    tabBarActiveTextColor={theme.content_color}
                    tabBarInactiveTextColor={'#646566'}
                    tabBarTextStyle={{ fontSize: 14 }}>
                    
                        
                        
                    
                    
                        
                        
                    
                    
                        
                        
                    
                

            
        );
    }

}
const SwiperView = class extends Component {
    render() {
        return (
            
                
                    
                        swiper1
                    
                    
                        swiper2
                    
                    
                        swiper3
                    
                
            

        )
    }
}
const styles = StyleSheet.create({
    mainStyle: {
        flex: 1
    },
})

抛出问题:为什么这种情况下没有出现手势冲突了?

来看看两个库的核心源码:

  • react-native-scrollable-tab-view/index.js
renderScrollableContent() {
    if (Platform.OS === 'ios') {
      const scenes = this._composeScenes();
      return 
          {scenes}
      ;
    } else {
      const scenes = this._composeScenes();
      return  { this.scrollView = scrollView; }}
        {...this.props.contentProps}
      >
        {scenes}
      ;
    }
  },
  • react-native-swiper/src/index.js
renderScrollView = pages => {
     if (Platform.OS === 'ios') {
      return (
        
          {pages}
        
       )
     }
     return (
       
         {pages}
       
     )
  }

你会发现两个库的核心组件都是用ViewPagerAndroid,所以你可以大胆猜测下:ViewPagerAndroid在手势监听方面做了一些操作,当你使用两个ViewPagerAndroid相互嵌套的话,根据手势或者触摸哪个View来决定滚动哪个。感兴趣的可以去看下ViewPagerAndroid的源代码。

就好像两个ScrollView相互嵌套,如果你不通过手势监听去禁止另外一个不滚动的话,那就会导致其中有个ScrollView滚动不了。

我还做了一个实验,就是把else部分的代码注释掉,统一用成ScrollView,结果是手势冲突了,切换Swiper的时候会出现切换成Tab。

对Tab和Swiper的手势冲突的初探就到这了,后面有空继续探究。

参考链接

  • 手势冲突踩坑
  • react-native-gesture-handler官方文档

你可能感兴趣的:(RN中的TabView和Swiper手势冲突之初探解决方案)