乐趣区

关于android:httpsmpweixinqqcomsi95HCZIn8wfvkY2qGVjow

ps:浏览原文可获取 demo 源码,文章开端有原文链接

ps:源码是基于 android api 27 来剖析的,demo 是用 kotlin 语言写的

在 Android View 事件的散发中,如果 View 的 dispatchTouchEvent 办法被调用,那么它就不会再往下散发,也不会进行拦挡,因为 View 是最底层的元素;在事件散发中,View 是充当子元素的,而不能充当父元素,它的 2 个办法施展着很好的合作能力,那就是 dispatchTouchEvent 办法和 onTouchEvent 办法;首先咱们先看 dispatchTouchEvent 办法:

public boolean dispatchTouchEvent(MotionEvent event) {

   
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        //1、// We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {return false;}
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        //2、// Defensive cleanup for new gesture
        stopNestedScroll();}

    //3、if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        
        //4、if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {result = true;}
        //5、if (!result && onTouchEvent(event)) {result = true;}
    }

    if (!result && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    //6、// Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {stopNestedScroll();
    }

    return result;
}

正文 1 示意如果不能取得焦点或者不存在一个 View,那么就不处理事件;正文 2 示意如果咱们点击 View 时,其余的依赖滑动都要先停下;正文 3 示意过滤掉一些不合理的事件,比如说弹出一个 Dialog 把 View 给挡住了;正文 4 示意(1)首先判断监听 ListenerInfo 对象不为 null 且咱们通过 setOnTouchListener 设置了监听,即是实现 OnTouchListener 接口的 onTouch 办法,(2)如果有实现就判断以后的 View 状态是不是 ENABLED(enabled 属性是否为 true),(3)如果实现的 OnTouchListener 的 onTouch 中返回 true,这(1)(2)(3)同时成立示意处理事件并调用;正文 5 示意如果正文 4 的条件不成立,那么执行正文 5 的代码,即触摸事件,阐明 OnTouchListener 的 onTouch 办法优先级比 onTouchEvent 办法的优先级高,在 Android 中 View 事件的散发第一篇这篇文章已验证;正文 6 示意如果这是手势的结尾,则在嵌套滚动后清理。

回过头来看,正文 5 的代码,本篇文章中的另外一个角色 onTouchEvent 办法;

public boolean onTouchEvent(MotionEvent event) {

    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    //7、final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    //8、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;}
    }

    //9、if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {
            case MotionEvent.ACTION_UP:
                ......
                //10、performClick();
                ......
                break;
            }
        }

        return true;
    }

    return false;

}

正文 8 示意如果是 DISABLED,也就是 View.setEnabled(false), 那么返回正文 7 的布尔值,正文 7 前面再剖析;正文 9 示意如果能够点击或者此视图能够在悬停又或长按时显示工具提醒,那么就返回 true,也就是生产事件;正文 10 示意当手指抬起来的时候会调用 OnClickListener.onClick 办法(能够点击 performClick 办法看看,如果以后的 View 重写了 onTouchEvent 办法,那么它的 up 事件返回值必须是 super.onTouchEvent(event)),如果以后 View 设置有 OnClickListener 事件的话。

正文 7 示意是否能够点击,由以后 View 的 CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE 决定,也就是 clickable、longClickable 和 contextClickable 属性是否为 true;View 的 longClickable 属性默认都为 false,clickable 属性要分状况, 如果是 Button 的 clickable 属性默认为 true, 而 TextView 的 clickable 属性默认为 false。

View(设置有 OnClickListener 事件)的 enable(ENABLED 值)属性不影响 super.onTouchEvent 的默认返回值(返回为 true),即便 View 是 disable(DISABLED 值)状态的,只有它的 clickable 或者 longClickable 或者 contextClickable 属性有一个为 true,那么它的 super.onTouchEvent 就返回 true,然而如果 View 真是 disable(DISABLED 值)状态的并且设置有 OnClickListener 事件,那么 OnClickListener.onClick 办法不会被回调;又如果 View 的 clickable、longClickable 和 contextClickable 属性都为 false,enable 属性为 true 并且设置有 OnClickListener 事件,那么 OnClickListener.onClick 办法会被回调,super.onTouchEvent(event) 的返回值也会为 true。

咱们来验证蓝色文字这一段话,上面咱们来写一个 demo;

(1)新建一个 kotlin 语言类型的 Activity,名叫 MainActivity:

class MainActivity: AppCompatActivity() {

companion object {var TAG: String = "MainActivity"}
var mView: View? = null
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    mView = findViewById(R.id.my_view)
    mView?.isLongClickable = false
    mView?.isContextClickable = false
    mView?.isClickable = true
    mView?.isEnabled = true
    mView?.setOnClickListener(object : View.OnClickListener {override fun onClick(v: View?) {Log.d(TAG,"----onClick----")
        }
    })
}

}

(2)新建一个 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 consume: Boolean = super.onTouchEvent(event)
    when(event?.action) {
        MotionEvent.ACTION_DOWN -> {Log.d(MainActivity.TAG,"--MotionEvent.ACTION_DOWN--isConsume =" + consume)
        }
        MotionEvent.ACTION_MOVE -> {Log.d(MainActivity.TAG,"--MotionEvent.ACTION_MOVE--isConsume =" + consume)
        }
        MotionEvent.ACTION_UP -> {Log.d(MainActivity.TAG,"--MotionEvent.ACTION_UP--isConsume =" + consume)
        }
    }
    return consume
}

}

(3)新建 MainActivity 对应的布局文件 activity_main:

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

xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FF00"
tools:context="com.xe.eventdemo4.MainActivity">

</com.xe.eventdemo4.MyView>

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

图片

咱们触摸一下绿色的屏幕,日志打印如下所示:

08-24 13:19:23.085 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_DOWN–isConsume = true
08-24 13:19:23.143 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.210 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.227 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.293 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.310 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.460 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.506 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_UP–isConsume = true
08-24 13:19:23.517 15226-15226/com.xe.eventdemo4 D/MainActivity: —-onClick—-

从日志看出 contextClickable、longClickable 和 clickable 属性只有有一个为 true 时,super.onTouchEvent(event) 就返回为 true。

咱们只把 enable 属性改为 false,即 mView?.isEnabled = true 改为 mView?.isEnabled = false,其余代码不变,而后运行程序,用手指触摸绿色屏幕,日志打印如下所示:

08-24 13:27:31.960 18435-18435/com.xe.eventdemo4 W/Activity: Slow Operation: Activity com.xe.eventdemo4/.MainActivity onCreate took 586ms
08-24 13:27:37.732 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_DOWN–isConsume = true
08-24 13:27:37.807 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:27:37.858 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:27:37.875 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:27:37.958 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:27:38.019 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_UP–isConsume = true

从日志看出 contextClickable、longClickable 和 clickable 属性只有有一个为 true 时,即便 enable 属性为 false,super.onTouchEvent(event) 返回值还是 true,同时 OnClickListener.onClick(该办法被调用的前提是 enable 属性为 true)办法不会被调用。

咱们把 clickable 属性改为 false,enable 属性改为 true,运行程序,用手指触摸绿色屏幕,日志打印如下所示:

08-24 13:56:03.837 20118-20118/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_DOWN–isConsume = true
08-24 13:56:03.949 20118-20118/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:56:04.189 20118-20118/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:56:04.284 20118-20118/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_UP–isConsume = true
08-24 13:56:04.292 20118-20118/com.xe.eventdemo4 D/MainActivity: —-onClick—-

从日志能够看出 super.onTouchEvent(event) 返回的是 true,OnClickListener.onClick 办法也会被调用。

退出移动版