編輯:關於Android編程
介紹了職責鏈模式,作為理解View事件分發機制的基礎。
套用職責鏈模式的結構分析,當我們的手指在屏幕上點擊或者滑動,就是一個事件,每個顯示在屏幕上的View或者ViewGroup就是職責對象,它們通過Android中視圖層級組織關系,層層傳遞事件,直到有職責對象處理消耗事件,或者沒有職責對象處理導致事件消失。
要理解有關View的事件分發,先要看幾個關鍵概念
當手指接觸到屏幕以後,所產生的一系列的事件中,都是由以下三種事件類型組成。
1. ACTION_DOWN: 手指按下屏幕
2. ACTION_MOVE: 手指在屏幕上移動
3. ACTION_UP: 手指從屏幕上抬起
例如一個簡單的屏幕觸摸動作觸發了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->…->ACTION_MOVE->ACTION_UP
對於Android中的這個事件分發機制,其中的這個事件指的就是MotionEvent。而View的對事件的分發也是對MotionEvent的分發操作。可以通過getRawX和getRawY來獲取事件相對於屏幕左上角的橫縱坐標。通過getX()和getY()來獲取事件相對於當前View左上角的橫縱坐標。
public boolean dispatchTouchEvent(MotionEvent ev)
這是一個對事件分發的方法。如果一個事件傳遞給了當前的View,那麼當前View一定會調用該方法。對於dispatchTouchEvent的返回類型是boolean類型的,返回結果表示是否消耗了這個事件,如果返回的是true,就表明了這個View已經被消耗,不會再繼續向下傳遞。
public boolean onInterceptTouchEvent(MotionEvent ev)
該方法存在於ViewGroup類中,對於View類並無此方法。表示是否攔截某個事件,ViewGroup如果成功攔截某個事件,那麼這個事件就不在向下進行傳遞。對於同一個事件序列當中,當前View若是成功攔截該事件,那麼對於後面的一系列事件不會再次調用該方法。返回的結果表示是否攔截當前事件,默認返回false。由於一個View它已經處於最底層,它不會存在子控件,所以無該方法。
public boolean onTouchEvent(MotionEvent event)
這個方法被dispatchTouchEvent調用,用來處理事件,對於返回的結果用來表示是否消耗掉當前事件。如果不消耗當前事件的話,那麼對於在同一個事件序列當中,當前View就不會再次接收到事件。
上文部分內容來自《Android開發藝術探索》
為了驗證和理解實際的運行狀態,重寫View和ViewGroup這些關鍵方法,打印方法調用。
繼承View重寫方法,加入結果打印。
注:View作為子控件,不存在內部子控件,所以傳入事件就視圖處理,而不存在攔截子控件的事件,所以沒有onInterceptTouchEvent
方法
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result=super.dispatchTouchEvent(ev);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(ev.getAction()));
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result=super.onTouchEvent(event);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(event.getAction()));
return result;
}
}
繼承ViewGroup重寫方法,加入結果打印。
注:ViewGroup沒有實現onLayout布置控件位置,所以繼承LinearLayout,對分發不影響
public class MyViewGroup extends LinearLayout {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result=super.dispatchTouchEvent(ev);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(ev.getAction()));
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result=super.onTouchEvent(event);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(event.getAction()));
return result;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result=super.onInterceptTouchEvent(ev);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(ev.getAction()));
return result;
}
}
最後把這兩個控件加入布局文件就可以了。
首先運行上面的代碼後,可以看到兩個色塊。用手機點擊裡面的MyViewGroup的子控件MyView。
相信我,不論怎麼滑動或者點擊都是一樣的結果,下面會分析這樣的情況發生原因。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="分析">分析:
手指點擊在MyViewGroup中方法onInterceptTouchEvent開始調用,判斷是否攔截這個點擊事件。
ViewGroup2717行代碼,源碼中ViewGroup默認是不攔截事件的:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
因為ViewGroup不攔截點擊事件,事件開始分發,子控件View有機會得到事件,調用內部的兩個方法處理事件,因為我默認沒有做任何處理,View也不會處理事件,返回false。 最後因為Down事件沒有控件響應。如果不消耗當前事件的話,那麼對於在同一個事件序列當中,當前View就不會再次接收到事件。 所以手指在觸摸到屏幕之後滑動,View也就接受不到Move事件。所以手指怎麼滑動都沒有其他的日志結果打印。除非抬起,再按下生成新的事件,又看到同樣的打印結果。
修改代碼,給View添加點擊事件,也就是使用setOnClickListener
簡單的處理。
然後手指點擊迅速抬起,要不然打印結果太多了。
分析:產生點擊事件,子控件onTouchEvent可以處理事件,得到Down事件,同時影響ViewGroup父控件dispatchTouchEvent返回ture可以向下分發,後繼的UP事件也相繼的傳進來。
在原基礎上,再次修改代碼,給ViewGroup父控件的onInterceptTouchEvent方法返回true,表示攔截事件。然後給ViewGroup添加點擊監聽回調setOnClickListener
。
分析:有了上面的基礎,這裡就什麼好說的了,因為父控件攔截了事件,同時能夠響應事件,所有的事件都發送到ViewGroup上。
通過上面的3個實驗的結果,可以大概對ViewGourp的事件分發有個基本的認識。
所以通過抽象源碼提取關鍵實現,可以有下面的大概處理邏輯。
在ViewGroup中有如下邏輯:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume =false;//默認不處理
if (onInterceptTouchEvent(ev)){//首先判斷是否攔截
consume=onTouchEvent(ev);//看是否能夠處理
}else {
//如果不攔截,遍歷子控件,這裡省略掉
//調用子控件的分發方法,下發事件。
consume=getChildView.dispatchTouchEvent(ev);
}
return consume;
}
我在上篇博客介紹分析了職責鏈模式,用該模式的思想來分析。
可以畫出這樣的UML圖:
當然這是簡略的畫法,實際會復雜得多,而且View和VIewGorup采用設計模式組合模式的思想構造。
Cilent:表示發出請求的對象,在這裡應該對應的是Activity。 ViewHandler:內部以數組的方式持有後繼者,使用的時候采用從最後一位遍歷。dispatchTouchEvent方法表示統一的事件請求方法View和ViewGourp:都是實際的實現職責類,內部通過調用其他的方法判斷是否能夠處理請求事件。
鏈的構造:
很明顯鏈的構造是在ViewHandler中組裝的,內部鏈的實現方式。onTouchEvent和onInterceptTouchEvent都會影響鏈的構造。動態的生成職責鏈。
最後提出一些結論,給大家一些對事件分發的提示和總結。說不定面試的時候就用上了呢。
提示:ViewGroup在繼承關系上繼承View,所以下文可以用View指代ViewGroup。具體的原因自行Google。
某個View一旦決定攔截事件之後,它的onInterceptTouchEvent不會再調用,所以的後繼事件都直接給它處理而不再詢問是否攔截。這在上的打印結果可以得到驗證。 某個View一旦開始處理事件,如果它不消耗Down事件(onTouchEvent返回了false)那麼同一事件序列中的其他事件都不會再交給它處理,並且事件將重新交由父View處理,即父View的onTouchEvent會被調用。也就是一旦事件交給了View而它沒有消耗掉事件,之後的事件序列都不會再分發給它,父控件會開始嘗試處理事件。這點在第一個張實驗結果圖可以得到驗證。 如果View不消耗除Down以外的事件,那麼這個點擊事件會消失,並且父View的onTouchEvent不會調用,並且當前View可以持續收到後續事件,最終這些消失的點擊事件會傳遞給Activity處理。 ViewGroup默認不攔截任何事件,具體請看上文。 真正的View沒有onInterceptTouchEvent方法,一旦有事件發給它,它的onTouchEvent就會調用。 onClick會發生的前提是當前View可點擊,並且它收到了Down事件和Up事件。 事件傳遞過程是由外向內傳遞的。即事件總是先傳給父View,然後由父View決定分發。通過requestDisallowInterceptTouchEvent方法可以在子View中干預父View的事件分發過程,但是Down事件除外。這個就是我們在處理一些嵌套滑動時候遇到的主要問題。子控件攔截了事件,View對這個滑動事件不想要處理的時候,只能拋棄這個事件,而不會把這些傳給父view去處理。這就是滑動的嵌套的父子控件同方向滑動不流暢的原因。好消息時NestedScrollView的出現很好的解決了這個問題。
導航抽屜(navigationdrawer)是一個從屏幕左邊滑入的面板,用於顯示應用的主要導航項目。用戶可以通過在屏幕左邊緣滑入或者觸摸操作欄的應用圖標打開導航抽屜。導航
概述之前在討論組裡聽到許多討論okhttp的話題,可見okhttp是一個相對成熟的解決方案,看到android4.4後網絡訪問的源碼中HttpURLConnection已
前一段時間更新了Android Studio,目前最新的穩定版是1.4。更新之後沒看到什麼大的變化。今天去逛官方的更新日志,發現1.4版本著實增加了不少使用的
package com.example.administrator.newstop.entity;/** * Created by Administrator