編輯:關於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;
}
本人菜鳥一個,有什麼不對的地方希望大家指出評論,大神勿噴,希望大家一起學習進步!
Android項目仿UC浏覽器和360手機衛士消息常駐欄(通知欄)
之前網上看了下自定義消息欄,通知欄,了解到了Notification這個控件,發現UC浏覽器等都是這種類型,今天寫個demo實現下,如圖:其中每個按鈕都有不同的功能,代碼
Android模仿知乎的回答詳情頁的動畫效果
廢話不多說,咱們第一篇文章就是模仿“知乎”的回答詳情頁的動畫效果,先上個原版的效果圖,咱們就是要做出這個效果 在實現之前,
Android平台的全景視頻播放器——1.2 用OpenGL ES 2.0畫一個三角形
Github項目地址,歡迎star~!初始化OpenGL ES環境OpenGL ES的使用,一般包括如下幾個步驟:1. EGL Context初始化2. OpenGL E
android開發之ToggleButton控件
ToggleButton可以認為是一個開關,每單擊依次一次在“開”和“關”之間進行切換。 ToggleButto