首页 > 代码库 > 如果写一个android桌面滑动切换屏幕的控件(一)

如果写一个android桌面滑动切换屏幕的控件(一)

首先这个控件应该是继承ViewGroup:

初始化:

public class MyGroup extends ViewGroup{

	private Scroller mScroller;
	private float mOriMotionX;
	private float mLastMotionX;
	private VelocityTracker mVelocityTracker;
	private int mTouchState = TOUCH_STATE_REST;
	private static final int TOUCH_STATE_REST = 0;
	private int mTouchSlop;
	private int mMaximumVelocity;
	private static final int TOUCH_STATE_SCROLLING = 1;
	private float mLastDownX;
	private static final int DEFAULT_VALUE = http://www.mamicode.com/1000;>

先重写onmeasure:

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		final int width = MeasureSpec.getSize(widthMeasureSpec);
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}

	}

onLayout:

@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		int paddingleft = 0;
		int paddingTop = 0;
		int childLeft = paddingleft;
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			final View child = getChildAt(i);
			if (child.getVisibility() != View.GONE) {
				final int childWidth = child.getMeasuredWidth();
				final int childHeight = child.getMeasuredHeight() ;

				child.layout(childLeft, paddingTop, childLeft + childWidth,
						childHeight + paddingTop);
				<strong>childLeft += child.getMeasuredWidth();  //下个child的左边距和第一个child的左边距之间的距离正好是第一个child的width</strong>
			}
		}
		
	}

然后写View的touch事件:

onInterceptTouchEvent只有返回false事件才会传递给控件里的view,就是view的ontouch事件才可以捕捉
View里的onTouchEvent返回为true,才能执行多次touch事件,事件才能得了传递

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = ev.getAction();
		//如果为move事件,mTouchState为TOUCH_STATE_REST为静止状态,这个是防止子控件在滑动时又用手指去滑,这种情况下不响应这个事件
		if ((action == MotionEvent.ACTION_MOVE)
				&& (mTouchState != TOUCH_STATE_REST)) {
			return true;
		}

		final float x = ev.getX();

		switch (action) {
			case MotionEvent.ACTION_MOVE:
				final int xDiff = (int) Math.abs(x - mLastMotionX);
				final int touchSlop = mTouchSlop;
				boolean xMoved = xDiff > touchSlop;
				//如果xMoved为true表示手指在滑动
				if (xMoved) {
					mTouchState = TOUCH_STATE_SCROLLING;
				}
				break;
			case MotionEvent.ACTION_DOWN:
				mLastMotionX = x;
				//mScroller.isFinished() 为true表示滑动结束了,这时候我们把状态置为TOUCH_STATE_REST
				mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
						: TOUCH_STATE_SCROLLING;
				break;

			case MotionEvent.ACTION_CANCEL:
			case MotionEvent.ACTION_UP:
				mTouchState = TOUCH_STATE_REST;
				break;
			default:
				break;
			}
		
		//如果不是在静止状态,都返回true,这样事件就不会传递给onTouchEvent了
		return mTouchState != TOUCH_STATE_REST;
	}

在滑动的时候返回true的原因是这时候不需要响应里面控件的ontouch事件

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(ev);

		int mScrollX = this.getScrollX(); //mScrollX表示X轴上的距离,往左滑动为正,这个时候屏幕向右移动

		final int action = ev.getAction();
		final float x = ev.getX();
		final float y = ev.getY();
		switch (action) {
			case MotionEvent.ACTION_DOWN:
				mOriMotionX = x;
				mLastMotionX = x;
				if (!mScroller.isFinished()) {
					mScroller.abortAnimation();
				}
				mOriMotionX = x;
				mLastMotionX = x;
				mLastDownX = x;
				return true;
			case MotionEvent.ACTION_MOVE:
				System.out.println("====action move mScrollX="+mScrollX);
				final int buffer = getWidth() / 2; //这个表示在第一页或是最后一页还可以滑动半个屏幕
				//如果是往后滑动,屏幕向前,那么mLastMotionX是比x大的,deltaX是正的
				int deltaX = (int) (mLastMotionX - x);
				mLastMotionX = x;
				System.out.println("=====deltaX="+deltaX);
				//deltaX<0表示往前滑动
				if (deltaX < 0) {
					//这个是往右滑动,屏幕向左移动
					scrollBy(Math.max(-mScrollX - buffer, deltaX), 0);
				}else{
					int availableToScroll = 0;
					if (getChildCount() > 0) { //此时Workspace上可能未加任何item,count == 0
						System.out.println("====rihgt="+(getChildAt(
			                            getChildCount() - 1).getRight())+"avail="+(getChildAt(
			                            getChildCount() - 1).getRight()- mScrollX - getWidth()
			                            ));
						//getChildAt(getChildCount() - 1).getRight()为所有的view加一起的宽度,这里加了3个view,一个view为1080,则这个值为3240
					    availableToScroll = getChildAt(
			                            getChildCount() - 1).getRight()
			                            - mScrollX - getWidth();
					    //availableToScroll + buffer可以滑动的最大距离,deltax为滑动的距离
					    scrollBy(Math.min(availableToScroll + buffer, deltaX), 0);
					}
				}
				
			return true;
			case MotionEvent.ACTION_UP:
				final VelocityTracker velocityTracker = mVelocityTracker;

				velocityTracker.computeCurrentVelocity(DEFAULT_VALUE,
						mMaximumVelocity);
				int velocityX = (int) velocityTracker.getXVelocity();
				//velocityX为手指滑动的速率,我们会跟给定值SNAP_VELOCITY做比较
				if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
					//  这个时候是手指往前滑动,屏幕是向后移动
					snapToScreen(mCurrentScreen - 1);
				} else if (velocityX < -SNAP_VELOCITY
						&& mCurrentScreen < getChildCount() - 1) {
					//  move right
					snapToScreen(mCurrentScreen + 1);
				} else {
					snapToDestination(mLastMotionX < mOriMotionX);
				}
				if (mVelocityTracker != null) {
					mVelocityTracker.recycle();
					mVelocityTracker = null;
				}
				mTouchState = TOUCH_STATE_REST;
				if (Math.abs(mLastDownX - x) > 10) {
					return true;
				}
				return false;
			case MotionEvent.ACTION_CANCEL:
				mTouchState = TOUCH_STATE_REST;
				return false;
			default:
				break;
			}

		return true;
	}

/**滑动的距离,离屏宽几分之一时,就开始执行换屏动作。*/
	/**
	 * snapToDestination.
	 * mLastMotionX < mOriMotionX (mLastMotion < mOriMotionX)表示这个是手向后滑动,但屏幕是往前的,反之是向前
	 * forward为true为往前划动,这时将scrollX加上三分之二的屏幕的宽度
	 * scrollX / screenWidth 来决定当前在哪个屏幕
	 * @param forward 是前进还是后退.
	 */
	public void snapToDestination(boolean forward) {
		final int screenWidth = getWidth();
		int scrollX = getScrollX();

		if (forward) {
		    scrollX += screenWidth - screenWidth / 3;
		} else { 
		    scrollX += screenWidth / 3;
		}
		System.out.println("======screenWidth="+screenWidth+"scrollX / screenWidth="+(scrollX / screenWidth));
		snapToScreen(scrollX / screenWidth);
	}
	
	/**
	 * 如果计算要滑动的距离:(whichScreen * getWidth())为滑动后的X坐标,this.getScrollX()为当前的坐标,两者相减为滑动的距离
	 * Math.abs(delta) * 2为滑动的持续时间
	 */
	public void snapToScreen(int whichScreen) {
		whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
		boolean changingScreens = whichScreen != mCurrentScreen;

		mNextScreen = whichScreen;
		int mScrollX = this.getScrollX();
		final int newX = whichScreen * getWidth();
		final int delta = newX - mScrollX;
		System.out.println("====snapToScreen delta="+delta);
		mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
		//invalidate非常重要,不然你移动一点页面不能回复原状
		invalidate();
	}