自定义viewAndroid自定义View

Android事件分发机制 & 面试解析

2022-02-10  本文已影响0人  JianLee

先总结一下事件分发机制的流程

ViewGroup 默认拦截事件吗?

答:默认不拦截任何事件,onInterceptTouchEvent返回的是false。

一旦有事件传递给view,view的onTouchEvent一定会被调用吗?

答:是的,因为view 本身没有onInterceptTouchEvent方法,所以只要事件来到view这里 就一定会走onTouchEvent方法。
并且默认都是消耗掉,返回true的。除非这个view是不可点击的,所谓不可点击就是clickable和longgclikable同时为fale
Button的clickable就是true 但是textview是false。

enable是否影响view的onTouchEvent返回值?

答:不影响,只要clickable和longClickable有一个为真,那么onTouchEvent就返回true。

requestDisallowInterceptTouchEvent 可以在子元素中干扰父元素的事件分发吗?如果可以,是全部都可以干扰吗?

答:肯定可以,但是down事件干扰不了。

dispatchTouchEvent每次都会被调用吗?

答:是的,onInterceptTouchEvent则不会。

滑动冲突问题如何解决 思路是什么?

要解决滑动冲突核心的方法就是2个 外部拦截也就是父亲拦截,另外就是内部拦截,也就是子view拦截法。 学会这2种 基本上所有的滑动冲突都是这2种的变种,而且核心代码思想都一样。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (ev.getAction()) {
            //down事件肯定不能拦截 拦截了后面的就收不到了
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (你的业务需求) {
                    //如果确定拦截了 就去自己的onTouchEvent里 处理拦截之后的操作和效果 即可了
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                //up事件 我们一般都是返回false的 一般父容器都不会拦截他。 因为up是事件的最后一步。这里返回true也没啥意义
                //唯一的意义就是因为 父元素 up被拦截。导致子元素 收不到up事件,那子元素 就肯定没有onClick事件触发了,这里的
                //小细节 要想明白
                intercepted = false;
                break;
            default:
                break;
        }
        return intercepted;
    }

子元素代码:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (如果父容器需要这个点击事件) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }//否则的话 就交给自己本身view的onTouchEvent自动处理了
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

父亲容器代码也要修改一下,其实就是保证父亲别拦截down:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            return false;

        }
        return true;
    }

怎么解决 ScrollView 嵌套 ScrollView 后内部 ScrollView 无法滑动的问题?

答:根本原因就是因为用户的滑动操作都被外部 ScrollView 拦截并消费了,导致内部 ScrollView 一直无法响应滑动事件。

这里选择使用内部拦截法来解决问题。首先需要让外部 ScrollView 拦截 ACTION_DOWN 之外的任何事件

class ExternalScrollView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

    override fun onInterceptTouchEvent(motionEvent: MotionEvent): Boolean {
        val intercepted: Boolean
        when (motionEvent.action) {
            MotionEvent.ACTION_DOWN -> {
                intercepted = false
                super.onInterceptTouchEvent(motionEvent)
            }
            else -> {
                intercepted = true
            }
        }
        return intercepted
    }

}

内部 ScrollView 判断自身是否还处于可滑动状态,如果滑动到了最顶部还想再往下滑动,或者是滑动到了最底部还想再往上滑动,那么就将事件都交由外部 ScrollView 处理,其它情况都直接拦截并消费掉事件,这样内部 ScrollView 就可以实现内部滑动了

class InsideScrollView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

    private var lastX = 0f

    private var lastY = 0f

    override fun dispatchTouchEvent(motionEvent: MotionEvent): Boolean {
        val x = motionEvent.x
        val y = motionEvent.y
        when (motionEvent.action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(true)
            }
            MotionEvent.ACTION_MOVE -> {
                val deltaX = x - lastX
                val deltaY = y - lastY
                if (abs(deltaX) < abs(deltaY)) { //上下滑动的操作
                    if (deltaY > 0) { //向下滑动
                        if (scrollY == 0) { //滑动到顶部了
                            parent.requestDisallowInterceptTouchEvent(false)
                        }
                    } else { //向上滑动
                        if (height + scrollY >= computeVerticalScrollRange()) { //滑动到底部了
                            parent.requestDisallowInterceptTouchEvent(false)
                        }
                    }
                }
            }
            MotionEvent.ACTION_UP -> {
            }
        }
        lastX = x
        lastY = y
        return super.dispatchTouchEvent(motionEvent)
    }

}

上一篇 下一篇

猜你喜欢

热点阅读