实现在视图上滑动并单击。是否可以拦截手势,对其进行分析并将其传递回父视图?

时间:2019-09-13 13:16:08

标签: android kotlin

我希望有一个ViewGroup可以拦截用户的手势(因为MotionEventonInterceptTouchEvents()中的onTouchEvent()序列都没有关系) ,对其进行分析,然后决定-是自行处理还是将其返回传递给此ViewGroup父级

更具体地说,让我们假设我在TextView中有一个LinearLayout。而此LinearLayout又位于某个滚动容器内-通常是{strong>水平滚动的RecyclerView(或ViewPagerScrollView)内>。

我想在该TextView上实现滑动(左,右),将其传递到封闭的容器(RecyclerView)并在那里进行处理,滚动。另一方面,我还希望允许用户直接单击TextView并获得回调。因此,这听起来像是一个已知的问题,需要同时单击和滑动某些View

但是我的问题是我不知道如何将整个手势放在LinearLayout中,然后将其返回传递给父母RecyclerView),前提是该手势不是点击,而是像滑动一样。

例如,我以以下方式扩展了LinearLayout(请参见下文)。然后,我想拦截手势,检查它是否是直接点击(由触摸倾斜给出的小半径范围内的点击),如果是,则触发回调,否则我要整个手势将被传递回父母以在那里处理,例如-在LinearLayout上滑动:

class SwipeLinearLayout : LinearLayout {

    private var listener: (() -> Unit)? = null

    constructor(context: Context): this(context, null)

    constructor(context: Context, attributes: AttributeSet?): this(context, attributes, 0)

    constructor(context: Context, attributes: AttributeSet?, defStyleAttr: Int): super(context, attributes, defStyleAttr)

    fun setOnDirectClickListener(l: (() -> Unit)?) {
        listener = l
    }

    private var dispatching = false
    private var prevX: Float = 0.0f
    private var prevY: Float = 0.0f
    private var startX: Float = 0.0f
    private var startY: Float = 0.0f
    private var directClick: Boolean = false
    private var startClick: Boolean = false
    private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
    private val track = mutableListOf<MotionEvent>()
    private var trackOpen = true

    fun dispatchAll() {
        dispatching = true
        var y = 0.0f
        loop@ for (i in 0 until track.size) {
            val it = track[i]
            when (it.actionMasked) {
                MotionEvent.ACTION_MOVE -> {
                    if (y == 0.0f) y = it.y
                    if (abs(y - it.y) > touchSlop) {
                        it.recycle()
                        continue@loop  // skip non-swipe motion events
                    }
                }
            }
            (parent as View).dispatchTouchEvent(it)
            it.recycle()
        }
        track.clear()
        dispatching = false
    }

    fun distance(event: MotionEvent): Float {
        val x = abs(event.x - prevX)
        val y = abs(event.y - prevY)
        return x*x + y*y
    }

    fun initCoordinates(event: MotionEvent) {
        prevX = event.x
        prevY = event.y
    }

    fun _invalidate() {
        directClick = false
        startClick = false
        track.clear()
    }

    // ------------------
    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        var result = super.onInterceptTouchEvent(event)
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> result = false
        }
        return result
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (dispatching) {
            return false
        }
        var result = super.onTouchEvent(event)
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                startX = event.x
                startY = event.y
                trackOpen = true
            }
        }
        if (trackOpen) {
            val e = when (event.actionMasked) {
                MotionEvent.ACTION_UP -> MotionEvent.obtain(event).apply { action = MotionEvent.ACTION_CANCEL }
                else -> MotionEvent.obtain(event)
            }
            track.add(e)
        }
        result = _onTouchEvent(event)
        return result
    }

    fun _onTouchEvent(event: MotionEvent): Boolean {
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                initCoordinates(event)
                startClick = true
                return true
            }
            MotionEvent.ACTION_MOVE -> {
                directClick = distance(event) < touchSlop
                if (!directClick) {
//                    track.add(MotionEvent.obtain(event).apply { action = MotionEvent.ACTION_CANCEL })
                    trackOpen = false
                    dispatchAll()
                    return true
                }
                return directClick
            }
            MotionEvent.ACTION_UP -> {
                initCoordinates(event)
                if (startClick && directClick) {
                    _invalidate()
                    listener?.invoke()
                    return true
                } else {
                    dispatchAll()
                }
            }
            MotionEvent.ACTION_CANCEL -> {
                initCoordinates(event)
                dispatchAll()
            }
        }
        return false
    }
}

我所做的是我记录了整个手势,并将其存储在track数组中,当我识别出该手势不是直接单击时(即,其运动偏差超出了触摸倾斜的范围) ),我将整个手势通过dispatchAll()方法传递给 Parent

但是,这无法正常工作-有时可以工作,而滑动则可以直接单击(单击)分开,有时不行,而且我在父级上看到一些错误的动作或根本没有动作

我还认为我的技术是不可接受的:我从未在Android文档和教程中看到有人记录该手势并将其传递回 Parent 。但是我不知道该怎么做,因为我需要分析手势,如果它不是我期望的手势,我完全不会使用它,而是让它由 Parent处理。我该如何实现?

P.S .:我已经尝试过GestureDetector,但并没有帮助我。另外,GestureDetector不会阻止父级处理直接点击,但我需要仅在该父级 内的LinearLayout中对其进行处理>,然后让 Parent 处理其他手势。

PSS:我也不想在某些根视图或Activity中截取手势,因为它破坏了封装:我想拥有一个自定义LinearLayout,我可以将其包括在内任何布局,而自定义LinearLayout可以发挥作用-在单击和在其上滑动之间进行清晰的区分。因此,如果实际上是滑动手势,则将其放到更多的根容器中;否则,如果单击,则将其处理并向监听器触发回调。

0 个答案:

没有答案