編輯:關於Android編程
事件機制是Android中一個比較復雜且重要的知識點,比如你想自定義攔截事件,或者某系組件中嵌套了其他布局,往往會出現這樣那樣的事件沖突,坑爹啊!!事件主要涵蓋onTouch,onClick,onTouchEvent,dispatchTouchEvent,onInterceptTouchEvent等等一系列事件,並且事件間還相互交互耦合,甚至有的事件還有返回值,一會true,一會false,什麼情況下返回true,什麼情況下返回false,為什麼要有返回值,想想這些就感覺整個人都不好了。
但是(萬惡的但是),該知識點還是必須要掌握的,知識的深度與廣度決定了你走的遠度,鑒於此我們就來捅一捅該知識點。
俗話說工欲善其事必先利其器,為了看他的執行流程,我們還是先寫個樣例,打幾個日志看看執行流程吧!
首先自定義一個外層布局的Layout,自定義Layout繼承了LinearLayout,復寫了相應的函數,在調用之前輸入日志。如下:
public class Layout extends LinearLayout {
public Layout(Context context) {
super(context);
init();
}
public Layout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Layout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//requestDisallowInterceptTouchEvent(false);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("Event", "Layout onInterceptTouchEvent " + MotionEvent.actionToString(ev.getAction()));
return super.onInterceptTouchEvent(ev);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("Event", "Layout onTouchEvent " + MotionEvent.actionToString(event.getAction()));
return super.onTouchEvent(event);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("Event", "Layout dispatchTouchEvent " + MotionEvent.actionToString(event.getAction()));
return super.dispatchTouchEvent(event);
}
}
我們還自定義了一個LogTextView,繼承自TextView,也是為了輸出日志,代碼如下:
public class LogTextView extends TextView {
public LogTextView(Context context) {
super(context);
}
public LogTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LogTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("Event", "TextView onTouchEvent " + MotionEvent.actionToString(event.getAction()));
return super.onTouchEvent(event);
}
@Override
public void setOnTouchListener(OnTouchListener l) {
super.setOnTouchListener(l);
}
@Override
public void setOnClickListener(OnClickListener l) {
super.setOnClickListener(l);
}
}
接下來是布局文件了:
布局中嵌套了兩個view,一個TextView,一個ImageView。最後就是主界面了。
public class MainActivity extends AppCompatActivity {
private LogTextView tv;
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
setViewListener();
}
private void findViews() {
tv = (LogTextView) findViewById(R.id.textView);
imageView = (ImageView) findViewById(R.id.image);
}
private void setViewListener() {
tv.setOnTouchListener(new View.OnTouchListener() {
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("Event", "TextView onTouch " + MotionEvent.actionToString(event.getAction()));
return true;
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("Event", "TextView onClick ");
}
});
imageView.setOnTouchListener(new View.OnTouchListener() {
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("Event", "ImageView onTouch " + MotionEvent.actionToString(event.getAction()));
return false;
}
});
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("Event", "ImageView onClick ");
}
});
}
}
round 1
TextView的onTouch返回為false,點擊TextView,日志如下:
05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_DOWN
05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_DOWN
05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_DOWN
05-05 14:04:44.091 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_DOWN
05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_MOVE
05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_MOVE
05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_MOVE
05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_MOVE
05-05 14:04:44.158 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_UP
05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_UP
05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_UP
05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onTouchEvent ACTION_UP
05-05 14:04:44.159 27644-27644/com.sunny.event E/Event: TextView onClick
根據日志我們可以看到首先有一個ACTION_DOWN事件,執行的順序是Layout的dispatchTouchEvent→onInterceptTouchEvent→(TextView)onTouch要→onTouchEvent,之後的我帕金森發生了,產生了ACTION_MOVE事件,傳遞的順序與Down是一致的,最後一個事件是UP事件,正常點擊不滑動是不會產生MOVE事件的,在這個這個三個事件最後調用了TextView的onClick事件。
小結:
1 . 事件的傳遞順序是先外層容器,之後再是具體的View。
2. onTouch事件先於onTouchEvent事件,onTouchEvent先於onClick事件
round 2
我們將TextView的onTouch事件返回true。重新執行。執行順序如下:
05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_DOWN
05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_DOWN
05-05 14:10:20.556 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_DOWN
05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_MOVE
05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_MOVE
05-05 14:10:20.616 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_MOVE
05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_UP
05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_UP
05-05 14:10:20.622 27644-27644/com.sunny.event E/Event: TextView onTouch ACTION_UP
從日志可以看出如果onTouch返回為true,執行順序變成了如下:
首先還是ACTION_DOWN事件(Layout)dispatchTouchEvent→onInterceptTouchEvent→(TextView)onTouch,ACTION_MOVE與ACTION_UP執行順序同ACTION_DOWN,可以發現的是TextView的onTouchEvent事件沒有了,並且onClick事件也沒有了。
小結
1,onTouch事件的返回值為true會攔截onTouchEvent事件
2,onTouchEvent與onClick有關聯
上面的兩次執行中每次都調用了onInterceptTouchEvent事件,這個到底又是啥?我們去看看他的返回值是什麼?
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
可以看到默認返回false,注釋長的嚇人,那我們就來改寫一下他的返回值,這個函數是ViewGroup才有的,說明與布局容器有關.
round 3
我們將Layout的onInterceptTouchEvent的返回值改為true。重新執行。執行順序如下:
05-05 14:59:17.829 15157-15157/com.sunny.event E/Event: Layout dispatchTouchEvent ACTION_DOWN
05-05 14:59:17.830 15157-15157/com.sunny.event E/Event: Layout onInterceptTouchEvent ACTION_DOWN
05-05 14:59:17.830 15157-15157/com.sunny.event E/Event: Layout onTouchEvent ACTION_DOWN
從日志可以發現,只有最外層的控件能夠執行事件,TextView已經收不到任何事件。
小結
父控件onInterceptTouchEvent返回true會攔截子控件的事件
我們從代碼的層面來看看他是怎麼執行的,當屏幕接收到點擊事件時會首先傳遞到Activity的dispatchTouchEvent:
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
在這裡執行了三步,
1.第一告訴用戶ACTION_DOWN,用戶可以復寫onUserInteraction來處理點擊開始
2.調用了getWindow().superDispatchTouchEvent(ev),這裡的getWindow得到是PhoneWindow對象,因此執行的PhoneWindow的superDispatchTouchEvent函數,
3.調用了Activity的onTouchEvent事件
PhoneWindow的superDispatchTouchEvent又調用了DecorView的superDispatchTouchEvent函數,每一個Activity都有一個PhoneWindow,每一個PhoneWindow都有一個DecorView,DecoView繼承自FrameLayout,這裡又調用了super.dispatchTouchEvent(event),FrameLayout裡面是沒有改函數的,所以最終執行的是ViewGroup的dispatchTouchEvent函數。
這裡我們先穿插一點界面的知識,以我測試手機為例,DecorView中有兩個child,分別是ViewStub和LinerLayout,LinerLayout中又包含了FrameLayout,FrameLayout中包含了一個ActionBarOverlayLayout,ActionBarOverlayLayout裡又包含了兩個child,分別是ActionBarContainer與ContentFrameLayout。
接下來我們去看看ViewGroup的dispatchTouchEvent函數:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
.........
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
............
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
.......
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled || actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
我們只看最重要的部分
1: 事件為ACTION_DOWN時,執行了cancelAndClearTouchTargets函數,該函數主要清除上一次點擊傳遞的路徑,之後執行了resetTouchState,重置了touch狀態,其中執行了 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;就是攔截狀態為false,這個與requestDisallowInterceptTouchEvent函數相關。
2: 獲取intercepted的值,首先判斷了disallowIntercept狀態,是否攔截子控件的事件執行。從代碼可以看到當disallowIntercept為false時,該狀態主要取決於onInterceptTouchEvent函數的返回值,這就是前面我們攔截的函數,如果為true,這時intercepted為true標識攔截。
3: 接著判斷了!canceled && !intercepted的值,canceled這裡為false,如果intercepted為false,則會進入判斷條件,這裡假設不攔截,進入後繼續判斷如果是ACTION_DOWN事件,則會繼續進入判斷,遍歷所有子控件,isTransformedTouchPointInView會判斷當前點擊區域是否在控件內,如果不在則遍歷下一個,之後調用dispatchTransformedTouchEvent函數。最後在調用addTouchTarget函數,將當前選中的控件,掛載到當前點擊目標鏈表。alreadyDispatchedToNewTouchTarget賦值為true。
接著判斷mFirstTouchTarget是否為空,經過上一步的addTouchTarget的執行,這裡mFirstTouchTarget不為空。第一個事件alreadyDispatchedToNewTouchTarget為true,且target == newTouchTarget,因此handled值為true,如果是後續的事件,則會進入dispatchTransformedTouchEvent中。
我們接著看看第三部中的dispatchTransformedTouchEvent函數:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
.......
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
.......
// Done.
transformedEvent.recycle();
return handled;
}
這裡會進入到第10行,且傳遞過來的child不為空,因此會繼續執行child.dispatchTouchEvent,這裡繼續執行ViewGroup的dispatchTouchEvent,一直遞歸執行,直到真正接受點擊的控件,到最後child會為空,這裡要麼是一個View控件,要麼是未包含任何子控件的ViewGroup,這時這裡會執行View的dispatchTouchEvent。
從上述執行邏輯可以直到,先從DecorView一直遞歸到Layout,最後再到TextView,這裡我們去看看View的dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent event) {
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
首先會停止掉嵌套滑動,之後先判斷了ListenerInfo不為空,這裡只要是設置了onTouch,onKey,onHover,onDrag等等中的任何一個這裡就不為空,具體可以去看看ListenerInfo包含的listenerInfo類型。其次判斷了mOnTouchListener不為空,只要設置了onTouchListener這裡就不為空,再之後判斷了該控件是否是enabled,一般都會enabled,可以代碼設置為false,再之後調用了mOnTouchListener的onTouch事件,這裡就是外面傳進來的onTouchListener,從這裡可以看到無論onTouch返回任何值,onTouch事件都會執行,但是如果返回為true,則會導致result為true,!result && onTouchEvent(event)因為短路,不會執行到onTouchEvent事件。
小結
1:onTouch返回為true導致onTouchEvent不能執行
2:如果enable為false,因為短路onTouch不會執行
到此還沒有看到任何onClick事件的執行,我們繼續去看看onTouchEvent函數:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_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.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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();
}
mIgnoreNextUpEvent = false;
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 |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
我們首先看ACTION_DOWN事件,這裡主要看checkForLongClick,CheckForTap中也調用了該函數,這裡就是添加一個長按事件,如果達到長按標准且長按listener不為空,則執行長按事件,接著我們看ACTION_UP,這裡看到如果不是長按事件,則調用了performClick,performClick裡面執行了onClick事件。
小結
1:onClick事件與onLongClick事件是在onTouchEvent中執行的
2:如果執行了長按事件則onClick不執行
3:就api 23代碼,長按的時間間隔為500毫秒
上面解析了intercepted為false的情況,那intercepted為true,它到底是怎麼攔截的?
如果intercepted為true,則!canceled && !intercepted為false,不能進入該判斷,mFirstTouchTarget為空,會繼續執行如下分支:
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
這裡第三參數傳遞的child為null,因此就會執行該控件onTouch與onTouchEvent函數,不會繼續遞歸傳遞,因此也就攔截了子控件的執行。
一般我們都是使用android:xxx=...這樣的android的屬性。但有時我們需要使用自定義的屬性,尤其是自定義view的時候尤其需要。 一般需要以下幾個步驟: 1
首先給大家看一下我們今天這個最終實現的效果圖:我這裡只是單純的實現了ListView返回頂部的功能。具體效果大家可以適當地美化在實際項目中可以換圖標,去掉右側滾動條等。具
一、著色游戲概述近期群裡偶然看到一哥們在群裡聊不規則圖像填充什麼四聯通、八聯通什麼的,就本身好學務實的態度去查閱了相關資料。對於這類著色的資料,最好的就是去搜索些相關ap
在 Android 上使用 RxNettyNetty是由JBOSS提供的一個Java開源框架,是一個支持TCP/UDP/HTTP等網絡協議的通信框架,和Mina類似,廣泛