事件傳遞機(jī)制面試問(wèn)什么封孙?

自定義Behavior的藝術(shù)探索-仿UC瀏覽器主頁(yè)

關(guān)于Android的事件傳遞,應(yīng)該從兩個(gè)方面來(lái)說(shuō)匿垄,5.0以前移宅,5.0以后
在5.0以前:
我們一般關(guān)注三個(gè)方法

  • dispatchTouchEvent
  • onInterceptTouchEvent
  • onTouchEvent
    ViewGroup是繼承自View的,而onInterceptTouchEvent方法是ViewGroup獨(dú)有的椿疗,可以對(duì)事件進(jìn)行攔截漏峰,攔截之后事件是無(wú)法傳遞到子view上的(包括本次事件序列的move、up等)
    一個(gè)點(diǎn)擊事件發(fā)生會(huì)依次經(jīng)過(guò)activity的dispatchTouchEvent 届榄,ViewGroup的dispatchTouchEvent浅乔,ViewGroup的onInterceptTouchEvent。
    下面分兩種情況:
  • onInterceptTouchEvent返回true
    表示ViewGroup對(duì)本事件進(jìn)行攔截,會(huì)直接調(diào)用ViewGroup的onTouchEvent靖苇,如果onTouchEvent不處理該事件席噩,會(huì)調(diào)用Activity的onTouchEvent,后續(xù)的事件不會(huì)再傳遞到ViewGroup了贤壁。
  • onInterceptTouchEvent返回false
    事件會(huì)傳遞到子view上悼枢,會(huì)調(diào)用View的dispatchTouchEvent,View的onTouchEvent脾拆,如果子View的onTouchEvent沒(méi)有處理事件馒索,事件會(huì)經(jīng)過(guò)ViewGroup的onTouchEvent,如果ViewGroup的onTouchEvent沒(méi)有處理假丧,會(huì)調(diào)用Activity的onTouchEvent双揪,后續(xù)的事件不會(huì)再傳遞到ViewGroup了。
  • onInterceptTouchEvent先返回false后返回true包帚,則子view無(wú)法接收后續(xù)的事件(但會(huì)接收到一個(gè)cancel事件渔期,cancel事件會(huì)重置mFirstTarget,這也就是后續(xù)事件不會(huì)再執(zhí)行onInterceptTouchEvent的原因)

一些細(xì)節(jié):
onInterceptTouchEvent在兩種情況下會(huì)調(diào)用:

  • down事件的時(shí)候坑定會(huì)調(diào)用
  • 子view處理了down事件渴邦,后續(xù)的move也會(huì)執(zhí)行到onInterceptTouchEvent

5.0以后:引入嵌套滑動(dòng)
引入了嵌套滑動(dòng)CoordinatorLayout之后疯趟,一般是要配合Behavior使用,在使用過(guò)程中我們需要關(guān)注兩種情況:

  • 某個(gè)view監(jiān)聽(tīng)另一個(gè)view的狀態(tài)變化谋梭,例如大小信峻、位置、顯示狀態(tài)等
  • 某個(gè)view監(jiān)聽(tīng)CoordinatorLayout里的滑動(dòng)狀態(tài)
    對(duì)于第一種情況瓮床,我們關(guān)心的是:
    layoutDependsOn和onDependentViewChanged方法盹舞,
    對(duì)于第二種情況,我們關(guān)心的是:
    onStartNestedScroll和onNestedPreScroll方法隘庄。

先來(lái)說(shuō)第一組方法:
layoutDependsOn和onDependentViewChanged的調(diào)用時(shí)機(jī)
這個(gè)主要是在CoordinatorLayout類的onAttachedToWindow()方法踢步,內(nèi)部定義了一個(gè)ViewTreeObserver,然后給這個(gè)ViewTreeObserver添加了一個(gè)監(jiān)聽(tīng)器OnPreDrawListener丑掺,用來(lái)監(jiān)聽(tīng)view的draw操作获印。然后在這個(gè)監(jiān)聽(tīng)器中循環(huán)遍歷所有的子View,一旦某個(gè)子View發(fā)生了draw操作街州,就循環(huán)遍歷所有子view兼丰,如果發(fā)現(xiàn)某個(gè)view上有Behavior,就調(diào)用Behavior中的這些方法唆缴。

看下第二組方法:
寫(xiě)個(gè)demo來(lái)測(cè)試一下CoordinatorLayout鳍征、Behavior、子View之間事件的流程

package com.example.huozhenpeng.myapplication;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 郵箱 huohacker@sina.com
 */

public class MCoordinateLayout extends CoordinatorLayout {


    public MCoordinateLayout(Context context) {
        super(context);
    }

    public MCoordinateLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("abc","coordinatorlayout------onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.e("abc","coordinatorlayout------onTouchEvent");
        return super.onTouchEvent(ev);
    }
}

package com.example.huozhenpeng.myapplication;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 郵箱 huohacker@sina.com
 */

public class MBehavior extends CoordinatorLayout.Behavior<View> {

    public MBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.e("abc","behavior----onInterceptTouchEvent");
        return super.onInterceptTouchEvent(parent, child, ev);
//        return true;
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.e("abc","behavior----onTouchEvent");
        return super.onTouchEvent(parent, child, ev);
    }
}

package com.example.huozhenpeng.myapplication;

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;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 郵箱 huohacker@sina.com
 */

public class MTextView extends android.support.v7.widget.AppCompatTextView {
    public MTextView(Context context) {
        super(context);
    }

    public MTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("abc","textview---------onTouchEvent");
        return super.onTouchEvent(event);
//        return true;
    }
}

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.huozhenpeng.myapplication.MainActivity">


   <com.example.huozhenpeng.myapplication.MCoordinateLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent">

       <com.example.huozhenpeng.myapplication.MTextView
           android:layout_width="100dp"
           android:layout_height="50dp"
           android:background="#ff00ff"
           android:text="hhh "
           app:layout_behavior="com.example.huozhenpeng.myapplication.MBehavior"
           />
   </com.example.huozhenpeng.myapplication.MCoordinateLayout>




</android.support.constraint.ConstraintLayout>

image.png
package com.example.huozhenpeng.myapplication;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 郵箱 huohacker@sina.com
 */

public class MBehavior extends CoordinatorLayout.Behavior<View> {

    public MBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.e("abc","behavior----onInterceptTouchEvent");
//        return super.onInterceptTouchEvent(parent, child, ev);
        return true;
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.e("abc","behavior----onTouchEvent");
        return super.onTouchEvent(parent, child, ev);
    }
}

image.png
package com.example.huozhenpeng.myapplication;

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;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 郵箱 huohacker@sina.com
 */

public class MTextView extends android.support.v7.widget.AppCompatTextView {
    public MTextView(Context context) {
        super(context);
    }

    public MTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("abc","textview---------onTouchEvent");
//        return super.onTouchEvent(event);
        return true;
    }
}

image.png

大概總結(jié)一下:事件傳遞到CoordinatorLayout的時(shí)候琐谤,CoordinatorLayout會(huì)把事件交給Behavior(不管是在onInterceptTouchEvent()方法中還是在onTouchEvent()方法中)蟆技,Behavior具有中斷事件的能力,如果Behavior中斷了事件,事件也是傳遞不到子view的质礼。

我們以NestedScrollView為例來(lái)研究下第二組方法
在onTouchEvent()方法中調(diào)用startNestedScroll()

@Override
    public boolean startNestedScroll(int axes, int type) {
        return mChildHelper.startNestedScroll(axes, type);
    }

緊接著

public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    setNestedScrollingParentForType(type, p);
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
                    nestedScrollAxes, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
        }
        return false;
    }

其實(shí)就是去調(diào)用CoordinatorLayout 類的onStartNestedScroll方法了

 @Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                        target, axes, type);
                handled |= accepted;
                lp.setNestedScrollAccepted(type, accepted);
            } else {
                lp.setNestedScrollAccepted(type, false);
            }
        }
        return handled;
    }

接著就去調(diào)用Behavior的onStartNestedScroll()方法了,onNestedScrollAccepted()也是這個(gè)調(diào)用流程旺聚。
然后去調(diào)用dispatchNestedScroll()方法

image.png
image.png

關(guān)于自定義Behavior時(shí),重寫(xiě)B(tài)ehavior中的一些方法:


image.png

在CoordinatorLayout的onStartNestedScroll方法中會(huì)根據(jù)其子view(直接的字view)的Behavior的onStartNestedScroll方法的返回值來(lái)給view的LayoutParams設(shè)置一個(gè)boolean值眶蕉,后續(xù)的方法都會(huì)對(duì)該值進(jìn)行判斷


image.png

比如說(shuō)在onNestedScroll方法中

CoordinatorLayout

看下CoordinatorLayout的onMeasure和onLayout的部分代碼

@Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

  ......
final Behavior b = lp.getBehavior();
           if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                   childHeightMeasureSpec, 0)) {
               onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                       childHeightMeasureSpec, 0);
           }

..........
     }
@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior behavior = lp.getBehavior();

            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
                onLayoutChild(child, layoutDirection);
            }
        }
    }

這說(shuō)明如果View的Behavior重寫(xiě)了onMeasureChild砰粹、onLayoutChild方法,那么會(huì)優(yōu)先調(diào)用Behavior中的方法造挽,而不會(huì)去調(diào)用CoordinatorLayout中的方法

有個(gè)疑問(wèn)是假如CoordinatorLayout的onNestedPreScroll在將事件分發(fā)給有Behavior的子View時(shí)碱璃,是可以同時(shí)將一個(gè)事件分發(fā)給所有實(shí)現(xiàn)了Behavior的子View嗎?看下代碼就理解了

@Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {
        int xConsumed = 0;
        int yConsumed = 0;
        boolean accepted = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted(type)) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                mTempIntPair[0] = mTempIntPair[1] = 0;
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type);

                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
                        : Math.min(xConsumed, mTempIntPair[0]);
                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
                        : Math.min(yConsumed, mTempIntPair[1]);

                accepted = true;
            }
        }

        consumed[0] = xConsumed;
        consumed[1] = yConsumed;

        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饭入,一起剝皮案震驚了整個(gè)濱河市嵌器,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谐丢,老刑警劉巖爽航,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異乾忱,居然都是意外死亡讥珍,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門窄瘟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)衷佃,“玉大人,你說(shuō)我怎么就攤上這事蹄葱∈弦澹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵图云,是天一觀的道長(zhǎng)觅赊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)琼稻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任饶囚,我火速辦了婚禮帕翻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萝风。我一直安慰自己嘀掸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布规惰。 她就那樣靜靜地躺著睬塌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揩晴,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天勋陪,我揣著相機(jī)與錄音,去河邊找鬼硫兰。 笑死诅愚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的劫映。 我是一名探鬼主播违孝,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泳赋!你這毒婦竟也來(lái)了雌桑?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祖今,失蹤者是張志新(化名)和其女友劉穎校坑,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體衅鹿,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撒踪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了大渤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片制妄。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泵三,靈堂內(nèi)的尸體忽然破棺而出耕捞,到底是詐尸還是另有隱情,我是刑警寧澤烫幕,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布俺抽,位于F島的核電站,受9級(jí)特大地震影響较曼,放射性物質(zhì)發(fā)生泄漏磷斧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一捷犹、第九天 我趴在偏房一處隱蔽的房頂上張望弛饭。 院中可真熱鬧,春花似錦萍歉、人聲如沸侣颂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)憔晒。三九已至藻肄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拒担,已是汗流浹背嘹屯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留澎蛛,地道東北人抚垄。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谋逻,于是被迫代替她去往敵國(guó)和親呆馁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容