《Android自定义控件入门到精通》文章索引 ☞ https://blog.csdn.net/Jhone_csdn/article/details/118146683
《Android自定义控件入门到精通》所有源码 ☞ https://gitee.com/zengjiangwen/Code
文章目录
事件分发机制
Event事件
触摸交互是智能手机必不可少的交互方式,用户通过触摸屏幕的方式,来与我们的应用交互。
屏幕捕获事件后通过onTouchEvent(MotionEvent event)来监听用户的触摸。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//当用户按下
break;
case MotionEvent.ACTION_MOVE:
//当用户移动
break;
case MotionEvent.ACTION_UP:
//当用户抬起
break;
}
return super.onTouchEvent(event);
}
我们可以在哪些地方监听触摸事件?
- Activity
- 自定义ViewGroup
- 自定义View
- View对象.setOnTouchListener(OnTouchListener l)
案例:移动View
我们可以通过监听上面说到的四个地方实现这个例子:
Activity-onTouchEvent实现
public class TestActivity extends AppCompatActivity {
private MyView myView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
myView = findViewById(R.id.myView);
}
float mX, mY;
float dx,dy;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mX = event.getX();
mY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
dx=event.getX()-mX;
dy=event.getY()-mY;
myView.setTranslationX(myView.getX()+dx);
myView.setTranslationY(myView.getY()+dy);
mX = event.getX();
mY = event.getY();
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}
}
关注点:
- event.getX()
- return super.onTouchEvent(event)
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="horizontal">
<cn.code.code.wiget.MyView
android:id="@+id/myView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:bg="#ff0000"
app:radius="40dp" />
</LinearLayout>
自定义View-onTouchEvent实现
public class MyView extends View {
private int bg, radius;
private Paint mPaint;
//当创建的方式是 new MyView(context)时调用
public MyView(Context context) {
this(context, null);
}
//当创建的方式是 xml布局的方式时调用
public MyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyView);
//获取bg属性
bg = ta.getColor(R.styleable.MyView_bg, 0xff00ff00);
//获取radius属性
radius = ta.getDimensionPixelSize(R.styleable.MyView_radius, 30);
//注意回收
ta.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(bg);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取模式和尺寸
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0, height = 0;
//根据模式,求得合理的尺寸
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
width = radius * 2;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
height = radius * 2;
break;
}
//设置自定义View的尺寸
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xff888888);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);
}
float mX, mY;
float dx, dy;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//这里要使用getRawX,来获取相对于屏幕的坐标
mX = event.getRawX();
mY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
dx = event.getRawX() - mX;
dy = event.getRawY() - mY;
setTranslationX(this.getX() + dx);
setTranslationY(this.getY() + dy);
mX = event.getRawX();
mY = event.getRawY();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
关注点:
- event.getRawX()
- return true
自定义ViewGroup-onTouchEvent实现
public class MyGroupView extends ViewGroup {
//当创建的方式是 new MyView(context)时调用
public MyGroupView(Context context) {
this(context, null);
}
//当创建的方式是 xml布局的方式时调用
public MyGroupView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyGroupView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View child=getChildAt(0);
child.layout(0,0,child.getMeasuredWidth(),child.getMeasuredHeight());
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
float mX, mY;
float dx, dy;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//这里要使用getRawX,来获取相对于屏幕的坐标
mX = event.getRawX();
mY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
dx = event.getRawX() - mX;
dy = event.getRawY() - mY;
getChildAt(0).setTranslationX(getChildAt(0).getX() + dx);
getChildAt(0).setTranslationY(getChildAt(0).getY() + dy);
mX = event.getRawX();
mY = event.getRawY();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
关注点:
- event.getRawX()
- return true
View对象.setOnTouchListener(OnTouchListener l)实现
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
myView = findViewById(R.id.myView);
myView.setOnTouchListener(new View.OnTouchListener() {
float mX, mY;
float dx, dy;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mX = event.getRawX();
mY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
dx = event.getRawX() - mX;
dy = event.getRawY() - mY;
myView.setTranslationX(myView.getX() + dx);
myView.setTranslationY(myView.getY() + dy);
mX = event.getRawX();
mY = event.getRawY();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
});
}
关注点:
- event.getRawX()
- return true
在上面四种写法中,我们有两个地方要注意的:
- event.getX()和event.getRawX()的区别:event.getX()是相对于View左上角(0,0),event.getRawX()是相对屏幕左上角(0,0),前者是相对于父控件的位置,后者是相对于屏幕的位置
- onTouchEvent()和onTouch()的返回值:return true、return false、return super.onTouchEvent(event)是什么意思,有什么用,该怎么用,先来了解下事件的分发机制👇
事件的分发机制
Event事件相关的方法:
- dispatchTouchEvent(MotionEvent event),事件的分发
- onInterceptTouchEvent(MotionEvent event),事件的拦截(这个方法只有ViewGroup有)
- onTouchEvent(MotionEvent event),事件的处理
我们通过自定义View模型EventActivity–>MyViewGroup–>MyView来探寻Event事件分发机制
xml:
<cn.code.code.wiget.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="horizontal">
<cn.code.code.wiget.MyView
android:id="@+id/myView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:bg="#ff0000"
app:radius="40dp" />
</cn.code.code.wiget.MyViewGroup>
然后在EventActivity、MyViewGroup、MyView中重写这几个方法:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Logger.e(getClass().getSimpleName()+"--》onInterceptTouchEvent:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Logger.e(getClass().getSimpleName()+"--》onInterceptTouchEvent:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Logger.e(getClass().getSimpleName()+"--》onInterceptTouchEvent:ACTION_UP");
break;
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Logger.e(getClass().getSimpleName()+"--》onTouchEvent:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Logger.e(getClass().getSimpleName()+"--》onTouchEvent:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Logger.e(getClass().getSimpleName()+"--》onTouchEvent:ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
当我们点击MyView所在区域,查看日志:
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyViewGroup--》onInterceptTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyViewGroup--》onTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: EventActivity--》onTouchEvent:ACTION_DOWN
06-24 15:43:33.360 5203-5203/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-24 15:43:33.360 5203-5203/cn.code.code E/BUG: EventActivity--》onTouchEvent:ACTION_UP
点击会触发两个事件,ACTION_DOWN和ACTION_UP:
- ACTION_DOWN(按下):事件由dispatchTouchEvent方法从EventActivity传递到MyView,再依次执行MyView、MyViewGroup、EventActivity的onTouchEvent事件
- ACTION_UP(抬起):事件由EventActivity的dispatchTouchEvent分发,由EventActivity的onTouchEvent消费
再来看看Activity、ViewGroup、View默认的事件处理机制源码
源码分析
Activity事件处理
//Activity.java
//事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//将事件传递给RootView(ViewGroup)
//true/false:ViewGroup消费/不消费事件
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果RootView没有消费事件(false👆),则Activity中的onTouchEvent被调用
return onTouchEvent(ev);
}
//事件处理
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
在上面Activity中的源码中,我们知道,Activity中的onTouchEvent方法会不会被调用,取决于RootView(ViewGroup)中的dispatchTouchEvent的返回值,那我们把MyViewGroup中的dispatchTouchEvent方法直接返回true,看看事件是不是不会传递给MyView,并且所有的onTouchEvent方法都不会被调用
//MyViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_UP");
break;
}
// return super.dispatchTouchEvent(event);
return true;
}
查看日志:
06-28 11:05:32.128 4035-4035/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-28 11:05:32.128 4035-4035/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-28 11:05:32.194 4035-4035/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-28 11:05:32.194 4035-4035/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_UP
可以看到,ACTION_DOWN和ACTION_UP事件都是分发到MyViewGroup后,就结束了
ViewGroup中dispatchTouchEvent的结论:
- return true:事件停止分发,且所有的onTouchEvent事件不会被调用
- return false:我们现在只知道Activity中的onTouchEvent会被调用,事件进一步的处理,需要看看RootView(ViewGroup)中的dispatchTouchEvent源码👇
ViewGroup事件处理
//ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean handled = false;
//判断当前事件是否可以继续分发(Window is obscured, drop this touch)
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//当事件为新触发的事件,把之前没处理完的事件丢弃
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
//标记事件是否被拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//可以通过ViewGroup对象.requestDisallowInterceptTouchEvent(true/false)设置FLAG_DISALLOW_INTERCEPT
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//当前ViewGroup中的事件可以被拦截(默认disallowIntercept=false)
if (!disallowIntercept) {
//调用onInterceptTouchEvent()方法,返回值intercepted参与事件的分发流程
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {//当前ViewGroup被设置为不可拦截事件
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;
}
//当事件没有被拦截
if (!canceled && !intercepted) {
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.
//遍历View树,找出那些包含点击坐标的child
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历child,从View树最底层的那个child开始
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//如果child不能接收事件,则遍历下一个child(比如child设置为clickable=false,则不能接收事件)
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//调用child的dispatchTouchEvent()方法,如果返回true,则:
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();
//将child添加到可以调用onTouchEvent方法的集合中
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
......
}
if (preorderedList != null) preorderedList.clear();
}
......
}
}
// Dispatch to touch targets.
// 将事件分发给child
// 当点击区域没有可用的child时
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 调用super.dispatchTouchEvent(event),也就是View.dispatchTouchEvent(event)
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {// 将事件分发给child
// 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;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
//默认不拦截
return false;
}
ViewGroup继承至View,且并没有重写onTouchEvent(event)方法
View事件处理
public boolean dispatchTouchEvent(MotionEvent event) {
......
// 标记事件是否结束
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 停止滚动事件
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//View设置为enable时,return true,事件由View的onTouchEvent方法处理
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
//处理View对象设置的mOnTouchListener触摸监听事件
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
//如果mOnTouchListener的onToutchEvent方法返回true,事件结束,View的onTouchEvent不会被调用👇
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//result=false
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//处理不可点击状态的事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//处理可点击状态的事件
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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)) {
performClickInternal();
}
}
}
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:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
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(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
final float ambiguousMultiplier =
ViewConfiguration.getAmbiguousGestureMultiplier();
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* ambiguousMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= ambiguousMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
结论:
- 当OnTouchListener返回true时,事件传递到OnTouchListener就结束了,View的onTouchEvent()不会被调用
- 当OnTouchListener返回false时,事件传递到OnTouchListener,还会继续调用View的onTouchEvent()方法
事件分发总结
dispatchTouchEvent
- return true or false:事件结束,onTouchEvent()不会被调用
- return super.dispatchTouchEvent():继续分发事件,onTouchEvent()会被调用
onInterceptTouchEvent
- return true:拦截事件,事件交由onTouchEvent()方法处理并结束
- return false:不拦截事件,事件继续分发
- return super.onInterceptTouchEvent():不拦截事件,事件继续分发
onTouchEvent
onTouchEvent的返回值决定了MOVE和UP事件分发给哪个控件监听(决定了事件由哪个控件结束),优先级 MyView>MyViewGroup>EventActivity
- return true:MOVE和UP事件分发给当前View监听
- return false:MOVE和UP事件分发给上一个View监听
- return super.onTouchEvent:MOVE和UP事件分发给上一个View监听
onTouchEvent的返回值不好理解,我们举个例子,还拿EventActivity–MyViewGroup–MyView模型来说
默认情况下的点击事件流程是这样的:
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: MyViewGroup--》onTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: EventActivity--》onTouchEvent:ACTION_DOWN
06-28 15:58:11.733 8531-8531/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-28 15:58:11.733 8531-8531/cn.code.code E/BUG: EventActivity--》onTouchEvent:ACTION_UP
- ACTION_DOWN事件在三个类中的onTouchEvent()中都有调用
- ACTION_UP事件只会执行一次,且在EventActivity中的onTouchEvent()消费
当我们在MyViewGroup中的onTouchEvent()方法中return true
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_DOWN
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_DOWN
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: MyViewGroup--》onTouchEvent:ACTION_DOWN
06-28 16:00:21.213 8634-8634/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-28 16:00:21.213 8634-8634/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_UP
06-28 16:00:21.213 8634-8634/cn.code.code E/BUG: MyViewGroup--》onTouchEvent:ACTION_UP
- ACTION_DOWN事件在MyViewGroup和MyView两个类中的onTouchEvent()中都有调用
- ACTION_UP事件只会执行一次,且在MyViewGroup中的onTouchEvent()消费
同样的当我们在MyView中的onTouchEvent()方法中return true
06-28 16:03:35.971 8743-8743/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-28 16:03:35.971 8743-8743/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-28 16:03:35.971 8743-8743/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_DOWN
06-28 16:03:35.971 8743-8743/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_DOWN
06-28 16:03:36.045 8743-8743/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-28 16:03:36.045 8743-8743/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_UP
06-28 16:03:36.045 8743-8743/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_UP
06-28 16:03:36.045 8743-8743/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_UP
- ACTION_DOWN事件仅在MyView中的onTouchEvent()中调用
- ACTION_UP事件只会执行一次,且在MyView中的onTouchEvent()消费