React Native笔记(二):ViewPager2嵌套rn组件滑动冲突解决思路

背景

由于业务需求,要求使用原生的ViewPager2在rn中实现tab侧滑功能,而tab页面中会有水平滑动的FlatList列表,这样就会造成滑动冲突的情况。

解决思路:因为ViewPager2被声明为final的,所以只能从FlatList去动手,于是本文基于谷歌官方的NestedScrollableHost进行改造。

步骤

改造NestedScrollableHost给rn使用

isFromRN字段标识该组件是由rn创建而来的
ReactFindViewUtil.findView可以找到当前根view中声明了nativeID属性的rn组件view

class NestedScrollableHost : FrameLayout {
    
    
    private var isFromRN: Boolean = false

    constructor(context: Context, isFromRN: Boolean) : super(context){
    
    
        this.isFromRN = isFromRN
    }
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    private var touchSlop = 0
    private var initialX = 0f
    private var initialY = 0f
    private val parentViewPager: ViewPager2?
        get() {
    
    
            var v: View? = parent as? View
            while (v != null && v !is ViewPager2) {
    
    
                v = v.parent as? View
            }
            return v as? ViewPager2
        }

    private val child: View?
        get() {
    
    
            return when {
    
    
                isFromRN -> {
    
    
                    var view : View? = ReactFindViewUtil.findView(this, "FlatList")
                    return view
                }
                childCount > 0 -> {
    
    
                    getChildAt(0)
                }
                else -> {
    
    
                    null
                }
            }
        }


    init {
    
    
        touchSlop = ViewConfiguration.get(context).scaledTouchSlop
    }

    private fun canChildScroll(orientation: Int, delta: Float): Boolean {
    
    
        val direction = -delta.sign.toInt()
        return when (orientation) {
    
    
            0 -> child?.canScrollHorizontally(direction) ?: false
            1 -> child?.canScrollVertically(direction) ?: false
            else -> throw IllegalArgumentException()
        }
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    
    
        ev?.let {
    
     handleInterceptTouchEvent(it) }
        return super.dispatchTouchEvent(ev)
    }

    private fun handleInterceptTouchEvent(e: MotionEvent) {
    
    
        val orientation = parentViewPager?.orientation ?: return

        // Early return if child can't scroll in same direction as parent
        if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
    
    
            return
        }

        if (e.action == MotionEvent.ACTION_DOWN) {
    
    
            initialX = e.x
            initialY = e.y
            parent.requestDisallowInterceptTouchEvent(true)
        } else if (e.action == MotionEvent.ACTION_MOVE) {
    
    
            val dx = e.x - initialX
            val dy = e.y - initialY
            val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL

            // assuming ViewPager2 touch-slop is 2x touch-slop of child
            val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
            val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
//            if (scaledDx > touchSlop || scaledDy > touchSlop) {
    
    
                if (isVpHorizontal == (scaledDy > scaledDx)) {
    
    
                    // Gesture is perpendicular, allow all parents to intercept
                    parent.requestDisallowInterceptTouchEvent(false)
                } else {
    
    
                    // Gesture is parallel, query child if movement in that direction is possible
                    if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
    
    
                        // Child can scroll, disallow all parents to intercept
                        parent.requestDisallowInterceptTouchEvent(true)
                    } else {
    
    
                        // Child cannot scroll, allow all parents to intercept
                        parent.requestDisallowInterceptTouchEvent(false)
                    }
                }
//            }
        }else{
    
    
            parent.requestDisallowInterceptTouchEvent(false)
        }
    }
}

child为嵌套在NestedScrollableHost中的FlatList组件

public class NestedScrollableHostManager extends ViewGroupManager<NestedScrollableHost> {
    
    
    @NonNull
    @Override
    public String getName() {
    
    
        return "NestedScrollableHost";
    }

    @NonNull
    @Override
    protected NestedScrollableHost createViewInstance(@NonNull ThemedReactContext reactContext) {
    
    
        return new NestedScrollableHost(reactContext, true);
    }
}

rn端使用

const NestedScrollableHost = Platform.OS == 'android' ? requireNativeComponent("NestedScrollableHost") : <></>

interface Props{
    
    
    style?: StyleProp<ViewStyle>
}
const MyNestedScrollableHost: React.FC<Props> = (props) => {
    
    
    return (
        <NestedScrollableHost
            {
    
    ...props}
        >
            {
    
    props.children}
        </NestedScrollableHost>
    )
}

export default MyNestedScrollableHost

在有可能跟原生组件造成滑动冲突的地方引入

<MyNestedScrollableHost>
	<FlatList
		nativeID={
    
    "FlatList"}
		...
    />
</MyNestedScrollableHost>

nativeID的值要与ReactFindViewUtil.findView第二个参数一致

本文有任何不足或者有更好的实现思路的话,欢迎在评论中指出~

猜你喜欢

转载自blog.csdn.net/weixin_40855673/article/details/123752111