Android_事件的分发与处理机制及解决事件冲突问题

本博文为子墨原创,转载请注明出处!
http://blog.csdn.net/zimo2013/article/details/16807023

1.事件概述

// 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的事件分发

Android_事件的分发与处理机制及解决事件冲突问题_第1张图片

ViewGroup的onInterceptTouchEvent()方法默认返回为false,即不拦截当前的事件,会向栈底分发,直到事件不能再分发为止,则会调用当前view的onTouchEvent()方法如果一个view对象的onTouchEvent方法执行并返回false,即表明这个事件没有被处理,需要调用上面一层ViewGroup的onTouchEvent()方法来处理当前的事件。如果返回到达栈顶,这个事件仍然没有被处理,那么就不再接收后续的move/up等事件。但是如果触发新的down事件后,事件的分发就会重新开始。

总之,是一个自顶向下的事件分发与自底向上的事件响应机制

2.dispatchTouchEvent()源码分析

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;
}

3.侧滑菜单

Android_事件的分发与处理机制及解决事件冲突问题_第2张图片                          Android_事件的分发与处理机制及解决事件冲突问题_第3张图片

实现侧滑,一个要解决布局问题,这里介绍一种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 + "】");
		}
	}
}

4.侧滑与HorizontalScrollView事件冲突问题

一整个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,否则会认为该事件被处理了
	}
});

你可能感兴趣的:(Android,Android高级)