一、事件分发概述
在Android中,事件分发指的是Android系统把用户的触摸事件或按键事件从Activity分发到用户界面中的各个View的过程。一个ViewGroup或View在收到事件后,会先进行一些相关的处理,然后再把事件传递给子View进行处理。
事件分发过程中,每个View都有自己的触摸事件或按键事件处理方法,同时也可以设置是否拦截事件或者是否传递事件。这种机制使得开发者可以自由地设置View在事件流传递中的位置和相关的处理方法,从而实现各种复杂的交互效果。
二、事件分发流程
Android事件分发流程主要分为三个部分,分别是:事件的分发、事件的拦截和事件的处理。整个过程涉及到如下三个方法:
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if(onInterceptTouchEvent(ev)) { consume = onTouchEvent(ev); } else { consume = child.dispatchTouchEvent(ev); } return consume; } public boolean onInterceptTouchEvent(MotionEvent ev) { return false; } public boolean onTouchEvent(MotionEvent ev) { return false; }
1、事件的分发
事件的分发流程是从Activity的dispatchTouchEvent方法开始,系统把用户的触摸事件或按键事件分发给根布局中的ViewGroup。在分发事件时,ViewGroup会先调用自己的onInterceptTouchEvent方法判断是否要拦截事件,如果不拦截,则继续将事件分发给自己的子View处理;否则,ViewGroup会直接调用自己的onTouchEvent方法处理事件。
2、事件的拦截
ViewGroup的onInterceptTouchEvent方法用来判断事件是否需要被拦截。如果事件被拦截,则会直接交给ViewGroup的onTouchEvent方法处理;否则,事件继续传递给子View进行处理。
3、事件的处理
View的onTouchEvent方法用来处理具体的触摸事件或按键事件。如果View对事件进行了处理,则返回true;否则返回false,并允许事件继续传递给上层View。
三、事件分发示例
为了更好地理解事件分发机制,下面我们通过代码来模拟一个事件分发的示例。
1、首先,我们创建一个自定义View,用于展示事件流传递的过程:
public class MyView extends View { private static final String TAG = "MyView"; public MyView(Context context) { super(context); } public MyView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "MyView onTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "MyView onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "MyView onTouchEvent ACTION_UP"); break; } return super.onTouchEvent(event); } }
2、然后,我们创建一个自定义ViewGroup,用于展示ViewGroup处理事件的流程:
public class MyViewGroup extends ViewGroup { private static final String TAG = "MyViewGroup"; public MyViewGroup(Context context) { super(context); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int childTop = 0; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); child.layout(0, childTop, child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); childTop += child.getMeasuredHeight(); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "MyViewGroup onInterceptTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "MyViewGroup onInterceptTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "MyViewGroup onInterceptTouchEvent ACTION_UP"); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "MyViewGroup onTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "MyViewGroup onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "MyViewGroup onTouchEvent ACTION_UP"); break; } return super.onTouchEvent(event); } }
3、最后,我们在Activity中创建MyViewGroup,并添加MyView作为其子View:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyViewGroup myViewGroup = findViewById(R.id.myViewGroup); MyView myView = new MyView(this); myView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300)); myViewGroup.addView(myView); } }
运行后,我们可以看到在控制台输出了如下事件流:
MyViewGroup onInterceptTouchEvent ACTION_DOWN MyView onTouchEvent ACTION_DOWN MyView onTouchEvent ACTION_MOVE MyView onTouchEvent ACTION_MOVE MyView onTouchEvent ACTION_MOVE MyView onTouchEvent ACTION_MOVE MyView onTouchEvent ACTION_MOVE MyView onTouchEvent ACTION_UP
从输出中可以看出,事件首先被MyViewGroup的onInterceptTouchEvent方法拦截了,然后直接交给了MyView的onTouchEvent方法进行处理。在处理过程中,MyViewGroup并没有再次处理事件。
四、事件分发机制的灵活运用
事件分发机制是Android应用程序中非常常用的一种机制,它可以被开发人员灵活运用于各种交互效果的实现中。下面我们以一个简单的实例来讲述如何使用事件分发机制来实现自定义View的滑动效果。
1、首先,我们创建一个自定义View:
public class DragView extends View { private int lastX; private int lastY; public DragView(Context context) { super(context); } public DragView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public DragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int dx = (int) event.getRawX() - lastX; int dy = (int) event.getRawY() - lastY; int left = getLeft() + dx; int top = getTop() + dy; int right = getRight() + dx; int bottom = getBottom() + dy; layout(left, top, right, bottom); lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); break; case MotionEvent.ACTION_UP: break; } return true; } }
2、然后,我们创建一个自定义ViewGroup用于包含DragView:
public class MyLayout extends FrameLayout { private DragView dragView; public MyLayout(Context context) { super(context); init(context); } public MyLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } public MyLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { dragView = new DragView(context); dragView.setBackgroundColor(Color.RED); dragView.setLayoutParams(new LayoutParams(200, 200)); addView(dragView); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: return false; case MotionEvent.ACTION_MOVE: return true; case MotionEvent.ACTION_UP: return false; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; case MotionEvent.ACTION_MOVE: dragView.onTouchEvent(event); return true; case MotionEvent.ACTION_UP: return true; } return super.onTouchEvent(event); } }
3、最后,在Activity中创建MyLayout,即可实现滑动效果:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyLayout myLayout = findViewById(R.id.myLayout); } }
运行后,我们可以通过按下MyLayout并滑动来控制DragView的移动。
五、总结
通过上面的讲解和示例,我们可以看到Android事件分发机制是一个非常重要的机制,它能够灵活地帮助开发者实现各种复杂的交互效果。在实际开发过程中,我们需要根据具体需求合理地设置View在事件处理流程中的位置和拦截事件的位置,从而保证交互效果能够正常实现。