关于android:Android中View事件的分发第三篇

37次阅读

共计 10415 个字符,预计需要花费 27 分钟才能阅读完成。

本文系转载文章,浏览原文可获取源码,文章开端有原文链接

ps:demo 是基于 kotlin 语言来写的,代码是基于 Android Api 26 剖析的

后面写了 2 篇的 Android 中查看事件散发的一些源码剖析,演示演示和总结一些论断,这一篇接着写 的 Android 中 V IEW 事件配给物的论断。

查看的 onTouchEvent 默认不会超时事件,即它的返回值为 false,咱们查看一下查看中的 onTouchEvent 办法源码:

public boolean onTouchEvent(MotionEvent event) {

    ......
    if ((viewFlags & ENABLED_MASK) == DISABLED) {if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable;
    }
    if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}
    }

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        ......
        return true;
    }

    return false;

}

看这个办法的后果,发现返回是假的,能够看到的 onTouchEvent 办法默认返回值是假的,写一个演示验证一下咱们(案例 D):

案例 D

(1)新建一个 kotlin 语言的类 MyView 并继承于 View:

class MyView: View {

 constructor(context: Context): super(context) { }
constructor(context: Context,@Nullable attrs: AttributeSet): super(context,attrs) {
}
constructor(context: Context,@Nullable attrs: AttributeSet,defStyleAttr: Int): super(context,attrs,defStyleAttr) {
}

override fun onTouchEvent(event: MotionEvent?): Boolean {var b: Boolean = super.onTouchEvent(event)
    Log.d(D_Activity.TAG,"MyView----" + b)
    return b
}

}

(2)新建一个 kotlin 类型的 Activity,名为 D_Activity 并继承于 AppCompatActivity:

class D_Activity : AppCompatActivity() {

companion object {var TAG: String = "D_Activity"}
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_d_)
}

}

(3)D_Activity 对应的图文文件 activity_d_如下所示:

<?xml version=”1.0″ encoding=”utf-8″?>
<com.xe.views.MyView

xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#FF0000"
android:layout_height="match_parent"
>

</com.xe.views.MyView>

程序一开始运行的界面如下所示:

图片

用手触摸一下屏幕,打印如下:

07-27 13:23:07.878 19660-19660/com.xe.eventdemo3 D/D_Activity: MyView—-false

View 的 onTouchEvent 办法默认返回验证了这值为 false。

如果有 2 个视图有重叠且父元素不拦挡事件,都要做向下解决的事件,也就是生产挪动、向上等事件,那么最下面的视图有可能接管到事件处理,被笼罩的视图就接管不到到事件;如果在下面的视图不生产所有事件,那么就在上面的视图生产蕴含的事件,那么在该视图上面的视图就会接管到事件处理。这有点不太好了解,咱们举个例子(案例 E):

案例 E

(1)新建一个 kotlin 语言的类 MyView2 并继承于 View:

class MyView2: View {

constructor(context: Context): super(context) {
}
constructor(context: Context, @Nullable attrs: AttributeSet): super(context,attrs) {
}
constructor(context: Context, @Nullable attrs: AttributeSet, defStyleAttr: Int): super(context,attrs,defStyleAttr) {
}

override fun onTouchEvent(event: MotionEvent?): Boolean {when (event?.action) {
        MotionEvent.ACTION_DOWN -> {Log.d(E_Activity.TAG,"--MyView2--ACTION_DOWN")
        }
        MotionEvent.ACTION_MOVE -> {Log.d(E_Activity.TAG,"--MyView2--ACTION_MOVE")
        }
        MotionEvent.ACTION_UP -> {Log.d(E_Activity.TAG,"--MyView2--ACTION_UP")
        }
    }
    return true
}

}

(2)新建一个 kotlin 语言的类 MyView3 并继承于 View:

class MyView3: View {

constructor(context: Context): super(context) {
}
constructor(context: Context, @Nullable attrs: AttributeSet): super(context,attrs) {
}
constructor(context: Context, @Nullable attrs: AttributeSet, defStyleAttr: Int): super(context,attrs,defStyleAttr) {
}

override fun onTouchEvent(event: MotionEvent?): Boolean {when (event?.action) {
        MotionEvent.ACTION_DOWN -> {Log.d(E_Activity.TAG,"--MyView3--ACTION_DOWN")
        }
        MotionEvent.ACTION_MOVE -> {Log.d(E_Activity.TAG,"--MyView3--ACTION_MOVE")
        }
        MotionEvent.ACTION_UP -> {Log.d(E_Activity.TAG,"--MyView3--ACTION_UP")
        }
    }
    return true
}

}

(3)新建一个 kotlin 类型的 Activity,名叫 E_Activity:

class E_Activity : AppCompatActivity() {

companion object {var TAG: String = "E_Activity"}
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_e_)
}

}

(4)E_Activity 对应的布局文件 activity_e_:

<?xml version=”1.0″ encoding=”utf-8″?>
<FrameLayout

xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#00FF00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.xe.views.MyView2
    android:layout_width="400px"
    android:layout_height="400px"
    android:background="#FF0000">
</com.xe.views.MyView2>
<com.xe.views.MyView3
    android:layout_width="200px"
    android:layout_height="200px"
    android:background="#0000FF">
</com.xe.views.MyView3>

</FrameLayout>

程序一开始运行的界面如下所示:

图片

当咱们用手指触摸蓝色区域时,打印日志如下:

07-28 13:20:26.400 4554-4554/com.xe.eventdemo3 D/E_Activity: –MyView3–ACTION_DOWN
07-28 13:20:26.485 4554-4554/com.xe.eventdemo3 D/E_Activity: –MyView3–ACTION_MOVE
07-28 13:20:26.837 4554-4554/com.xe.eventdemo3 D/E_Activity: –MyView3–ACTION_MOVE
07-28 13:20:26.935 4554-4554/com.xe.eventdemo3 D/E_Activity: –MyView3–ACTION_UP

首先 activity_e_.xml 这个文件的布局采纳的是 FrameLayout,它的布局款式是,子视图如果不增加管制地位的属性,个别默认设置在 FrameLayout 的左上角,再增加的子视图后面增加的子视图给笼罩掉,也就是增加的子视图会看到后面增加子视图的下面;MyView2 是一开始就增加的,背景色彩是红色,而 MyView3 是增加的,色彩是蓝色,MyView3 会 MyView2 的下面;从日志打印进去,MyView2 天经地义的没有接管到任何事件,而 MyView3 接管到了向下、挪动、向上事件。

咱们想改一下,把 MyView3 的 onTouchEvent 办法的返回值改为假,再运行程序日志,再用手指触摸蓝色的区域,打印如下所示:

07-28 13:48:19.467 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView3–ACTION_DOWN
07-28 13:48:19.468 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView2–ACTION_DOWN
07-28 13:48:19.644 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView2–ACTION_MOVE
07-28 13:48:19.828 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView2–ACTION_MOVE
07-28 13:48:19.845 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView2–ACTION_MOVE
07-28 13:48:19.878 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView2–ACTION_MOVE
07-28 13:48:19.895 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView2–ACTION_MOVE
07-28 13:48:19.962 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView2–ACTION_MOVE
07-28 13:48:20.122 9254-9254/com.xe.eventdemo3 D/E_Activity: –MyView2–ACTION_UP

从日志,MyView2 的 TouchEvent 办法被调用了并进行了事件处理,是因为位于 MyView2 之上的 MyView3 没有处理事件,所以 MyView2 的父元素引发事件给了 MyView2。

在事件流传的过程中,如果子视图解决了事件(曾经解决了事件,就是 onTouchEvent 的返回值为 true),那么父元素 ViewGroup 的 onTouchEvent 办法一次都不会调用;如果子视图和父元素 ViewGroup 都没有处理事件,那么他们都会执行下来事件,而且还是子视图的返回如果优先执行,但它们的值依然是假的;子视图接管到了下来事件,没有生产,而父元素视图组进行了生产,那么子查看的羽绒还是比父元素的羽绒事件优先执行。

咱们先来看看 ViewGroup 的 dispatchTouchEvent 办法的一段源码:

public boolean dispatchTouchEvent(MotionEvent ev) {

    ......
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {intercepted = false;}
    } else {
        // There are no touch targets and this action is not an initial down
        // so this view group continues to intercept touches.
        intercepted = true;
    }
    ......

}

从 ViewGroup 的 dispatchTouchEvent 办法的办法的触发事件能够触发事件,触发事件是通过 disallowIntercept 变量和 onInterceptTouchEvent 办法管制的,触发事件无奈拦挡;传递过程是由内向内的即事件总是先传递给父元素 ViewGroup,而后再由父元素 ViewGroup 流传给子 View,子 View 通过 getParent().requestDisallowInterceptTouchEvent( 值参数) 属性能够在子元素中引入父元素的事件流传过程,下来事件除外。为了更好的了解,咱们来举个例子(案例 F):

案例 F

(1)新建一个 kotlin 的类 MyView4 并继承于 View(这里的 parent.requestDisallowInterceptTouchEvent(true) 语句等同于 Java 的 getParent().requestDisallowInterceptTouchEvent(true) 语句:

class MyView4: View {

constructor(context: Context): super(context) {
}
constructor(context: Context, @Nullable attrs: AttributeSet): super(context,attrs) {
}
constructor(context: Context, @Nullable attrs: AttributeSet, defStyleAttr: Int): super(context,attrs,defStyleAttr) {
}

override fun onTouchEvent(event: MotionEvent?): Boolean {when (event?.action) {
        MotionEvent.ACTION_DOWN -> {Log.d(F_Activity.TAG,"--MyView4--onTouchEvent--ACTION_DOWN")
            super.onTouchEvent(event)
            parent.requestDisallowInterceptTouchEvent(true)
        }
        MotionEvent.ACTION_MOVE -> {Log.d(F_Activity.TAG,"--MyView4--onTouchEvent--ACTION_MOVE")
        }
        MotionEvent.ACTION_UP -> {Log.d(F_Activity.TAG,"--MyView4--onTouchEvent--ACTION_UP")
        }
    }
    return true
}

}

(2)新建一个 kotlin 语言的类 MyFrameLayout 并继承于 FrameLayout:

class MyFrameLayout: FrameLayout {

constructor(context: Context): super(context) {
}
constructor(context: Context, @Nullable attrs: AttributeSet): super(context,attrs) {
}
constructor(context: Context, @Nullable attrs: AttributeSet, defStyleAttr: Int): super(context,attrs,defStyleAttr) {
}

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {when (ev?.action) {
        MotionEvent.ACTION_DOWN -> {Log.d(F_Activity.TAG,"--MyFrameLayout--onInterceptTouchEvent--ACTION_DOWN")
            super.onInterceptTouchEvent(ev)
            return false
        }
        MotionEvent.ACTION_MOVE -> {Log.d(F_Activity.TAG,"--MyFrameLayout--onInterceptTouchEvent--ACTION_MOVE")
        }
        MotionEvent.ACTION_UP -> {Log.d(F_Activity.TAG,"--MyFrameLayout--onInterceptTouchEvent--ACTION_UP")
        }
    }
    return true
}

}

(3)新建一个 kotlin 语言的 Activity,名为 F_Activity 并继承于 AppCompatActivity:

class F_Activity : AppCompatActivity() {

companion object {var TAG: String = "F_Activity"}
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_f_)
}

}

(4)F_Activity 对应的布局文件 activity_f_.xml 如下所示:

<?xml version=”1.0″ encoding=”utf-8″?>
<com.xe.views.MyFrameLayout

xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
tools:context="com.xe.eventdemo3.F_Activity">
<com.xe.views.MyView4
    android:layout_width="match_parent"
    android:layout_height="300px"
    android:background="#0000FF"/>

</com.xe.views.MyFrameLayout>

程序一开始运行的界面如下所示:

图片

当咱们用手触摸蓝色区域的时候,打印日志如下所示:

07-30 13:33:33.619 27770-27770/? D/F_Activity: –MyFrameLayout–onInterceptTouchEvent–ACTION_DOWN
07-30 13:33:33.620 27770-27770/? D/F_Activity: –MyView4–onTouchEvent–ACTION_DOWN
07-30 13:33:33.696 27770-27770/? D/F_Activity: –MyView4–onTouchEvent–ACTION_MOVE
07-30 13:33:33.764 27770-27770/? D/F_Activity: –MyView4–onTouchEvent–ACTION_MOVE
07-30 13:33:33.780 27770-27770/? D/F_Activity: –MyView4–onTouchEvent–ACTION_MOVE
07-30 13:33:33.974 27770-27770/? D/F_Activity: –MyView4–onTouchEvent–ACTION_MOVE
07-30 13:33:33.975 27770-27770/? D/F_Activity: –MyView4–onTouchEvent–ACTION_UP

从录音,MyView4 申请 MyFrameLayout 不拦挡事件胜利,咱们联合下面 ViewGroup 的 dispatchTouchEvent 办法源码进行剖析;

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {

intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed

} else {

intercepted = false;

}

当 MyFrameLayout 的 onInterceptTouchEvent 不对向下事件进行拦挡时办法,MyView4 接管到向下事件并调用 parent.requestDisallowInterceptTouchEvent(true) 语句让第二次调用 ViewGroup 的 dispatchTouchEvent 办法中的 disallowIntercept 为 true,从而让 MyFrameLayout 的 onInterceptTouchEvent 无奈再次调用,所以也让截取的值为假。

咱们把 parent.requestDisInterceptTouchEvent(true) 语句的参数改为 false 再运行程序,用手指再触摸蓝色的区域,打印日志:

07-30 13:55:01.733 31063-31063/? D/F_Activity: –MyFrameLayout–onInterceptTouchEvent–ACTION_DOWN
07-30 13:55:01.733 31063-31063/? D/F_Activity: –MyView4–onTouchEvent–ACTION_DOWN
07-30 13:55:01.786 31063-31063/? D/F_Activity: –MyFrameLayout–onInterceptTouchEvent–ACTION_MOVE

这里的 parent.DisallowInterceptTouchEvent(false) 语句的参数为 false 和不写该语句的成果是的,都是申请办法了 ViewGroup 的 dispatchTouchEvent 中的 disallowIntercept 是 false,手指次挪动的过程中,加上第二调用 MyFrameLayout 的 onInterceptTouchEvent 办法并且它的返回值为 true,也就是 move 事件的返回值为 true,所以 MyView4 的 move 事件被拦挡了,所以 MyView4 的 move 事件没有打印日志。

正文完
 0