// 1.首先会调用Activity的dispatchTouchEvent()方法 public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } /* * 如果没有view去处理,即getWindow().superDispatchTouchEvent(ev)返回为false * 则调用当前activity的onTouchEvent()方法 */ return onTouchEvent(ev); }
// 2.ViewGroup的事件分发
ViewGroup的onInterceptTouchEvent()方法默认返回为false,即不拦截当前的事件,会向栈底分发,直到事件不能再分发为止,则会调用当前view的onTouchEvent()方法。如果一个view对象的onTouchEvent方法执行并返回false,即表明这个事件没有被处理,需要调用上面一层ViewGroup的onTouchEvent()方法来处理当前的事件。如果返回到达栈顶,这个事件仍然没有被处理,那么就不再接收后续的move/up等事件。但是如果触发新的down事件后,事件的分发就会重新开始。
总之,是一个自顶向下的事件分发与自底向上的事件响应机制!
public boolean dispatchTouchEvent(MotionEvent ev) { // ... // Check for interception. final boolean intercepted; /* * 拦截判断 intercepted * 如果当前事件为DOWN 或者 触发DOWN事件的区域存在child view对象 * 首次DOWN事件触发时mFirstTouchTarget=null,在down后,如果存在childView, * mFirstTouchTarget的指向第一个TouchTarget,该TouchTarget对应的控件落在 * DOWN事件对应点的区域。。。 */ if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { /* * 是否允许拦截 * 该功能很重要,比如一个整个activity区域可以侧滑,而且存在一个可以水平滚动的水平滚动条,如果检测到是水平方法的滑动,侧滑会被拦截 * 同时希望,水平滚动条也可以有效,此时,可以为水平滚动条的设定一个OnTouchListener监听器,可以设定是否允许侧滑菜单是否拦截事件 */ 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 { intercepted = true; } // ... TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //如果事件没有比拦截,则会遍历子view 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 (childrenCount != 0) { // Find a child that can receive the event. // Scan children from front to back. final View[] children = mChildren; final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } //以上遍历出child view,符合 在down事件触发时对应的point是落在child view上控件 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; } /* * 子控件 对事件的处理 * 通过dispatchTransformedTouchEvent()方法,child view完成对事件的处理 * 因此,子控件对当前事件处理的返回值(true/false),在事件处理上至关重要 * 如果为true,即表明当前事件已经被处理,通过break语句,跳出循环,不再遍历其它的child view; * 如果为false,即表明事件没有被处理完成,继续遍历后续的view控件 */ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //addTouchTarget(),完成mFirstTouchTarget的赋值,在上面的拦截判断时需要 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } 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) { /* * 如果没有mFirstTouchTarget,则将当前的ViewGroup当做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) { //遍历处理 } } return handled; }
实现侧滑,一个要解决布局问题,这里介绍一种marginLeft相对位置,来控制布局,marginLeft可以为正也可以为负,还需要解决事件冲突问题
/** * 自定义侧滑菜单布局 * * @author zimo2013 * @see http://blog.csdn.net/zimo2013 */ public class SlidingView extends RelativeLayout { private static final String TAG = "SlidingLayout"; private GestureDetector detector; private boolean moveX; // x轴是否能够移动 private OnScrollListener mScrollListener; public SlidingView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // 自定义一个手势探测器 detector = new GestureDetector(context, new LayoutGestureListener()); detector.setIsLongpressEnabled(false);// 设置不支持长按,可以检测到onScroll()事件 } public SlidingView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SlidingView(Context context) { this(context, null, 0); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { moveX = detector.onTouchEvent(ev); // 手势探测器确定是否拦截 return moveX; } @Override public boolean onTouchEvent(MotionEvent event) {//自己处理当前事件 if (MotionEvent.ACTION_UP == event.getAction() && moveX) { if (mScrollListener != null) { mScrollListener.onRelease();// 如果是自己处理侧滑,应该检测侧滑的终点位置 } } return detector.onTouchEvent(event); } public class LayoutGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (moveX) { if (mScrollListener != null) { mScrollListener.onScroll(distanceX); } } else { moveX = Math.abs(distanceX) > Math.abs(distanceY);// 如果不是水平的,则不拦截,返回false } return moveX; } } /** * 侧滑监听器 * * @author Administrator * */ public interface OnScrollListener { void onScroll(float distanceX); /** * 处理用户抬出手指后的结果 */ void onRelease(); } public void setOnScrollListener(OnScrollListener listener) { mScrollListener = listener; } }
public class MainActivity extends BaseFragmentActivity implements SlidingView.OnScrollListener, OnItemClickListener, LoadingView.OnLoadingListener, OnClickListener { private static final String TAG = "MainActivity"; // 侧滑菜单相关成员变量 private RelativeLayout menu; private RelativeLayout.LayoutParams params; private SlidingView sliding; private int menuWidth; private ListView listMenu;; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); } private void initViews() { container = (LinearLayout) findViewById(R.id.container); back = (LinearLayout) findViewById(R.id.layoutl_group_back); back.setOnClickListener(this); // 侧滑菜单相关初始化 sliding = (SlidingView) findViewById(R.id.sliding); sliding.setOnScrollListener(this);//設置监听器 } /** * 处理左右滑动事件 * * @param value * @param restart * 是否重新设置left margin值 */ public void scrollX(int value, boolean restart) { if (restart) { params.leftMargin = value; params.rightMargin = -value; } else { params.leftMargin -= value; params.rightMargin += value; if (params.leftMargin > menuWidth) { params.leftMargin = menuWidth; params.rightMargin = -menuWidth; } else if (params.leftMargin < 0) { params.leftMargin = 0; params.rightMargin = 0; } } main.setLayoutParams(params); } // 监听器滑动事件 @Override public void onScroll(float distanceX) { scrollX((int) distanceX, false); } // 当用户抬出手指时的事件处理 @Override public void onRelease() { int left = params.leftMargin; if (left <= menuWidth / 2) { scrollX(0, true); } else { scrollX(menuWidth, true); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (params.leftMargin > 0) {// 用户按下返回键且菜单打开,应该折叠菜单项 scrollX(0, true); return true; } } // 菜单键,打开或者折叠菜单项 if (keyCode == KeyEvent.KEYCODE_MENU) { if (params.leftMargin > 0) { scrollX(0, true); return true; } else { scrollX(menuWidth, true); return true; } } return super.onKeyDown(keyCode, event); } // 用户点击侧滑item @Override public void onItemClick(AdapterView> parent, View view, int position, long id) { if (parent == listMenu) { show("【" + moreList.get(position).name + "】"); } } }
一整个activity区域可以侧滑,而且存在一个可以水平滚动的水平滚动条,如果检测到是水平方法的滑动,侧滑会被拦截。同时希望,水平滚动条也可以有效,此时,可以为水平滚动条的设定一个OnTouchListener监听器,可以设定是否允许侧滑菜单是否拦截事件。由于第一次触发DOWN事件时,该事件由于手势探测器的onDown()默认返回为false,即事件不被拦截,HorizontalScrollView将处理该事件。
hh.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { //没有不允许事件被拦截,即事件可以被拦截,会调用onInterceptTouchEvent() sliding.requestDisallowInterceptTouchEvent(false); } else { //不允许事件被拦截,即不会调用onInterceptTouchEvent()方法 sliding.requestDisallowInterceptTouchEvent(true); } return false; //必须返回为false,否则会认为该事件被处理了 } });