Android-自定义View的事件分发及拦截机制简单流程先体验再研究(场景?疑问? 待续...)

简书上有一篇写的蛮不错的...Android View的事件分发及拦截机制分析,不过我们也自己打印下看看流程吧! 然后琢磨下哪些场景需要处理这个事件分发,需要解决这个事件冲突。小白印象中,ViewPaper与横向滑动的RecycleView/ListView事件冲突的问题比较经常看到!(这个小白会看sdk文档里面的一些方法介绍).

小白也打印下看看妮?

CustomConstraintLayout.java

 package me.heyclock.hl.customcopy;

import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class CustomConstraintLayout extends ConstraintLayout {
    public CustomConstraintLayout(Context context) {
        this(context, null);
    }

    public CustomConstraintLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomConstraintLayout onTouchEvent" );
        return super.onTouchEvent(event);
    }
}

CustomTextView.java

package me.heyclock.hl.customcopy;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
    public CustomTextView(Context context) {
        this(context, null);
    }

    public CustomTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0, 0);
    }

    public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomTextView dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomTextView onTouchEvent" );
        return super.onTouchEvent(event);
    }
}

setContentView(R.layout.custom_viewgroup_event);

<?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffff00ee">

    <me.heyclock.hl.customcopy.CustomTextView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="#ffaa00ee"
        android:text="大大的中间"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <me.heyclock.hl.customcopy.CustomTextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#ffddaaee"
        android:text="小小的中间"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</me.heyclock.hl.customcopy.CustomConstraintLayout>

当点击大大的中间以及小小的中间,结果都是类似:

10-30 03:47:28.676 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent
    CustomConstraintLayout onTouchEvent
10-30 03:47:43.932 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent
    CustomConstraintLayout onTouchEvent

如码友分析的一样。事件的传递顺序是上册控件依次传递到下层控件,直到View. 而事件的处理顺序,则由底层的View依次返给上级,直到最顶层进行处理。

   事件传递的返回值:true,拦截,不继续;false,不拦截,继续流程。
  事件处理的返回值:true,处理了,不用审核了;false,给上级处理。
  初始情况下,返回值都是false。

为true就表示处理了,不用在往上传递了。So,我们就可以选择某个传递的环节消费掉该事件,或者拦截掉事件传递,让其不往下层传递;比如在CustomConstraintLayout 的onInterceptTouchEvent中返回true进行拦截,这样中间的空间就不会响应了。

     @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
        //return super.onInterceptTouchEvent(ev);
        return true;
    }
10-30 04:04:30.397 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomConstraintLayout onTouchEvent
10-30 04:04:32.916 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomConstraintLayout onTouchEvent

再比如仅仅把CustomTextView的

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomTextView onTouchEvent" );
        //return super.onTouchEvent(event);
        return true;
    }

事件传递继续到底,onTouch不再往上到顶

 10-30 04:11:57.038 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent
10-30 04:11:57.132 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent

目前小白也只是知道这样的一个效果。现在有两个问题要考虑下:

1. 运行两遍?

2. 有什么场景我们可以考虑实践一下?

第一个问题,因为down和up都会走.....所以会进行两次事件传递。也就是down先传递到底层,然后up再次传递到底层(如果你进行了滑动,还会有move事件传递...)。到底层后我们在View里面return true - 消费掉了这个事件,所以上层不再收到相关事件处理。

第二个问题,有什么场景需要做特殊处理呢?

比如我们要实现这样的处理(子View可以进行左右滑动, 同时, 当在子View上面进行上下滑动时,依然是上层ViewGroup进行上下滑动.)

思路一下:

1. 子View控件onTouchEvent返回true,保证响应处理相关touch事件(down,move,up)

2. 父ViewGroup在onInterceptTouchEvent中进行处理和拦截move事件(由于左右滑动时,子View需要做响应,所以只能拦截上下滑动的情况 - 这种情况返回true,表示父ViewGroup自己处理move事件,不再交给子View)

So,我们简单定义一下ViewGroup和View

CustomTextView.java

package me.heyclock.hl.customcopy;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
    public CustomTextView(Context context) {
        this(context, null);
    }

    public CustomTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0, 0);
    }

    public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomTextView dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    private float x1, x2;
    private float y1, y2;
    private float swing = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomTextView onTouchEvent" + event.getAction());
        //return super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x1 = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:   ///< 上下滑动已经被上层拦截了,所以这里肯定就是左右滑动了
                x2 = event.getX();

                ///< 5作为阀值就可以了,可以根据效果调整
                if(y1 - y2 > 5) {         ///< 上
                } else if(y2 - y1 > 5) { ///< 下
                } else if(x1 - x2 > 5) { ///< 左
                    swing = x1 - x2;
                    ///< 采用系统View类的滚动方法
                    scrollBy((int) swing, 0);
                    invalidate();
                } else if(x2 - x1 > 5) { ///< 右
                    swing = -(x2 - x1);
                    ///< 采用系统View类的滚动方法
                    scrollBy((int) swing, 0);
                    invalidate();
                }
                x1 = x2;
                y1 = y2;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
}

CustomConstraintLayout.java

package me.heyclock.hl.customcopy;

import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class CustomConstraintLayout extends ConstraintLayout {
    public CustomConstraintLayout(Context context) {
        this(context, null);
    }

    public CustomConstraintLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout dispatchTouchEvent"+ ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    private float x1, x2;
    private float y1, y2;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" + ev.getAction());
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x1 = ev.getX();
                y1 = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                x2 = ev.getX();
                y2 = ev.getY();

                ///< 5作为阀值就可以了,可以根据效果调整
                if(y1 - y2 > 15) {         ///< 上
                    return true;
                } else if(y2 - y1 > 15) { ///< 下
                    return true;
                } else if(x1 - x2 > 15) { ///< 左
                } else if(x2 - x1 > 15) { ///< 右
                }
                x1 = x2;
                y1 = y2;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 滚动相关(上下滑动)
     */
    private float wx2;
    private float wy2;
    private float swing = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                wx2 = event.getX();
                wy2 = event.getY();

                ///< 5作为阀值就可以了,可以根据效果调整
                if(y1 - wy2 > 5) {         ///< 上
                    swing = y1 - wy2;
                    ///< 采用系统View类的滚动方法
                    scrollBy(0, (int) swing);
                    invalidate();

                    Log.e("test", "上滑动");
                } else if(wy2 - y1 > 5) { ///< 下
                    swing = -(wy2 - y1);
                    ///< 采用系统View类的滚动方法
                    scrollBy(0, (int) swing);
                    invalidate();

                    Log.e("test", "下滑动");
                } else if(x1 - wx2 > 50) { ///< 左
                } else if(wx2 - x1 > 50) { ///< 右
                }
                x1 = wx2;
                y1 = wy2;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onTouchEvent(event);
    }
}

布局 custom_viewgroup_event.xml

 <?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffff00ee">

    <me.heyclock.hl.customcopy.CustomTextView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="#ffaa00ee"
        android:text="大大的中间"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</me.heyclock.hl.customcopy.CustomConstraintLayout>

效果...

image

还有问题,就是上下滑动子View的外部还不行?

这个也很简单了,有时候一直盯着子View,所以忘记了onTouch事件处理顺序了!只需要把父ViewGroup的onTouchEvent返回true不就可以接收后续的move,up事件了么....

     /**
     * 滚动相关(上下滑动)
     */
    private float wx2;
    private float wy2;
    private float swing = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                x1 = event.getX();
                y1 = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                wx2 = event.getX();
                wy2 = event.getY();

                ///< 5作为阀值就可以了,可以根据效果调整
                if(y1 - wy2 > 5) {         ///< 上
                    swing = y1 - wy2;
                    ///< 采用系统View类的滚动方法
                    scrollBy(0, (int) swing);
                    invalidate();

                    Log.e("test", "上滑动");
                } else if(wy2 - y1 > 5) { ///< 下
                    swing = -(wy2 - y1);
                    ///< 采用系统View类的滚动方法
                    scrollBy(0, (int) swing);
                    invalidate();

                    Log.e("test", "下滑动");
                } else if(x1 - wx2 > 50) { ///< 左
                } else if(wx2 - x1 > 50) { ///< 右
                }
                x1 = wx2;
                y1 = wy2;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;//super.onTouchEvent(event);
    }

这样我们就大体实现了这个目标...

image

这个的理解我们先这样,回去再清醒清醒。下一篇打算去看sdk的官方文档ViewGroup | Android Developers 以及一些个常用的方法,比如类似这种getParent().requestDisallowInterceptTouchEvent(false);

下班之前妮看两篇文章吧...

事件分发:onTouchEvent返回false一定不执行ACTION_MOVE吗?

(转)浅谈onInterceptTouchEvent、onTouchEvent与onTouch - oZuiJiaoWeiYang的专栏 - CSDN博客

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容