在android上开发一个左右侧滑的组件,需要先了解以下知识
Android中View绘制流程以及invalidate()等相关方法分析
使用Scroller实现横滑
android事件传递机制
先上图,看看demo
左边view显示:
右边view显示
源码如下
package com.wan.ui.view; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.Scroller; public class FlipperLayout extends ViewGroup { private final String TAG = "FlipperLayout"; private final int LEFT_WIDTH = 54; private Scroller mScroller; private VelocityTracker mVelocityTracker; private int mWidth; public static final int SCREEN_STATE_CLOSE = 0; public static final int SCREEN_STATE_LEFT_OPEN = 1; public static final int SCREEN_STATE_RIGHT_OPEN = 2; public static final int SCREEN_STATE_LEFT_CLOSE = 3; public static final int SCREEN_STATE_RIGHT_CLOSE = 4; public static final int TOUCH_STATE_RESTART = 0; public static final int TOUCH_STATE_SCROLLING = 1; public static final int SCROLL_STATE_NO_ALLOW = 0; public static final int SCROLL_STATE_ALLOW = 1; // 页面移动的状态 private int mScreenState = SCREEN_STATE_CLOSE; //标记Scroller 是否正在滚动 private int mTouchState = TOUCH_STATE_RESTART; // 是否允许横滑,这个参数很重要,如果mScrollState ==SCROLL_STATE_NO_ALLOW 则横滑的事件不成立,取消 private int mScrollState = SCROLL_STATE_NO_ALLOW; // 即将触发的事件 private int mWillState = SCREEN_STATE_CLOSE; //滚动的速度 private int mVelocityValue = 0; //是否产生点击事件,在lelfView或者rightView出现的时候,mOnClick或许=true private boolean mOnClick = false; private View mLeftView, mRightView, mMasterView; private float mLastMotionX, mLastMotionY; // 触发横滑的临界值 private int mTouchSlop; public FlipperLayout(Context context) { super(context); initData(context); } public FlipperLayout(Context context, AttributeSet attrs) { super(context, attrs); initData(context); } public FlipperLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initData(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width, height); for (int i = 0, j = getChildCount(); i < j; i++) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); } } public void setLeftView(View leftView, LayoutParams params) { if (mLeftView == null) { this.mLeftView = leftView; addView(mLeftView, 0, params); } } public void setRightView(View rightView, LayoutParams params) { if (mRightView == null) { this.mRightView = rightView; addView(mRightView, 1, params); } } public void setMasterView(View masterView, LayoutParams params) { if (mMasterView == null) { this.mMasterView = masterView; addView(mMasterView, 2, params); } } private void initData(Context context) { mScroller = new Scroller(context); mWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, LEFT_WIDTH, getResources().getDisplayMetrics()); mTouchSlop = ViewConfiguration.get(getContext()).getScaledEdgeSlop(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mLeftView.layout(0, 0, mLeftView.getMeasuredWidth(), mLeftView.getMeasuredHeight()); mMasterView.layout(0, 0, mMasterView.getMeasuredWidth(), mMasterView.getMeasuredHeight()); mRightView.layout(0, 0, mRightView.getMeasuredWidth(), mRightView.getMeasuredHeight()); } @Override public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { mMasterView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } // 分发事件 @Override public boolean dispatchTouchEvent(MotionEvent ev) { obtainVelocityTracker(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTART : TOUCH_STATE_SCROLLING; if (mTouchState == TOUCH_STATE_RESTART) { int x = (int) ev.getX(); int screenWidth = getWidth(); mScrollState = SCROLL_STATE_NO_ALLOW; // to close left 当lelfView 可见,且x > screenWidth - mWidth ,则有可能触发关闭lelfView if (mScreenState == SCREEN_STATE_LEFT_OPEN && x >= screenWidth - mWidth) { mScrollState = SCROLL_STATE_ALLOW; mOnClick = true; } // to close right 当rightView可见,且x <= mWidth ,则有可能触发关闭rightView if (mScreenState == SCREEN_STATE_RIGHT_OPEN && x <= mWidth) { mScrollState = SCROLL_STATE_ALLOW; mOnClick = true; } } else return false; break; case MotionEvent.ACTION_MOVE: // to close left if (mScrollState == SCROLL_STATE_ALLOW && getWidth() - (int) ev.getX() < mWidth && mScreenState == SCREEN_STATE_LEFT_OPEN) { mWillState = SCREEN_STATE_LEFT_CLOSE; return true; } // to close right if (mScrollState == SCROLL_STATE_ALLOW && ev.getX() < mWidth && mScreenState == SCREEN_STATE_RIGHT_OPEN) { mWillState = SCREEN_STATE_RIGHT_CLOSE; return true; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: releaseVelocityTracker(); break; default: break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); obtainVelocityTracker(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastMotionX = x; mLastMotionY = y; mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTART : TOUCH_STATE_SCROLLING; if (mTouchState == TOUCH_STATE_SCROLLING) { return false; } break; case MotionEvent.ACTION_MOVE: final float dx = x - mLastMotionX; if (mScrollState == SCROLL_STATE_NO_ALLOW) { final float xDiff = Math.abs(dx); final float yDiff = Math.abs(y - mLastMotionY); mLastMotionX = x; mLastMotionY = y; if (xDiff > mTouchSlop && xDiff > yDiff) { // 当横向移动的距离大于竖向移动的距离,并且xDiff>mTouchSlop(移动的边缘数据),则可能触发横滑 if (mScrollState == SCROLL_STATE_NO_ALLOW) if (mScreenState == SCREEN_STATE_CLOSE) { mScrollState = SCROLL_STATE_ALLOW; Log.i(TAG, "scroll state allow"); return false; } } } // 判断横滑的方向,通过移动方向来设置leftView 和rightView的显示 mOnClick = false; mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity()); if (mScrollState == SCROLL_STATE_ALLOW && Math.abs(mVelocityTracker.getXVelocity()) > 200) { Log.i(TAG, "scroll state allow 2"); if (dx > 0 && mScreenState == SCREEN_STATE_CLOSE) { // 向右移,即将打开leftView if (mRightView.getVisibility() == View.VISIBLE) mRightView.setVisibility(View.INVISIBLE); if (mLeftView.getVisibility() == View.INVISIBLE) mLeftView.setVisibility(View.VISIBLE); mWillState = SCREEN_STATE_LEFT_OPEN; } else if (dx < 0 && mScreenState == SCREEN_STATE_CLOSE) { // 向左移,即将打开rightView if (mLeftView.getVisibility() == View.VISIBLE) mLeftView.setVisibility(View.INVISIBLE); if (mRightView.getVisibility() == View.INVISIBLE) mRightView.setVisibility(View.VISIBLE); mWillState = SCREEN_STATE_RIGHT_OPEN; } return true; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: releaseVelocityTracker(); break; default: break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { final float x = ev.getX(); obtainVelocityTracker(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastMotionX = x; mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTART : TOUCH_STATE_SCROLLING; if (mTouchState == TOUCH_STATE_SCROLLING) { return false; } break; case MotionEvent.ACTION_MOVE: if (!mOnClick) { final float deltaX = x - mLastMotionX; mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity()); mVelocityValue = (int) mVelocityTracker.getXVelocity(); mMasterView.scrollBy(-(int) deltaX, 0); //通过这句话,让View跟着手指移动 mLastMotionX = x; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // ACTION_UP 和ACTION_CANCEL 的时候,通过之前mWillState的值和mScrollState,处理最后view的滚动效果。 if (mOnClick) { if (mScreenState == SCREEN_STATE_LEFT_OPEN) closeLeftView(); else if (mScreenState == SCREEN_STATE_RIGHT_OPEN) closeRightView(); mOnClick = false; releaseVelocityTracker(); return super.onTouchEvent(ev); } if (mScrollState == SCROLL_STATE_ALLOW) { if (mWillState == SCREEN_STATE_LEFT_OPEN && mScreenState == SCREEN_STATE_CLOSE) { if (mVelocityValue > 2000) openLeftView(200); else if (ev.getX() > getWidth() / 2) openLeftView(); else closeLeftView(); } else if (mWillState == SCREEN_STATE_RIGHT_OPEN && mScreenState == SCREEN_STATE_CLOSE) { if (mVelocityValue < -2000) openRightView(200); else if (ev.getX() < getWidth() / 2) openRightView(); else closeRightView(); } else if (mWillState == SCREEN_STATE_LEFT_CLOSE && mScreenState == SCREEN_STATE_LEFT_OPEN) { if (mVelocityValue < -2000) closeLeftView(200); else if (ev.getX() < getWidth() / 2) closeLeftView(); else openLeftView(); } else if (mWillState == SCREEN_STATE_RIGHT_CLOSE && mScreenState == SCREEN_STATE_RIGHT_OPEN) { if (mVelocityValue > 2000) closeRightView(200); else if (ev.getX() > getWidth() / 2) closeRightView(); else openRightView(); } else { Log.i(TAG, "up other "); if (mMasterView.getScrollX() < 0) closeLeftView(); else closeRightView(); } } releaseVelocityTracker(); break; default: break; } return super.onTouchEvent(ev); } private void obtainVelocityTracker(MotionEvent event) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } private void releaseVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } private void openLeftView(int duration) { mLeftView.setVisibility(View.VISIBLE); mRightView.setVisibility(View.INVISIBLE); mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTART : TOUCH_STATE_SCROLLING; if (mTouchState == TOUCH_STATE_RESTART) { mScreenState = SCREEN_STATE_LEFT_OPEN; Log.i(TAG, "open left view"); mScroller.startScroll(mMasterView.getScrollX(), 0, -(getWidth() - mWidth - Math.abs(mMasterView.getScrollX())), 0, duration); invalidate(); } } public void openLeftView() { openLeftView(800); } private void closeLeftView(int duration) { Log.i(TAG, "close left view"); mScreenState = SCREEN_STATE_CLOSE; mScroller.startScroll(mMasterView.getScrollX(), 0, -mMasterView.getScrollX(), 0, duration); invalidate(); } public void closeLeftView() { closeLeftView(800); } public void openRightView(int duration) { mLeftView.setVisibility(View.INVISIBLE); mRightView.setVisibility(View.VISIBLE); mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTART : TOUCH_STATE_SCROLLING; if (mTouchState == TOUCH_STATE_RESTART) { Log.i(TAG, "open right view"); mScreenState = SCREEN_STATE_RIGHT_OPEN; mScroller.startScroll(mMasterView.getScrollX(), 0, (getWidth() - mMasterView.getScrollX() - mWidth), 0, duration); invalidate(); } } public void openRightView() { openRightView(800); } public void closeRightView(int duration) { Log.i(TAG, "close right view"); mScreenState = SCREEN_STATE_CLOSE; mScroller.startScroll(mMasterView.getScrollX(), 0, -mMasterView.getScrollX(), 0, duration); invalidate(); } public void closeRightView() { closeRightView(800); } public int getLeftWidth() { return mWidth; } }
源码的demo地址如下:Android 左右侧滑组件