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

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),流程圖如下:


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ī)制。
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)該分配給誰呢?

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

相關(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基礎(chǔ):
- onTouch方法里能做的事情比onClick要多一些,比如判斷手指按下、抬起、移動(dòng)等事件。
- onTouch事件默認(rèn)返回false;如果設(shè)置為true,那么這個(gè)觸摸事件會(huì)被onTouch消費(fèi)掉,不會(huì)再繼續(xù)向下傳遞,即不會(huì)觸發(fā)onClick事件。
(4) 事件分發(fā)—源碼分析

- 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;
}
});