Android自定義控件——手把手教你實現SlidingMenu(二)

上篇回顧:上篇中創建了項目的基本組成,並且成功的把Activity的contentView  set  給了我們自定義的RelativeLayout中的SlidingMenuAbove

系列二

      本片介紹如何實現基本的滑動動作

  1. 手指在SlidingMenuAbove上左右滑動的時候SlidingMenuAbove在一定的情況下跟隨手指滑動
  2. 鬆開手指後Sliding完成餘下的動作,比如SldingMenuAbove滑動到初始位置/滑動到最終位置

1.此時我們就已經要開始考慮事件分發的情況了
  • 判定手指的滑動是否爲拖動動作,具體就是判斷X方向上的持續變化量>touchSlop(ViewCofigration中的常量,默認爲64,表示爲拖動動作的最小變化量),同時X>Y方向上的持續變化量。此時認爲是一個左右滑動動作而非上下滑動。然後將事件交給onTouchEvent處理
  • 不滿足上一條時把此事件攔截,滑動動作就不執行

SlidingMenuAbove.java   中的onInterceptTouchEvent中處理
事件交由onTouchEvent的Move中做具體的跟隨手指滾動,每次滾動的距離爲上一次滾動到的位置加上本次X的變化量,getScrollX()+deltaX
/**
 * SlidingMenu的上層用來裝Activity的ContentView的視圖的容器
 * 
 * @author mingwei
 * 
 */
public class SlidingMenuAbove extends ViewGroup {

	private final String TAG = SlidingMenuAbove.class.getSimpleName();
	private boolean DEBUG = false;

	private Scroller mScroller;
	private int mTouchSlop;
	private VelocityTracker mTracker;
	private int mMinimumVelocity;
	private int mMaximumVelocity;
	private int mFlingDistance;

	private boolean mEnabled = true;
	/**
	 * 是否允許被拖拽,在determineDrag中對觸摸事件進行計算處理然後決定是否被拖拽
	 */
	private boolean mIsBeingDraged;
	private boolean mIsUnableToDrag;
	private boolean mQuickReturn;

	private float mLastMotionX;
	private float mLastMotionY;
	private float mInitialMotionX;

	private int mCurItem;
	private float mScrollX = 0.0f;

	protected int mActivePointerID = INVALID_POINTER;
	private static final int INVALID_POINTER = -1;

	private final static int MAX_SETTLE_DURATION = 600;
	private final static int MIN_DISTANCE_FOR_FLING = 25;
	private final static Interpolator S_INTERPOLATOR = new Interpolator() {

		@Override
		public float getInterpolation(float t) {
			t -= 1.0f;
			return t * t * t * t * t + 1.0f;
		}
	};

	private View mContentView;

	public SlidingMenuAbove(Context context) {
		this(context, null);
	}

	public SlidingMenuAbove(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	@SuppressLint("ResourceAsColor")
	public SlidingMenuAbove(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		initAboveView();
	}

	private void initAboveView() {
		setWillNotDraw(false);
		setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
		setFocusable(true);
		Context context = getContext();
		mScroller = new Scroller(context, S_INTERPOLATOR);
		ViewConfiguration configuration = ViewConfiguration.get(context);
		mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
		mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
		mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

		/**
		 * 密度,和判定爲Fling動作的最小距離
		 */
		final float density = context.getResources().getDisplayMetrics().density;
		mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);

	}

	@Override
	protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
		mContentView.layout(arg1, arg2, arg3, arg4);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int width = getDefaultSize(0, widthMeasureSpec);
		int height = getDefaultSize(0, heightMeasureSpec);
		setMeasuredDimension(width, height);
		int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width);
		int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);
		mContentView.measure(contentWidth, contentHeight);
	}

	public void setContent(View content) {
		if (mContentView != null) {
			this.removeView(mContentView);
		}
		mContentView = content;
		addView(mContentView);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// Log.i("Gmw", "intercept_touch");
		final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
		/**
		 * 動作判定,是否對touch事件進行攔截
		 */
		switch (action) {
		case MotionEvent.ACTION_MOVE:
			// Log.i("Gmw", "intercept_touch_move");
			// determineDrag(ev);
			break;
		case MotionEvent.ACTION_DOWN:
			// Log.i("Gmw", "intercept_touch_down");
			int index = MotionEventCompat.getActionIndex(ev);
			mActivePointerID = MotionEventCompat.getPointerId(ev, index);
			if (mActivePointerID == INVALID_POINTER) {
				break;
			}
			mLastMotionX = mInitialMotionX = MotionEventCompat.getX(ev, index);
			mLastMotionY = MotionEventCompat.getY(ev, index);
			mIsBeingDraged = false;
			mIsUnableToDrag = false;
			break;
		case MotionEventCompat.ACTION_POINTER_UP:
			// Log.i("Gmw", "intercept_touch_up");
			break;
		default:
			break;
		}

		if (!mIsBeingDraged) {
			if (mTracker == null) {
				mTracker = VelocityTracker.obtain();
				mTracker.addMovement(ev);
			}
		}
		Log.i("Gmw", "intercept_touch_result=" + mIsBeingDraged);
		return mIsBeingDraged;
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		// Log.i("Gmw", "touch_touch");
		final int action = ev.getAction();
		if (mTracker == null) {
			mTracker = VelocityTracker.obtain();
		}
		mTracker.addMovement(ev);
		switch (action & MotionEventCompat.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
			// Log.i("Gmw", "touch_down");
			int index = MotionEventCompat.getActionIndex(ev);
			mActivePointerID = MotionEventCompat.getPointerId(ev, index);
			mLastMotionX = mInitialMotionX = ev.getX();
			break;
		case MotionEvent.ACTION_MOVE:
			// Log.i("Gmw", "touch_move");
			if (!mIsBeingDraged) {
				determineDrag(ev);
			}
			if (mIsBeingDraged) {
				final int activePoiterIndex = getPointerIndex(ev, mActivePointerID);
				if (activePoiterIndex == INVALID_POINTER) {
					break;
				}
				float x = MotionEventCompat.getX(ev, activePoiterIndex);
				float deltaX = mLastMotionX - x;
				// Log.i("Gmw", "detalX=" + detalX + "______" + mLastMotionX +
				// "-" + x);
				mLastMotionX = x;
				float oldScrollX = getScrollX();
				float scrollX = oldScrollX + deltaX;
				Log.i("Gmw", "scrollX_________" + scrollX + "______oldScrollX____" + oldScrollX + "_______" + deltaX);
				float leftLimit = 0;
				float rightLimit = -300;
				if (scrollX > leftLimit) {
					scrollX = leftLimit;
				}
				if (scrollX < rightLimit) {
					scrollX = rightLimit;
				}
				mLastMotionX += scrollX - (int) scrollX;
				scrollTo((int) scrollX, getScrollY());

			}
			break;
		case MotionEvent.ACTION_UP:
			// Log.i("Gmw", "touch_up");
			
			break;
		case MotionEvent.ACTION_CANCEL:
			// Log.i("Gmw", "touch_cancel");
			break;
		case MotionEventCompat.ACTION_POINTER_DOWN:
			// Log.i("Gmw", "touch_pointer_down");
			break;
		case MotionEventCompat.ACTION_POINTER_UP:
			// Log.i("Gmw", "touch_pointer_up");
			break;
		default:
			break;
		}

		return true;
	}

	/**
	 * 根據手指在屏幕上的滑動判定contentView上的滑動姿勢
	 * 
	 * <li>是豎着滑動還是橫着滑動</li> <li>滑動的時候X方向上的變化大於touchslop (
	 * {@link ViewConfigurationCompat#getScaledPagingTouchSlop})
	 * 並且X方向上的持續變化量大於Y方向上的持續變化量時就判定爲滑動菜單的拖動動作</li>
	 * 
	 * @param event
	 *            MotionEvent
	 */
	private void determineDrag(MotionEvent event) {
		final int activePointerID = mActivePointerID;
		final int pointerIndex = getPointerIndex(event, activePointerID);
		if (activePointerID == INVALID_POINTER || pointerIndex == INVALID_POINTER) {
			return;
		}
		float x = MotionEventCompat.getX(event, pointerIndex);
		float dx = x - mLastMotionX;
		float xDiff = Math.abs(dx);
		// Log.i("Gmw", "xDiff=" + xDiff);
		float y = MotionEventCompat.getY(event, pointerIndex);
		float dy = y - mLastMotionY;
		float yDiff = Math.abs(dy);
		// Log.i("Gmw", "yDiff=" + yDiff);
		if (xDiff > mTouchSlop && xDiff > yDiff) {
			// Log.i("Gmw", "drag______________________");
			startDrag();
			mLastMotionX = x;
			mLastMotionY = y;
		} else if (xDiff > mTouchSlop) {
			mIsUnableToDrag = true;
		}

	}

	/**
	 * 使用了V4包中的MotionEventCompat來輔助處理MotionEvent事件
	 * 
	 * @param event
	 * @param id
	 * @return
	 */
	private int getPointerIndex(MotionEvent event, int id) {
		int activePointerIndex = MotionEventCompat.findPointerIndex(event, id);
		if (activePointerIndex == -1) {
			activePointerIndex = INVALID_POINTER;
		}
		return activePointerIndex;
	}

	/**
	 * 拖拽開始
	 */
	private void startDrag() {
		mIsBeingDraged = true;
		mQuickReturn = false;

	}

	/**
	 * 拖拽結束
	 */
	private void endDrag() {
		if (mTracker != null) {
			mTracker.recycle();
			mTracker = null;
		}
	}

	/**
	 * 重寫scrollTo
	 */
	@Override
	public void scrollTo(int x, int y) {
		super.scrollTo(x, y);
		mScrollX = x;
	}

	public int determineTargetPage(float offset, int velocity, int deltax) {

		return 0;
	}

	public int getmCurItem() {
		return mCurItem;
	}

	public void setmCurItem(int mCurItem) {
		this.mCurItem = mCurItem;
	}

	/**
	 * 判斷LeftMenu或者RightMenu是否打開
	 * 
	 * @return 菜單開返回true,關返回flase
	 */
	public boolean isMenuOpen() {
		return mCurItem == 0 || mCurItem == 2;
	}

	/**
	 * 左滑動
	 * 
	 * @return 允許左滑並且滑動成功
	 */
	public boolean pageLeft() {
		if (mCurItem > 0) {
			// setmCurItem(mCurItem-1);
			return true;
		}
		return false;
	}

	/**
	 * 右滑動
	 * 
	 * @return 允許並且右滑動成功
	 */
	public boolean pageRight() {
		if (mCurItem < 1) {
			// do something
			return true;
		}
		return false;
	}
}

效果


2.可見目前的效果是手指拖動的時候上層的View已經跟着滾動了,但是鬆開手指後View就停在那兒不動了,所以我們在onTouchEvent中還需要繼續處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章