看效果图:
手指可以滑动图片切换到一个图片,这种放到App的帮助页面是很合适的,然后也适合图片轮播图等等场合
我们来看一下Activity中怎么调用这个控件:
public class SnapToScreenActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.snap_to_screen); int[] images = new int[]{R.drawable.help_01,R.drawable.help_02,R.drawable.help_03}; MyGroup myGroup = (MyGroup) findViewById(R.id.workspace); for (int i = 0; i < 3; i++) { final View item = getLayoutInflater().inflate( R.layout.introduction_item_test, null); myGroup.addView(item); final ImageView imageView = (ImageView) item .findViewById(R.id.introduction_image_view); try { imageView.setImageResource(images[i]); } catch (OutOfMemoryError e) { } } } }
MyGroup这个类明显是一个ViewGroup,为了让这个viewGroup可以支持手指切换,我们要重写
onLayout,dispatchDraw,computeScroll,onMeasure,onInterceptTouchEvent,onTouchEvent这些方法
重点在于对手指的事件处理,下面我们来看代码,如何算一次正常的手指滑动:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); 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; if (xMoved) { mTouchState = TOUCH_STATE_SCROLLING; } break; case MotionEvent.ACTION_DOWN: mLastMotionX = x; 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; } return mTouchState != TOUCH_STATE_REST; } @Override public boolean onTouchEvent(MotionEvent ev) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); int mScrollX = this.getScrollX(); 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; int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; System.out.println("=====deltaX="+deltaX); if (deltaX < 0) { scrollBy(Math.max(-mScrollX - buffer, deltaX), 0); }else{ int availableToScroll = 0; if (getChildCount() > 0) { System.out.println("====rihgt="+(getChildAt( getChildCount() - 1).getRight())+"avail="+(getChildAt( getChildCount() - 1).getRight()- mScrollX - getWidth() )); availableToScroll = getChildAt( getChildCount() - 1).getRight() - mScrollX - getWidth(); 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(); 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; }
onLayout是相对简单的,只是把子view布局一下就行
@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); childLeft += child.getMeasuredWidth(); } } }
重点在于
childLeft += child.getMeasuredWidth();
这一句,使得每个字view都并排排列
然后使用dispatchDraw()绘制每个view
@Override protected void dispatchDraw(Canvas canvas) { int childCount = getChildCount(); if (childCount == 0) { return; } boolean restore = false; int restoreCount = 0; final long drawingTime = getDrawingTime(); final float scrollPos = (float) getScrollX() / getWidth(); final int leftScreen = (int) scrollPos; final int rightScreen = leftScreen + 1; if (leftScreen >= 0 && leftScreen < childCount) { drawChild(canvas, getChildAt(leftScreen), drawingTime); } if (rightScreen < getChildCount()) { drawChild(canvas, getChildAt(rightScreen), drawingTime); } if (restore) { canvas.restoreToCount(restoreCount); } }
这个类的完整代码我也贴出来:
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 = 1000; private int mNextScreen = -1; private static final int SNAP_VELOCITY = 700; private int mCurrentScreen; public MyGroup(Context context, AttributeSet attrs) { super(context, attrs); initWorkspace(); } private void initWorkspace() { mScroller = new Scroller(getContext()); setCurrentScreen(0); final ViewConfiguration configuration = ViewConfiguration .get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); } @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); childLeft += child.getMeasuredWidth(); } } } @Override protected void dispatchDraw(Canvas canvas) { int childCount = getChildCount(); if (childCount == 0) { return; } boolean restore = false; int restoreCount = 0; final long drawingTime = getDrawingTime(); final float scrollPos = (float) getScrollX() / getWidth(); final int leftScreen = (int) scrollPos; final int rightScreen = leftScreen + 1; if (leftScreen >= 0 && leftScreen < childCount) { drawChild(canvas, getChildAt(leftScreen), drawingTime); } if (rightScreen < getChildCount()) { drawChild(canvas, getChildAt(rightScreen), drawingTime); } if (restore) { canvas.restoreToCount(restoreCount); } } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } else if (mNextScreen != -1) { setCurrentScreen(Math.max(0, Math.min(mNextScreen, getChildCount() - 1))); mNextScreen = -1; // if (mListener != null) { // mListener.onViewChanged(mCurrentScreen); // } } } void setCurrentScreen(int index) { mCurrentScreen = index; resetVisibilityForChildren(); } private void resetVisibilityForChildren() { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (Math.abs(mCurrentScreen - i) <= 0) { child.setVisibility(View.VISIBLE); } else { child.setVisibility(View.INVISIBLE); } } } @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); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); 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; if (xMoved) { mTouchState = TOUCH_STATE_SCROLLING; } break; case MotionEvent.ACTION_DOWN: mLastMotionX = x; 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; } return mTouchState != TOUCH_STATE_REST; } @Override public boolean onTouchEvent(MotionEvent ev) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); int mScrollX = this.getScrollX(); 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; int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; System.out.println("=====deltaX="+deltaX); if (deltaX < 0) { scrollBy(Math.max(-mScrollX - buffer, deltaX), 0); }else{ int availableToScroll = 0; if (getChildCount() > 0) { System.out.println("====rihgt="+(getChildAt( getChildCount() - 1).getRight())+"avail="+(getChildAt( getChildCount() - 1).getRight()- mScrollX - getWidth() )); availableToScroll = getChildAt( getChildCount() - 1).getRight() - mScrollX - getWidth(); 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(); 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; } 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); } 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(); } }