public class MyView extends ViewGroup { private ViewDragHelper mDragger; //被滑动的view private View mDragView; //边缘是否被触摸 private boolean isEdgeTouched; //监听滑动速度 private VelocityTracker vTracker = null; //是否打开 private boolean isOpen; //自定义属性,可以打开的距离 float cap; public MyView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.MyView_cap: //获得可以打开的距离,转化为px单位 cap = a.getDimensionPixelSize(attr, 250); } } a.recycle(); mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { //只有边缘被触摸,才返回滑动的view if (isEdgeTouched) return mDragView == child; else return false; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { final int leftBound = getPaddingLeft(); return Math.min(Math.max(left, leftBound), getWidth() - (int) cap); } @Override public void onEdgeDragStarted(int edgeFlags, int pointerId) { isEdgeTouched = true; mDragger.captureChildView(mDragView, pointerId); } //手指释放的时候回调 @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { if (releasedChild == mDragView) { //如果正向滑动,没有打开并且速度大于每秒5,直接打开view。 if (xvel > 5 && !isOpen) { //这里以自定义属性gap为打开的距离 mDragger.settleCapturedViewAt(getWidth() - (int) cap, getTop()); invalidate(); isOpen = true; return; } //如果负向滑动,已经打开并且速度大于每秒5,直接关闭view。 if (xvel < -5 && isOpen) { mDragger.settleCapturedViewAt(0, getTop()); isEdgeTouched = false; invalidate(); isOpen = false; return; } //判断滑动的距离是否超过最大距离的一半,有则打开 if (mDragView.getLeft() > (getWidth() - cap) / 2) { mDragger.settleCapturedViewAt(getWidth() - (int) cap, getTop()); invalidate(); isOpen = true; return; } //判断滑动的距离是否小于等于最大距离的一半,是则关闭 if (mDragView.getLeft() <= (getWidth() - cap) / 2) { mDragger.settleCapturedViewAt(0, getTop()); isEdgeTouched = false; invalidate(); isOpen = false; return; } } } }); mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = this.getChildCount(); for (int i = 0; i < count; i++) { View child = this.getChildAt(i); child.measure(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { for (int i = 0, size = getChildCount(); i < size; i++) { if (i == 0) { View view = getChildAt(i); view.layout(left, top, right - (int) cap, bottom); } else { View view = getChildAt(i); view.layout(left, top, right, bottom); mDragView = view; } } } @Override public boolean onInterceptTouchEvent(MotionEvent event) { //如果view打开了,则当前view处理滑动事件,也就是说view打开后可以通过手势滑回去,把关注点放在打开的view上 if (isOpen) { return true; } else return mDragger.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { if (vTracker == null) { vTracker = VelocityTracker.obtain(); } else { vTracker.clear(); } //监听滑动速度,以每秒多少个像素为单位 vTracker.addMovement(event); vTracker.computeCurrentVelocity(1000);//1000指1000毫秒 mDragger.processTouchEvent(event); return true; } @Override public void computeScroll() { if (mDragger.continueSettling(true)) { invalidate(); } } }
关于从边测划出控件
最近使用滴滴打车,因为要打印发票才发现原来滴滴打车还有个侧边栏,不过不能划开,需要点击打开。估计是因为和里面的地图冲突,所以才这么设计的。于是自己实现了个小的控件去进行了改善。主要思想就是监听滑动事件,如果是在边上则当前的view处理,如果超过了边缘则传递给子view去处理。后面又发现了viewDragerhelper这个东东,于是更简单了~