編輯:關於Android編程
PS:以現在的眼光看以前寫的博客感覺寫的很爛,或許或一段時間再看現在的博客會有同樣的感覺。所以每時每刻都去學習,去發現和理解新的東西。
由於之前寫的一篇關於Android事件傳遞順序的博客質量太差,可能是理解不到位的原因,故最近又花了許多時間再次去看Android源碼,看完之後有了新的理解,所以打算重新整理這篇博客。理解Android觸摸事件傳遞機制有助於日後的開發以及自定義一些手勢效果等。注意:這篇博客是基於Android2.0源碼來分析的,不管老版本還是新版本的Android,其內部觸摸事件傳遞機制是不變的。只是說Android2.0的源碼相對比較少,便於讀者理解。
自定義一個MyCustomView
public class MyCustomView extends View {
private String TAG = MyButton;
public MyCustomView(Context context) {
super(context);
}
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, dispatchTouchEvent----->>ACTION_DOWN);
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, dispatchTouchEvent----->>ACTION_MOVE);
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, dispatchTouchEvent----->>ACTION_UP);
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, onTouchEvent----->>ACTION_DOWN);
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, onTouchEvent----->>ACTION_MOVE);
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, onTouchEvent----->>ACTION_UP);
break;
}
return super.onTouchEvent(event);
}
}
重寫dispatchTouchEvent和onTouchEvent方法,添加三種觸摸事件的打印日志。
MainActivity調用如下:
public class MainActivity extends Activity {
private MyCustomView button;
private String TAG = MainActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (MyCustomView) findViewById(R.id.button);
Log.e(TAG, the view is clickable + button.isClickable());
button.setClickable(true);
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, onTouch------->>ACTION_DOWN);
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, onTouch------->>ACTION_MOVE);
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, onTouch------->>ACTION_UP);
break;
}
return false;
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, onClick------->>onClick);
}
});
}
}
點擊自定義MyCustomView,結果打印如下:
07-29 11:03:51.714 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ the view is clickable false
07-29 11:03:54.877 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN
07-29 11:03:54.878 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN
07-29 11:03:54.878 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_DOWN
07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_MOVE
07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_MOVE
07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_MOVE
07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_UP
07-29 11:03:54.931 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_UP
07-29 11:03:54.931 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_UP
07-29 11:03:54.936 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onClick------->>onClick
分析:有上面的打印可以看出
觸摸事件主要有三種且執行順序為:ACTION_DOWN,ACTION_MOVE,ACTION_UP。也就是先執行ACTION_DOWN按下的行為,按下之後手指可能會移動,移動時就出發了ACTION_MOVE行為,當手指抬起時,觸發了ACTION_UP行為,至此觸摸事件順序執行結束。當然觸摸事件不止這三種行為,但是我們這裡主要分析這三種。 觸摸事件過程執行的方法順序為:dispatchTouchEvent,onTouch,onTouchEvent。最後執行了onClick點擊事件。也就是順序應該為:dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick onClick點擊事件是在觸摸事件ACTION_UP執行完之後才執行。為什麼會有以上三種現象和情況出現?現在我們只能從打印日志中看到結果,但是並不知道其內部原因,為了窺探其內部原因,read the fuck code 。基於Android2.0源碼分析View。View的觸摸事件分發是從View#dispatchTouchEvent方法開始執行的。至於為什麼從這裡,以後再說。
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
分析:方法實現很簡單,當滿足if條件就返回true退出方法,條件不滿足時,才去執行onTouchEvent方法且返回該方法的返回值。
1.那麼什麼情況下滿足mOnTouchListener != null條件呢?查看View源碼發現調用如下方法時:
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
當開發者給相應的View設置了View#setOnTouchListener觸摸事件之後,mOnTouchListener != null條件就成立。
2.View默認都是enabled狀態,所以第二個條件成立。
3.當前兩個條件都成立了,執行第三個條件接口方法mOnTouchListener.onTouch(this, event)。根據該方法的返回值來決定if條件是否成立。該方法在開發者設置View#setOnTouchListener觸摸事件實現,當onTouch方法返回false時,dispatchTouchEvent方法就會執行onTouchEvent方法,否則不執行onTouchEvent方法。
總結:
1.onTouch接口方法的返回值決定是否執行onTouchEvent方法。
2.只要onTouch接口方法返回值為true,dispatchTouchEvent方法一定返回true,否則根據onTouchEvent方法返回值決定dispatchTouchEvent返回值。
進入onTouchEvent方法:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
................
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if ((mPrivateFlags & PRESSED) != 0) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
performClick();
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
}
break;
case MotionEvent.ACTION_DOWN:
mPrivateFlags |= PRESSED;
refreshDrawableState();
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick();
}
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press checks
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
} else {
// Inside button
if ((mPrivateFlags & PRESSED) == 0) {
// Need to switch from not pressed to pressed
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
分析:代碼有點長,首先進入該方法判斷if條件是否成立?如果該View是可點擊的或者是可以長按點擊,則if條件成立,進入if判斷,執行ACTION_UP分支。
1.代碼第26行,調用了performClick方法來執行View的點擊事件
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
該方法判斷如果mOnClickListener!=null條件成立,則執行mOnClickListener.onClick(this);接口方法。什麼時候條件成立呢?當給當前View設置了點擊監聽事件之後,條件成立,因此調用接口onClick方法。在View類中有如下方法:
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
先判斷當前View是否可點擊的狀態?如果不可點擊的話,先設置成可點擊,之後對mOnClickListener賦值操作。總結:只要給任何一個View設置了setOnClickListener點擊監聽事件,不管這個View是否是可點擊的狀態,最後都設置為了可點擊的狀態了。
2.只有當前View是可點擊或者長按的狀態,才進入if條件判斷,然後執行相應的手勢操作,最後返回true。也就是說,只要View是可點擊的,onTouchEvent方法返回的就是true,從而dispatchTouchEvent方法返回的也是true。
3.只要是當前View是不可點擊或者長按的狀態,if條件不成立,不執行任何操作,直接返回false。也就是說,View不可點擊的時候,onTouchEvent方法返回的就是false,從而dispatchTouchEvent方法返回的也是false。
4.onClick方法是在ACTION_UP手勢裡面執行的,也就是當手勢抬起時才去執行onClick方法。
到此,Android View觸摸事件傳遞已經分析結束。如果條件都滿足,則整個觸摸事件傳遞過程就是:dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick。現在我們來驗證一下如下幾種情況:
改成如下
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, onTouch------->>ACTION_DOWN);
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, onTouch------->>ACTION_MOVE);
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, onTouch------->>ACTION_UP);
break;
}
return true;
}
});
此時點擊View的打印如下:
07-29 14:42:22.969 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN
07-29 14:42:22.970 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN
07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_MOVE
07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_MOVE
07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_UP
07-29 14:42:22.988 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_UP
當onTouch方法返回true時,就不執行onTouchEvnet方法,因此也就不執行onClick點擊事件。可以理解成此時onTouch把觸摸事件已經消費掉了,也就不會繼續往下傳遞觸摸事件。所以如果你不想自己的View執行onTouchEvent方法,你可以設置onTouch事件,且返回值為true即可。
在文章開頭的 MainActivity裡面,我是添加了一行代碼 button.setClickable(true); 目的是讓當前View可點擊,但是默認情況下除了Button,TextView少數控件外,其他大部分View控件默認都是不可點擊的狀態,除非你設置了View#setClickable(true)或者View#setOnClickListener。現在我將MainActivity中的button.setClickable(true);這一行代碼去掉且不設置setOnClickListener事件,
public class MainActivity extends Activity {
private MyCustomView button;
private String TAG = MainActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (MyCustomView) findViewById(R.id.button);
Log.e(TAG, the view is clickable + button.isClickable());
// button.setClickable(true);
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, onTouch------->>ACTION_DOWN);
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, onTouch------->>ACTION_MOVE);
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, onTouch------->>ACTION_UP);
break;
}
return false;
}
});
// button.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// Log.e(TAG, onClick------->>onClick);
// }
// });
}
}
Log打印日志如下:
07-29 14:57:03.656 4896-4896/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN
07-29 14:57:03.658 4896-4896/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN
07-29 14:57:03.658 4896-4896/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_DOWN
不知道你發現木有?此處打印看出只執行了ACTION_DOWN手指操作,其他的手勢操作呢?沒有執行,為什麼呢?
情況是這樣的:當onTouch方法返回false,則dispatchTouchEvent方法就會執行onTouchEvent方法,但是由於View不可點擊,所以onTouchEvent是不執行if條件體的,也就是onTouchEvent方法返回false,從而導致dispatchTouchEvent方法返回false,由於dispatchTouchEvent方法返回false,導致後面的手勢操作ACTION_MOVE,ACTION_UP得不到執行。
總結:如果我們將手勢操作分為三個過程的話:ACTION_DOWN,ACTION_MOVE,ACTION_UP。只有當dispatchTouchEvent方法返回true時,系統才會執行對應過程後面的手勢操作。
至此Android View 觸摸事件傳遞機制已經分析結束,現在用一個流程圖來體現:
觸摸事件傳遞順序:dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick。 onTouch和onTouchEvent區別:兩個方法先後在dispatchTouchEvent中調用,只有給View設置了觸摸事件View#setOnTouchListener才會執行onTouch方法;onTouch方法的返回值決定是否執行onTouchEvent方法。 手勢操作執行的順序為ACTION_DOWN,ACTION_MOVE,ACTION_UP,只有dispatchTouchEvent方法返回true值時後面的手勢才會被執行。 onClick方法的調用是在onTouchEvent的ACTION_UP手勢裡面執行的,也就是當手勢抬起時,手勢操作結束才會觸發onClick方法的調用。
環境主機:WIN 7開發環境:Android Studio2.2.2步驟安裝NDK打開Tools->Android->SDK Manager->SDK
怎麼在安卓模擬器裡面安裝app?它不像別的軟件那樣一拉一拖就進去的,我開始也是這樣的,老是安裝不了,後來讓我嘗試了,我才知道。下面是我跟大家一起分享一下怎麼
本文github地址:https://github.com/YoungBear/MyBlog/blob/master/VolleyLearn.mdVolley是Andro
什麼是廣播在Android中,Broadcast是一種廣泛運用的在應用程序之間傳輸信息的機制。我們拿廣播電台來做個比方。我們平常使用收音機收音是這樣的:許許多多不同的廣播