編輯:關於android開發
最近在開發中遇到view滑動沖突的問題,由於一開始就知道這個問題與view事件分發有關,之後在網上看了幾篇關於事件分發的資料後,開發中遇到的問題很快便得到解決。
在這裡總結一下我對view事件分發的理解。
首先,看下事件分發流程圖:
在對view的事件分發機制進行分析前,我們可以通過一個demo看看Button的事件處理的流程。
在布局文件中添加一個button控件,然後在代碼中實現Button的setOnClickListener和setOnTouchListener方法,注冊Click監聽和Touch監聽。
/**
* button事件
*/
private void showButtonTouch() {
mBtn = (Button) findViewById(R.id.btn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "Button onClick");
}
});
mBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
Log.e(TAG, "Button onTouch " + "ACTION_UP");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "Button onTouch " + "ACTION_MOVE");
break;
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "Button onTouch " + "ACTION_DOWN");
break;
}
return false;
}
});
}
demo運行起來之後點擊button,看下log日志(在點擊時移動一下保證ACTION_MOVE事件能被觸發):
在這裡通過日志可以看出事件處理的流程。
在View的源碼中,我們可以看到dispatchTouchEvent方法,這個方法可以理解為是View事件分發的入口。(代碼基於4.0.3即API 15,建議大家在理解源碼時不要看太高的版本,高版本源碼會有過多的優化,會妨礙我們對於代碼主要功能邏輯的理解)
以下是View中dispatchTouchEvent方法代碼:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
//這個是View事件分發的主要邏輯判斷
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
在這整個方法中,事件分發的關鍵就在這個判斷中 (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) 。
如果這個判斷為true,那麼該方法返回true;否則,執行onTouchEvent()方法並根據onTouchEvent方法的返回值返回具體結果。
li != null:代碼中ListenerInfo對象li,是View內定義的一個靜態類,這個類內部定義了View中所有Listener相關的類,一般情況下這個類不為空,在這裡不做過多解釋(2.3版本源碼中沒有該對象)。
li.mOnTouchListener != null:是否為空,mOnTouchListener 對象就是我們通過mBtn.setOnTouchListener(new View.OnTouchListener() {})設置的,所以這裡不為空。
(mViewFlags & ENABLED_MASK) == ENABLED:判斷控件是否是enable,很顯然為true。
li.mOnTouchListener.onTouch(this, event):最後就是判斷onTouch方法中返回值是否為true,onTouch方法就是我們在Button控件注冊Touch監聽時@Override的onTouch方法。
在前面的demo中,因為mOnTouchListener.onTouch(this, event)方法的返回值為false,我們在log中看到button先執行touch事件在執行click事件。在這裡我們將onTouch方法的返回值改為true,可以看到以下log:
在log中可以看到click方法沒有被執行,這又是為什麼呢?
其實在這裡大家通過閱讀dispatchTouchEvent方法代碼可以想到,因為onTouch返回值為true,所以這個判斷條件成立即dispatchTouchEvent方法返回true。if (onTouchEvent(event))->onTouchEvent方法沒有被執行,會不會是由於這個原因導致click方法沒有被執行呢? 很顯然我們要看看onTouchEvent方法的源碼。
以下是onTouchEvent方法的源碼:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
// 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;
}
}
//這是onTouchEvent代碼主要邏輯功能
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 (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
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) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
mPrivateFlags |= PRESSED;
refreshDrawableState();
checkForLongClick(0);
}
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
if (!pointInView(x, y, mTouchSlop)) {
// 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;
}
對於onTouchEvent方法,主要的功能邏輯我們只需要從第23行代碼開始閱讀便可。
首先判斷View是否是可點擊的,因為Button默認是可點擊的,所以這個條件成立,我們可以進入到switch分支判斷中。在這裡給大家留下一個疑問,如果這個條件不成立即就是控件是不可點擊的,會出現什麼樣的情況呢?
MotionEvent.ACTION_UP:在這個分支判斷中我們可以看到在58行有個performClick()方法,我們進入到這個方法體中可以看到:
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
在該方法中可以看到li.mOnClickListener.onClick(this)被調用了,到此處我們可以確認Button的click事件就是在onTouchEvent方法中調用的。
在onTouchEvent中曾留下一個疑問,如果判斷控件是否可點擊為false,會出現什麼樣的情況呢?
為了驗證這個問題我們可以通過TextView和Button進行比較一下,因為TextView默認不可點擊,Button默認可點擊的。
首先在xml中添加TextView和Button,在代碼中分別為他們注冊setOnTouchListener監聽,代碼如下:
/**
* button事件
*/
private void showButtonTouch() {
mBtn = (Button) findViewById(R.id.btn);
mBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
Log.e(TAG, "Button onTouch " + "ACTION_UP");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "Button onTouch " + "ACTION_MOVE");
break;
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "Button onTouch " + "ACTION_DOWN");
break;
}
return false;
}
});
}
/**
* TextView事件
*/
private void showTextTouch() {
mTv = (TextView) findViewById(R.id.tv);
mTv.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
Log.e(TAG, "TextView onTouch " + "ACTION_UP");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "TextView onTouch " + "ACTION_MOVE");
break;
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "TextView onTouch " + "ACTION_DOWN");
break;
}
return false;
}
});
}
因為我們需要進入到onTouchEvent方法中,所以必須讓setOnTouchListener.onTouch()方法的返回值為false,在這裡我們看看demo運行起來後分別點擊Button和TextView打印的log:
系統會認為ACTION_DOWN事件沒有被執行完成,那麼其他的touch事件就不能被觸發了。
現在大家再去看事件分發流程圖中的View部分想必就能看懂了吧!!!
android開發之SnackBar的使用 SnackBar是一個類似於Toast的東西,它也有顯示時長,但是比Toast更加靈活,同時,我們還可以給SnackBar設置
安卓UI適配限定符 引言 對於程序在不同尺寸的Android機器上運行,對UI的適用性造成了額外的開銷,不過限定符的出現,很方便的解決了這個問題。通過創建
Android,androidstudio當我們自定義View的時候,在給View賦值一些長度寬度的時候,一般都是在layout布局文件中進行的。,比如android:l
OpenCV學習筆記(七)—— OpenCV for Android實時圖像處理 在上篇中我們已經實現了相機打開和實時圖像信息的獲取,那麼接下來我們可以嘗