View的繪制與事件分發(fā)機(jī)制

1. Android視圖構(gòu)成

Android視圖構(gòu)成.png

2. View 的繪制流程

當(dāng) Activity 接收到焦點(diǎn)的時(shí)候,它會(huì)被請(qǐng)求繪制布局,該請(qǐng)求由 Android 的 framework 層處理。繪制是從根節(jié)點(diǎn)開始,從上到下開始遍歷,對(duì)布局樹遞歸地進(jìn)行 measure、layout 和 draw。整個(gè) View 樹的繪圖流程在 ViewRoot.java 類的 performTraversals() 函數(shù)展開,該函數(shù)所做 的工作可簡(jiǎn)單概況為是否需要重新計(jì)算視圖大小(measure)、是否需要重新安置視圖的位置(layout)、以及是否需要重繪(draw),流程圖如下:

View 樹的繪圖流程.png

繪制順序.png

3. 事件分發(fā)機(jī)制

(1) 為什么要使用事件分發(fā)
android中View是樹形結(jié)構(gòu)的,View可能會(huì)重疊在一起,當(dāng)我們點(diǎn)擊一個(gè)地方有多個(gè)View都可以響應(yīng),這個(gè)點(diǎn)擊事件應(yīng)該分配給誰呢?為了解決這個(gè)問題,就有了事件分發(fā)機(jī)制。

(2) 三個(gè)重要的事件分發(fā)事件

Android事件分發(fā)機(jī)制主要由“事件分發(fā)”—>“事件攔截”—>“事件響應(yīng)”這三步來進(jìn)行邏輯控制的。

  • 事件分發(fā):dispatchTouchEvent(MotionEvent event)
  • 事件攔截:onInterceptTouchEvent()
  • 事件響應(yīng):onTouchEvent()

(3) 事件分發(fā)流程
問題:如下圖所示,點(diǎn)擊View1的位置時(shí),由于View重疊,View1、GroupView 和 RootView都可響應(yīng)事件,這個(gè)點(diǎn)擊事件應(yīng)該分配給誰呢?

View重疊及對(duì)應(yīng)的視圖結(jié)構(gòu).png

分析:點(diǎn)擊View1位置時(shí),如其他父控件都不攔截,僅View1對(duì)事件進(jìn)行攔截,則事件分發(fā)流程如下

事件分發(fā)流程.png

相關(guān)結(jié)論

a. Activity 和 View 是沒有攔截事件的,即無onInterceptTouchEvent()方法。原因是

  • Activity 作為事件的原始分發(fā)者,若攔截了事件,則整個(gè)屏幕都會(huì)無法響應(yīng)事件。
  • View 作為事件傳遞的最末端,要么消費(fèi)處理掉事件,要么不處理并回傳事件給activity,因?yàn)橄蛳聸]有子控件了,所有沒必要再對(duì)事件攔截并向下分發(fā)。

b. 屏幕被點(diǎn)擊后,事件傳遞過程為:Activity—>PhoneWindow—>DecorView—>GroupView—>...—>View,即點(diǎn)擊事件發(fā)生后,事件先傳到Activity、再傳到ViewGroup、最終再傳到View。

c. 點(diǎn)擊事件的分發(fā)過程如下:dispatchTouchEvent—>onTouchListener的OnTouch方法—>onTouchEvent—>onClickListener的onClick方法。從而也可以看出onTouch優(yōu)先于onClick執(zhí)行,即事件傳遞的順序是先經(jīng)過onTouch,再傳遞到onClick。如:

為一個(gè)按鈕同事注冊(cè)點(diǎn)擊事件和觸摸事件。

package comi.example.liy.mytestdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

/**
 * Created by liy on 2019-12-18 8:55
 */
public class EventDispatchActivity extends AppCompatActivity {

    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_dispatch);
        button = findViewById(R.id.btn_event);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("liy", "onClick execute");
            }
        });

        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d("liy", "onTouch execute, action " + event.getAction());
                /*return false;*/

                switch (event.getAction()) {// 獲取當(dāng)前觸摸事件的Action
                    case MotionEvent.ACTION_DOWN://手指按下
                        Log.d("liy", "手指按下: " + event.getAction());
                        break;
                    case MotionEvent.ACTION_MOVE://手指滑動(dòng)
                        //獲取手指滑動(dòng)到新的位置
                        Log.d("liy", "手指滑動(dòng): " + event.getAction());
                        break;
                    case MotionEvent.ACTION_UP://手指抬起
                        Log.d("liy", "手指抬起: " + event.getAction());
                        break;
                }
                //onTouch事件默認(rèn)返回false;如果設(shè)置為true,那么這個(gè)觸摸事件會(huì)被onTouch消費(fèi)掉,不會(huì)再繼續(xù)向下傳遞。
                return false;

            }
        });
    }

}

點(diǎn)擊按鈕打印結(jié)果為:


onTouch優(yōu)先于onClick執(zhí)行.png

onTouch基礎(chǔ):

  • onTouch方法里能做的事情比onClick要多一些,比如判斷手指按下、抬起、移動(dòng)等事件。
  • onTouch事件默認(rèn)返回false;如果設(shè)置為true,那么這個(gè)觸摸事件會(huì)被onTouch消費(fèi)掉,不會(huì)再繼續(xù)向下傳遞,即不會(huì)觸發(fā)onClick事件。

(4) 事件分發(fā)—源碼分析

源碼分析.png

  • onTouch和onTouchEvent的區(qū)別:從源碼中可以看出,這兩個(gè)方法都是在View的dispatchTouchEvent中調(diào)用的,onTouch優(yōu)先于onTouchEvent執(zhí)行。如果在onTouch方法中通過返回true將事件消費(fèi)掉,onTouchEvent將不會(huì)再執(zhí)行。
  • 注意:如果控件是非enable的,那么給它注冊(cè)onTouch事件將永遠(yuǎn)得不到執(zhí)行。對(duì)于這一類控件(如 ImageView),如果我們想要監(jiān)聽它的touch事件:第一,在ImageView的onTouch方法里返回true,這樣可以保證ACTION_DOWN之后的其它action都能得到執(zhí)行;第二,在布局文件里面給ImageView增加一個(gè)android:clickable="true"的屬性,這樣ImageView變成可點(diǎn)擊的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到執(zhí)行的;第三:重寫該控件的onTouchEvent方法。
  • touch事件的層級(jí)傳遞:如果給一個(gè)控件注冊(cè)了touch事件,每次點(diǎn)擊它的時(shí)候都會(huì)觸發(fā)一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。這里需要注意,如果在執(zhí)行ACTION_DOWN的時(shí)候返回了false,后面一系列其它的action就不會(huì)再得到執(zhí)行了。簡(jiǎn)單的說,就是當(dāng)dispatchTouchEvent在進(jìn)行事件分發(fā)的時(shí)候,只有前一個(gè)action返回true,才會(huì)觸發(fā)后一個(gè)action。

3. 事件分發(fā)示例

(1) android外接USB掃碼槍

(2) ImageView的觸摸事件

ivPicture.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // 獲取當(dāng)前觸摸事件的Action
                switch (event.getAction()) {
                    //手指按下
                    case MotionEvent.ACTION_DOWN:
                        //獲取繪畫開始的位置
                        startX = event.getX();
                        startY = event.getY();
                        break;
                    //手指滑動(dòng)
                    case MotionEvent.ACTION_MOVE:
                        //獲取手指滑動(dòng)到新的位置
                        float newStartX = event.getX();
                        float newStartY = event.getY();
                        //開始繪畫
                        cacheCanvas.drawLine(startX, startY, newStartX, newStartY, paint);
                        //手指滑動(dòng)過程要不斷初始化開始位置,不然開始位置不變
                        startX = newStartX;
                        startY = newStartY;
                        //重新設(shè)置iv顯示副本
                        ivPicture.setImageBitmap(cacheBitmap);
                        break;
                    //手指抬起
                    case MotionEvent.ACTION_UP:
                        cacheCanvas.drawPath(path,paint);
                        path.reset();
                        break;
                }
                //如果設(shè)置為true,那么這個(gè)觸摸事件由該組件控制
                return true;
            }
        });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容