編輯:關於Android編程
Android中View的事件構成:
在Android中,事件主要包括點按、長按、拖拽、滑動等,點按又包括單擊和雙擊,另外還包括單指操作和多指操作。所有這些都構成了Android中的事件響應。總的來說,所謂的事件即MotionEvent,MotionEvent繼承於InputEvent,用於標記各種動作事件。,最重要的有3個:
(1)MotionEvent.ACTION_DOWN 按下View,是所有事件的開始
(2)MotionEvent.ACTION_MOVE 滑動事件
(3)MotionEvent.ACTION_UP 與down對應,表示抬起
事件操作主要就是發生在View和ViewGroup之間,下面就是View和ViewGroup用來響應事件的方法:
1.View裡,有兩個回調函數 :
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
2.ViewGroup裡,有三個回調函數 :
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onInterceptTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
3.在Activity裡,有兩個回調函數 :
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
首先我們先查看一下log日志,來確定事件是怎麼運行的
MainActivity中:
package com.example.mac.eventmechanismdemo; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.MotionEvent; import android.view.View; public class MainActivity extends AppCompatActivity { private MyTextView myTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myTextView = (MyTextView) this.findViewById(R.id.my_text_view); myTextView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: Log.d("aa", "MyTextView + onTouch + ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d("aa", "MyTextView + onTouch + ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d("aa", "MyTextView + onTouch + ACTION_UP"); break; } return false; } }); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.d("aa", "MainActivity + dispatchTouchEvent + ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d("aa", "MainActivity + dispatchTouchEvent + ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d("aa", "MainActivity + dispatchTouchEvent + ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d("aa", "MainActivity + onTouchEvent + ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d("aa", "MainActivity + onTouchEvent + ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d("aa", "MainActivity + onTouchEvent + ACTION_UP"); break; } return super.onTouchEvent(event); } }
MyTextView:
package com.example.mac.eventmechanismdemo; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.TextView; /** * Created by mac on 16-8-27. */ public class MyTextView extends TextView { public MyTextView(Context context) { super(context); } public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.d("aa", "MyTextView + dispatchTouchEvent + ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d("aa", "MyTextView + dispatchTouchEvent + ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d("aa", "MyTextView + dispatchTouchEvent + ACTION_UP"); break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.d("aa", "MyTextView + onTouchEvent + ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d("aa", "MyTextView + onTouchEvent + ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d("aa", "MyTextView + onTouchEvent + ACTION_UP"); break; } return super.onTouchEvent(event); } }
MainActivity的布局文件:
下面是log日志的信息:
通過上面的數據可以看出,
首先走的是按下事件:
1.事件先是從MainActivity的dispatchTouchEvent的action_down走的,應為返回false,
2.然後走MyTextView的dispatchTouchEvent的action_down,返回false,
3.然後走MainActivity中的myTextView事件監聽,onTouch的action_down,返回false,
4.然後走MyTextView的onTouchEvent的的action_down,返回false,
5.然後走MainActivity的onTouchEvent的的action_down,返回false,
然後走的是移動事件:
6.然後走MainActivity的dispatchTouchEvent的的action_move,返回false,
7.然後走MainActivity的onTouchEvent的的action_move,返回false,
之後還走了幾次6和7,因為我們的手指觸碰到屏幕的時候,可能會輕微的移動,所以會走還幾次呢!
然後走的是抬起事件:
8.然後走MainActivity的dispatchTouchEvent的的action_up,返回false,
9.然後走MainActivity的onTouchEvent的的action_up,返回false,
以上就是對View中的事件怎麼執行做的一個分析。
一。我們先來介紹一下View的攔截事件
通過查看log日志,可以看見首先執行的是activity中的dispatchTouchEvent,然後執行MyTexyView的dispatchTouchEvent,MainActivity的dispatchTouchEvent方法的返回值是super.dispatchTouchEvent(event),因此調用了父類方法,我們進入Activity.java的源碼中看看具體實現。
1.我們來看一下View中的dispatchTouchEvent的源碼:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
一共有三個條件來控制的,第一個條件:是mOnTouchListener變量,這個變量是在setOnTouchListener中來賦值的。也就是當你注冊touch事件的時候賦值的!
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }
第二個條件:判斷當前點擊的控件是否是enable的,按鈕默認都是enable的,因此這個條件恆定為true。
第三個條件:mOnTouchListener.onTouch(this, event),其實也就是去回調控件注冊touch事件時的onTouch方法,若在onTouch方法裡返回true,就會讓這三個條件全部成立,從而整個方法直接返回true;若在onTouch方法裡返回false,就會再去執行onTouchEvent(event)方法。
現在我們可以結合前面的log日志來分析一下,首先在dispatchTouchEvent中最先執行的就是onTouch方法,因此onTouch肯定是要優先於onClick執行的,也是印證了剛剛的打印結果。而如果在onTouch方法裡返回了true,就會讓dispatchTouchEvent方法直接返回true,不會再繼續往下執行。而打印結果也證實了如果onTouch返回true,onClick就不會再執行了。
根據以上源碼的分析,從原理上解釋了我們前面例子的運行結果。而上面的分析還透漏出了一個重要的信息,那就是onClick的調用肯定是在onTouchEvent(event)方法中的!那我們馬上來看下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; }
從上面的代碼中,我們可以看見在MotionEvent.ACTION_UP中調用了performClick()方法,我們再來看一下這個方法的源碼:
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
上面的代碼中,我們可以看到只要mOnClickListener不為空的話,就會執行onClick()方法,我們來看一下mOnClickListener實在哪裡進行初始化的,
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
原來mOnClickListener在setOnClickListener()方法中初始化的,因此每當控件被點擊的時候,都會在performClick()方法裡回調被點擊控件的onClick方法。
但是有一個地方需要注意一下,因為touch事件被注冊後,當用戶點擊的時候就會觸發一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件,若當在執行ACTION_DOWN事件的時候返回false的時候,就不會執行後面的action事件了,所以不難發現只有在前一個action的返回true的時候,後面的action才會被執行。
事件的執行的機制:
一、當有一個TouchEvent發生的時候,先讓activity將這個TouchEvent傳遞給最頂層的View,當TouchEvent最先到達最頂層的View的dispatchTouchEvent,再由dispatchTouchEvent方法進行事件的分發:
若返回true,則將這個TouchEvent交給這個View的onTouchEvent來處理:
若返回false,則將這個TouchEvent交給View的interceptTouchEvent方法來處理,在這個方法中可以決定是否要攔截這個事件,
若interceptTouchEvent方法返回的結果為true,則證明攔截了,並將其交給它的onTouchEvent來處理;
若interceptTouchEvent方法返回的結果為false,則證明不攔截,就將事件傳遞給子View,再由子View的dispatchTouchEvent來進行事件的分發,如果子View的dispatchTouchEvent也是一直也是返回false,則會一直向上傳遞,但都是onTouchEvent來接收的。
當傳遞到最上的onTouchEvent也是返回false,則這個事件就會消失,並且不能接收到下一次的事件了。
二、總結一下,事件的傳遞換個方式說,就是父視圖將事件傳遞給子視圖的過程,攔截機制就是父視圖發起攔截,若攔截不成功,則向下傳遞,由下面的子視圖來決定是否要攔截,到有一個子視圖攔截,若不攔截,則會一直循環,直到最後的一個子視圖,它還是不處理的話,就會想相反的方向來傳遞,向上傳遞,和上面的一樣,若還是沒有視圖的來攔截的事件,就會傳遞到最上的父視圖,父視圖不處理,就會是這個事件消失,並且不能接收到下一次的事件了。
三、事件攔截成功後,緊接著就會對事件進行處理,onInterceptTouchEvent負責對事件進行攔截,攔截成功後,處理的方法教給onTouchEvent方法處理,返回true的那個view進行處理。
讓子View先處理的方法是:從寫父View的onInterceptTouchEvent事件並返回false,
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
本人菜鳥一個,有什麼不對的地方希望大家指出評論,大神勿噴,希望大家一起學習進步!
之前網上看了下自定義消息欄,通知欄,了解到了Notification這個控件,發現UC浏覽器等都是這種類型,今天寫個demo實現下,如圖:其中每個按鈕都有不同的功能,代碼
廢話不多說,咱們第一篇文章就是模仿“知乎”的回答詳情頁的動畫效果,先上個原版的效果圖,咱們就是要做出這個效果 在實現之前,
Github項目地址,歡迎star~!初始化OpenGL ES環境OpenGL ES的使用,一般包括如下幾個步驟:1. EGL Context初始化2. OpenGL E
ToggleButton可以認為是一個開關,每單擊依次一次在“開”和“關”之間進行切換。 ToggleButto