在android上开发一个左右侧滑的组件,需要先了解以下知识
Android中View绘制流程以及invalidate()等相关方法分析
先上图,看看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 左右侧滑组件