編輯:關於Android編程
package cc.cv; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.widget.Button; import android.widget.ImageView; import android.app.Activity; /** * Demo描述: * View的事件分發 * * 在View的事件分發過程中主要涉及到dispatchTouchEvent()和onTouch()以及onTouchEvent() * * dispatchTouchEvent()返回true或者false表示是否繼續事件分發 * onTouch()返回 true或者false表示是事件是否被消耗 * onTouchEvent()中主要處理點擊Click事件 * * 事件的分發從dispatchTouchEvent()開始. * 方法dispatchTouchEvent()返回值為true時表示繼續事件分發;返回值為false時表示終止事件分發. * 源碼如下: * * public boolean dispatchTouchEvent(MotionEvent event) { * if (mOnTouchListener!= null&&(mViewFlags & ENABLED_MASK)==ENABLED&&mOnTouchListener.onTouch(this,event)){ * return true; * } * return onTouchEvent(event); * } * * 該方法的返回值有兩種情況: * * 1 滿足if條件時,返回true.注意該if條件的三個判斷. * 1.1 mOnTouchListener不等於null * 1.2 當前控件是enable的 * 1.3 調用mOnTouchListener.onTouch(this,event)返回的結果 * 前兩個條件沒啥可多說的,主要看看第三個條件: * 在 onTouch(View v, MotionEvent event)中會處理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP. * 該onTouch()方法返回true表示事件已經消耗,返回false表示事件未消耗. * 比如在處理ACTION_DOWN時返回true才會繼續分發ACTION_MOVE事件 * 比如在處理ACTION_MOVE時返回true才會繼續分發ACTION_UP事件 * 比如在處理ACTION_DOWN時返回false,那麼後續的ACTION_MOVE,ACTION_UP就不會再繼續分發. * 我們在代碼中也就無法捕捉到ACTION_MOVE,ACTION_UP這兩個Action了. * * 2 假如該if條件不滿足,那麼就繼續執行,返回 onTouchEvent(event)的執行結果. * * * 從該dispatchTouchEvent()的源碼也可以看出 * onTouch(this,event)和 onTouchEvent(event)的區別和關系: * 1 先調用onTouch()後調用onTouchEvent() * 2 在onTouch()方法中處理了Touch事件,即處理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP事件 * 返回false時表示事件(每個單獨的ACTION_DOWN,ACTION_MOVE,ACTION_UP都叫一個事件,並不是說這三者聯系在一起才是一個事件) * 未被消耗才會調用onTouchEvent(event). * 3 在onTouchEvent(event)中的ACTION_UP事件裡會調用performClick()處理OnClick點擊事件!!!! * 4 所以可知: * 4.1 Touch事件先於Click事件發生和處理,且注意onTouch()方法默認返回為false. * 4.2 只有在onTouch()返回false時(即事件未被消耗)才會調用onTouchEvent() * 4.3 在onTouchEvent()中的ACTION_UP事件會調用performClick()處理OnClick點擊事件. * 5 參見下面的onTouchEvent()源碼,請注意第三個if判斷,這個if判斷很重要!!!!!!! * 5.1 在該if條件中判斷該控件是否是可點擊的(CLICKABLE)或者是否是可以長按的(LONG_CLICKABLE). * 5.2 如果滿足CLICKABLE和LONG_CLICKABLE中任一條件則始終會返回true給onTouchEvent()方法 * 5.3 如果CLICKABLE和LONG_CLICKABLE這兩個條件都不滿足則返回false給onTouchEvent()方法 * * 請注意: * Button默認情況下就是CLICKABLE和LONG_CLICKABLE的,但是ImageView在 * 默認情況下CLICKABLE和LONG_CLICKABL均為不可用的. * * 所以在用Button和ImageView分別實驗OnTouchListener和OnClickListener是有區別的. * 再次提醒注意:onTouch()方法默認返回為false. * 1 Button做實驗分析dispatchTouchEvent(). * mOnTouchListener.onTouch()返回false(默認值),所以dispatchTouchEvent() * 如上源碼中的if不滿足,於是繼續調用onTouchEvent(event)時由於Button滿足CLICKABLE和LONG_CLICKABLE * 所以最後返回給dispatchTouchEvent()的是true,即繼續事件的分發. * 所以可以捕獲到一系列的:ACTION_DOWN,ACTION_MOVE,ACTION_UP. * 這裡就解釋了為什麼在Button中雖然onTouch()返回false(默認值)但是事件分發還在繼續!!!!!!!!!!!!! * * 2 用ImageView做實驗分析dispatchTouchEvent(). * mOnTouchListener.onTouch()返回false(默認值),所以dispatchTouchEvent() * 如上源碼中的if不滿足,在調用onTouchEvent(event)時由於ImageView不滿足CLICKABLE和LONG_CLICKABLE * 中任何一個所以最後返回給dispatchTouchEvent()的是false,即終止事件的分發.所以對於ImageView只有 * ACTION_DOWN沒有ACTION_MOVE和ACTION_UP * 這裡就解釋了為什麼在ImageView中在onTouch()返回裡false(默認值)就終止了事件分發!!!!!!!!!!!!! * * 如何才可以使ImageView像Button那樣"正規的"事件分發,有如下兩個方法: * 1 為ImageView設置setOnTouchListener,且在其onTouch()方法中返回true而不是默認的false. * 2 為ImageView設置android:clickable="true"或者ImageView設置OnClickListener. * 就是說讓ImageView變得可點擊. * 3 詳情可見以下代碼中的例子,關於這一點在下面的例子中有體現. * * * * 參考資料: * http://blog.csdn.net/guolin_blog/article/details/9097463 * Thank you very much * * 代碼隨筆: * 以前也看過事件分發,也自己總結了;可理解得不夠. * 最近打算結合以前的東西和郭大嬸的博客重新整理事件分發. * 由於理解上的差異郭大嬸的這邊博客,我看得比較吃力;後來也和他溝通了一下. * 所以這裡是自己的理解,是正確的;只是在表述上和他的博客有所不同. */ public class MainActivity extends Activity { private Button mButton; private ImageView mImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initButton(); initImageView(); } private void initButton(){ mButton=(Button) findViewById(R.id.button); mButton.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("Button ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: System.out.println("Button ACTION_MOVE"); break; case MotionEvent.ACTION_UP: System.out.println("Button ACTION_UP"); break; default: break; } return false; } }); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { System.out.println("Button Clicked"); } }); } private void initImageView(){ mImageView=(ImageView) findViewById(R.id.imageView); mImageView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("ImageView ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: System.out.println("ImageView ACTION_MOVE"); break; case MotionEvent.ACTION_UP: System.out.println("ImageView ACTION_UP"); break; default: break; } return false; } }); //因為ImageView默認是不可點擊的,所以如果屏蔽掉以下的代碼,則只有 //ImageView的ACTION_DOWN沒有ACTION_MOVE和ACTION_UP mImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { System.out.println("ImageView Clicked"); } }); } } /* * 此處為onTouchEvent源碼: public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & PRESSED) != 0 || prepressed) { // 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 removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); 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 = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } return true; } return false; } */
ButtonSubClass如下:
package cc.cv; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewGroup; import android.widget.Button; public class ButtonSubClass extends Button { public ButtonSubClass(Context context) { super(context); // TODO Auto-generated constructor stub } public ButtonSubClass(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public ButtonSubClass(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { // TODO Auto-generated method stub return super.dispatchTouchEvent(event); } }
PS:
以前也整理過事件分發,不過當時太膚淺。
最近重新整理了該部分;月底發出來,算是對今年的一個告別。
其實這部分的核心就在於ViewGroup的dispatchTouchEvent()源碼部分。
這次整理,對於該部分還是沒有完全看懂;期待以後有機會繼續。
學習是一個過程,這就是體現。
今天在網上看到一篇文章寫關於Android實現3D旋轉(http://www.ibm.com/developerworks/cn/opensource/os-cn-and
寫在前面從裝飾者模式到Context類族當觀察者模式和回調機制遇上Android源碼Android源碼中的靜態工廠方法Android中的工廠方法模式前面跟大家分享了裝飾者
5、Activity用SharedPreferences保存數據,大小有木有限制?個人理解:SharedPreferences是哪種存儲數據的方式竟然記不清楚了,個人印象
1.背景 Android Studio 自從 2013年谷歌I/O大會推出到現在將近2年了,也更新到了1.0的穩定版本,此時不入手更待何時。 Android Stud