package com.com.mr_wrong.CustomView; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.FrameLayout; import java.util.HashMap; import java.util.Set; /** * @author : 苦咖啡 * @version : 1.0 * @date :2015年4月19日 * @blog : http://blog.csdn.net/cyp331203 * @desc : */ public class InterceptorFrameLayout extends FrameLayout { /** * 代表滑动方向向上 */ public static final int ORIENTATION_UP = 0x1;// 0000 0001 /** * 代表滑动方向向下 */ public static final int ORIENTATION_DOWN = 0x2;// 0000 0010 /** * 代表滑动方向向左 */ public static final int ORIENTATION_LEFT = 0x4;// 0000 0100 /** * 代表滑动方向向右 */ public static final int ORIENTATION_RIGHT = 0x8;// 0000 1000 /** * 代表滑动方向的所有方向 */ public static final int ORIENTATION_ALL = 0x10;// 0001 0000 /** * 存放view的左上角的x和y坐标 */ static int[] touchLocation = new int[2]; /** * 用来代表触发移动事件的最短距离,如果小于这个距离就不触发移动控件,如viewpager就是用这个距离来判断用户是否翻页 */ private int mTouchSlop; /** * 用来记录Down事件发生时的x坐标 */ private float downX; /** * 用来记录Down事件发生时的y坐标 */ private float downY; /** * 用来存放需要自主控制事件分发的子view,以及其对应的滑动方向 */ private HashMap<View, Integer> mViewAndOrientation = new HashMap<View, Integer>(); /** * 表示某次事件发生时,找到的mViewAndOrientation中符合条件的子view */ private View mFirstTarget = null; private ViewConfiguration configuration; public InterceptorFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public InterceptorFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public InterceptorFrameLayout(Context context) { super(context); init(); } private void init() { configuration = ViewConfiguration.get(getContext()); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); // 意思应该是触发移动事件的最短距离,如果小于这个距离就不触发移动控件, // 如viewpager就是用这个距离来判断用户是否翻页 mTouchSlop = configuration.getScaledTouchSlop(); if (mFirstTarget != null) { // mFirstTarget不为空,表示最近的一次DOWN事件已经被mViewAndOrientation集合中的某个子view响应 // 于是将后续的事件继续分发给这个子view boolean flag = mFirstTarget.dispatchTouchEvent(ev); // 如果flag=true,表示事件被完全消耗,结束了,如果事件是ACTION_CANCEL或者ACTION_UP, // 也代表事件的结束,于是将mFirstTarget置空,便于下一次DOWN事件的响应 if (flag && (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP)) { mFirstTarget = null; } // 返回flag return flag; } // 拿到本次事件的坐标,由于只需要计算差值,所以getX也可以 final float currentX = ev.getX(); final float currentY = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mFirstTarget = findTargetView(ev, ORIENTATION_ALL); downX = currentX; downY = currentY; break; case MotionEvent.ACTION_MOVE: if (Math.abs(currentX - downX) / Math.abs(currentY - downY) > 0.5f && Math.abs(currentX - downX) > mTouchSlop) { System.out.print("左右滑动"); // 左右滑动 if (currentX - downX > 0) { // 右滑 mFirstTarget = findTargetView(ev, ORIENTATION_RIGHT); System.out.println("mFirstTarget=" + mFirstTarget); } else { // 左滑 mFirstTarget = findTargetView(ev, ORIENTATION_LEFT); System.out.println("mFirstTarget=" + mFirstTarget); } } else if (Math.abs(currentY - downY) / Math.abs(currentX - downX) > 0.5f && Math.abs(currentY - downY) > mTouchSlop) { System.out.print("上下滑动"); // 上下滑动 if (currentY - downY > 0) { // 向下 mFirstTarget = findTargetView(ev, ORIENTATION_DOWN); System.out.println("mFirstTarget=" + mFirstTarget); } else { // 向上 mFirstTarget = findTargetView(ev, ORIENTATION_UP); System.out.println("mFirstTarget=" + mFirstTarget); } mFirstTarget = null; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mFirstTarget = null; break; } // 走到这里,只要mFirstTarget不为空,则在集合中找到了对应的子view, // 则返回true,表示本次事件被消耗,不继续分发 if (mFirstTarget != null) { return true; } else { return super.dispatchTouchEvent(ev); } } /** * 在集合中查找对应event和方向参数的view,找到了则返回,没找到返回null */ private View findTargetView(MotionEvent ev, int orientation) { // mViewAndOrientation为存放要监测触摸事件的子view和对应方向参数的集合 Set<View> keySet = mViewAndOrientation.keySet(); for (View view : keySet) { Integer ori = mViewAndOrientation.get(view); // 由于所有的方向参数都是二进制相互与运算为0的 // 所以这里使用与运算来判断方向是否符合 // 这里所有的判断条件是: // ①该子view在mViewAndOrientation集合内 // ②方向一致 // ③触摸事件落在该子view的范围内 // ④该子view可以消费掉本次事件 // 同时满足上面四个条件,则代表该子view是我们要找的子view,于是返回 if ((ori & orientation) == orientation && isTouchInView(ev, view) && view.dispatchTouchEvent(ev)) { return view; } } return null; } public static boolean isTouchInView(MotionEvent ev, View view) { view.getLocationOnScreen(touchLocation); float motionX = ev.getRawX(); float motionY = ev.getRawY(); // 返回是否在范围内 return motionX >= touchLocation[0] && motionX <= (touchLocation[0] + view.getWidth()) && motionY >= touchLocation[1] && motionY <= (touchLocation[1] + view.getHeight()); } /** * 添加拦截 */ public void addInterceptorView(final View view, final int orientation) { // 到主线程执行 BaseApplication.getMainThreadHandler().post(new Runnable() { @Override public void run() { if (!mViewAndOrientation.containsKey(view)) { mViewAndOrientation.put(view, orientation); } } }); } /** * 去除拦截效果 */ public void removeInterceptorView(final View v) { // 到主线程执行 BaseApplication.getMainThreadHandler().post(new Runnable() { @Override public void run() { if (!mViewAndOrientation.containsKey(v)) { mViewAndOrientation.remove(v); } } }); } }