編輯:關於Android編程
Touch事件,四種狀態:
ACTION_DOWN ——> 表示按下了屏幕,一個事件必然從ACTION_DOWN開始
ACTION_MOVE ——> 表示移動手勢
ACTION_UP ——> 表示離開屏幕
ACTION_CANCEL ——> 表示取消手勢,一般由程序產生,不會由用戶產生
一個ACTION_DOWN, n個ACTION_MOVE,1個ACTION_UP,就構成了Android中眾多的事件。
Android中的事件onClick, onScroll, onFling等等,都是由許多個Touch組成的。
一個原則,所有的touch事件都是從父容器開始向下傳遞的,呈U字形。
Android中諸如ImageView、textView、Button等控件都沒有重寫View的dispatchTouchEvent方法,所以View的事件處理機制對這些控件都有效。
View.java(基於android2.3.3):
public boolean dispatchTouchEvent(MotionEvent event) {//返回true,表示該View內部消化掉了所有事件。返回false,表示View內部只處理了ACTION_DOWN事件,事件繼續傳遞,向上級View(ViewGroup)傳遞。
...
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//此處的onTouch方式就是回調的我們注冊OnTouchListener時重寫的onTouch()方法
return true;
}
if (onTouchEvent(event)) {// onTouchEvent參考下面源碼
return true;
}
...
}
public boolean onTouchEvent(MotionEvent event) {
...
// 當前onTouch的組件必須是可點擊的比如Button,ImageButton等等,此處CLICKABLE為true,才會進入if方法,最後返回true。
// 如果是ImageView、TexitView這些默認為不可點擊的View,此處CLICKABLE為false,最後返回false。當然會有特殊情況,如果給這些View設置了onClick監聽器,此處CLICKABLE也將為true,參考下面源碼
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClick();// 實際就是回調了我們注冊的OnClickListener中重新的onClick()方法,源碼下面源碼
}
...
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public boolean performClick() {
...
if (li != null && li.mOnClickListener != null) {
...
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
總結:
只有我們注冊OnTouchListener時重寫的onTouch()方法中返回false ——> 執行onTouchEvent方法 ——> 導致onClick()回調方法執行
onTouch()方法返回true ——> onTouchEvent方法不執行 ——> 導致onClick()回調方法不會執行
Android中諸如LinearLayout等的五大布局控件,都是繼承自ViewGroup,而ViewGroup本身是繼承自View,所以ViewGroup的事件處理機制對這些控件都有效。
ViewGroup.java(基於android2.3.3):
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
//onInterceptTouchEvent返回false,說明向下傳遞
//onInterceptTouchEvent返回true,說明攔截
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
...
// 偽代碼如下:
//1,找到當前控件子控件
//2,判斷當前touch的點的坐標(x,y)在哪個子控件的矩形區域內
//3,判斷當前子控件是viewgroup的子類對象,還是view的子類對象
//3.1 如果是viewgroup的子類: 調用其dispatchTouchEvent方法,上述操作再來一遍
//3.2 view 嘗試讓當前view去處理這個事件(
true,dispatchTouchEvent方法結束,並且返回true
false,dispatchTouchEvent繼續向下執行)
...
}
}
...
target = mMotionTarget
//target一定是null
if (target == null) {
...
//調用當前viewgroup的父View的處理事件的方法
return super.dispatchTouchEvent(ev);
}
...
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;// 默認返回false
}
總結:
1、dispatchTouchEvent作用:決定事件是否由onInterceptTouchEvent來攔截處理。
返回super.dispatchTouchEvent時,由onInterceptTouchEvent來決定事件的流向
返回false時,會繼續分發事件,自己內部只處理了ACTION_DOWN
返回true時,不會繼續分發事件,自己內部處理了所有事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP)
2、onInterceptTouchEvent作用:攔截事件,用來決定事件是否傳向子View
返回true時,攔截後交給自己的onTouchEvent處理
返回false時,攔截後交給子View來處理
3、onTouchEvent作用:事件最終到達這個方法
返回true時,內部處理所有的事件,換句話說,後續事件將繼續傳遞給該view的onTouchEvent()處理
返回false時,事件會向上傳遞,由onToucEvent來接受,如果最上面View中的onTouchEvent也返回false的話,那麼事件就會消失
以下摘自:http://www.longdw.com/android-onintercepttouchevent-ontouchevent/
源碼:
public class MainActivity extends Activity {
Group1 group1;
Group2 group2;
MyTextView myTv;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//--group1
//----|
//-------group2
//---------|
//------------myTv
group1 = new Group1(this);
group2 = new Group2(this);
myTv = new MyTextView(this);
group2.addView(myTv, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
group1.addView(group2, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
setContentView(group1);
}
}
public class Group1 extends FrameLayout {
public Group1(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, Group1 onInterceptTouchEvent觸發事件:+Constant.getActionTAG(ev.getAction()));
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, Group1 onTouchEvent觸發事件:+Constant.getActionTAG(event.getAction()));
return false;
}
}
public class Group2 extends FrameLayout {
public Group2(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, Group2 onInterceptTouchEvent觸發事件:+Constant.getActionTAG(ev.getAction()));
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, Group2 onTouchEvent觸發事件:+Constant.getActionTAG(event.getAction()));
return false;
}
}
public class MyTextView extends TextView {
public MyTextView(Context context) {
super(context);
this.setGravity(Gravity.CENTER);
this.setText(點擊我!);
// TODO Auto-generated constructor stub
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, MyTextView onTouchEvent觸發事件:+Constant.getActionTAG(event.getAction()));
return false;
}
}
public class Constant {
public static final String LOGCAT = logcat;
public static String getActionTAG(int action) {
switch (action) {
case 0:
return ACTION_DOWN;
case 1:
return ACTION_UP;
case 2:
return ACTION_MOVE;
default:
return NULL;
}
}
}
分別重寫Group1和Group2的onInterceptTouchEvent和onTouchEvent方法,重寫MyTextView的onTouchEvent方法,最終得到的控件層次結構如下:

1.在默認返回值情況下logcat輸出如下:

測試後可知默認情況下和所有方法返回值為false的結果一致,down事件的捕獲順序onInterceptTouchEvent先於onTouchEvent,由於onTouchEvent返回值為false,down事件沒被消化,後續的move和up事件沒有出現,同時逆序返回到父控件的onTouchEvent方法來捕獲,如下圖所示:

2.所有onTouchEvent返回值為true情況下logcat輸出如下:

輸出結果可以看出子控件MyTextView消化了down事件,後續的move和up事件正常捕獲,由於down事件被消化,上層的onTouchEvent方法不執行,如下圖所示:(三箭頭分別指down、move、up事件)

既然如此,如果MyTextView中onTouchEvent方法返回為false,而group1和group2的onTouchEvent方法返回true的結果自然也就如下圖的順序了:

測試輸出結果證明了這一猜測順序:

注意:可能有人對這種情況比較疑惑,ACTION_DOWN還好理解,但是ACTION_MOVE為什麼沒有經歷myTv,而且ACTION_MOVE只經歷了group1的onInterceptTouchEvent和group2的onTouchEvent而沒有經歷group2的onInterceptTouchEvent?我開始也費解,後來想想也是,大家對比第1條,由於onTouchEvent返回了false而沒有消耗down事件導致後續的move和up都沒有出現,這裡也是一樣由於myTv中onTouchEvent返回了false也就是說沒有消耗down事件,那麼後面的move和up也都不會出在這個view裡面,但是group2截獲到了down事件,但後來的move為什麼group2中的onInterceptTouchEvent沒有執行到呢,原因大家不要忘記了onInterceptTouchEvent的初衷是什麼,返回false是讓它的子view或viewgroup類處理,而group2的子控件顯然是myTv而myTv的onTouchEvent返回了false也就是接收不到後續的move和up事件,也就沒必要經過onInterceptTouchEvent來繼續分發了(因為分發了也還是接收不到),經過group2的onTouchEvent因為它返回的是true,截獲了事件並且消耗了事件。
3.當某個GroupView中的onInterceptTouchEvent方法返回值為true情況下logcat輸出如下(如group2):

如果在該方法返回值中返回true,那麼子控件將獲取不到任何點擊事件,轉而向自身的onTouchEvent方法轉發,如下圖所示:

如果onTouchEvent方法返回值都為true,那麼根據規律結果就如下圖順序觸發:

最後logcat的結果證實了這一猜測:

??
Android學習之往系統應用中添加framework層的jar包
Framework中的app為什麼在編譯的時候需要到源碼中編譯: 因為缺少必要的包(源碼)----在連接的時候是以class文件來連接編譯的 以Systemeui為例
Android組件DrawerLayout仿網易新聞v4.4側滑菜單
概述 今天這篇博客將記錄一些關於DrawerLayout的基本用法,我想關於DrawerLayou
Android開發本地及網絡Mp3音樂播放器(十二)創建NetMusicListAdapter、SearchResult顯示網絡音樂列表
實現功能:實現NetMusicListAdapter(網絡音樂列表適配器)實現SearchResult(搜索音樂對象)使用Jsoup組件請求網絡,並解析音樂數據,並,音樂
Android的下拉刷新/上拉加載控件
事實上之所以會有之前的那篇博文的出現,是起因於前段時間自己在寫一個練手的App時很快就遇到這種需求。其實我們可以發現類似這樣下拉刷新、上拉加載的功能正在變得越來越普遍,可