
前言
正好项目中有个页面底部拖动上下滑的手进使用实现UI;
今天我们就来讲解下ViewDragHelper;
这几天项目比较忙,文章更新会慢,阶之及拖各位老铁可以看历史记录;
一、详解下滑viewDragHleper详解
ViewDragHelper是动上针对 ViewGroup 中的拖拽和重新定位 views 操作时提供了一系列非常有用的方法和状态追踪;
1、ViewDragHelper初始化
public class ViewDragTest extends LinearLayout  {     ViewDragHelper mViewDragHelper;     @Override     protected void onAttachedToWindow() {         super.onAttachedToWindow();         //中间参数表示灵敏度,卡片比如滑动了多少像素才视为触发了滑动.值越大越灵敏.         mViewDragHelper = ViewDragHelper.create(this, 1f, new DragCallback());     }     @Override     public boolean onInterceptTouchEvent(MotionEvent ev) {         //固定写法         int action = MotionEventCompat.getActionMasked(ev);         if (action == MotionEvent.ACTION_CANCEL          || action == MotionEvent.ACTION_UP) {             mViewDragHelper.cancel();             return false;         }         return mViewDragHelper.shouldInterceptTouchEvent(ev);     }     @Override     public boolean onTouchEvent(MotionEvent event) {         //固定写法         mViewDragHelper.processTouchEvent(event);         return true;     }     @Override     public void computeScroll() {         //固定写法         //此方法用于自动滚动,比如自动回滚到默认位置.         if (mViewDragHelper.continueSettling(true)) {             ViewCompat.postInvalidateOnAnimation(this);         }     } }         
2、ViewDragHelper.Callback
//这个类的手进使用实现回调方法,才是ViewDragHelper的重点 private class ViewDragCallback extends ViewDragHelper.Callback{    @Override    public boolean tryCaptureView(View child, int pointerId) {        //child 表示想要滑动的view        //pointerId 表示触摸点的id, 比如多点按压的那个id        //返回值表示,是否可以capture,也就是是否可以滑动.可以根据不同的child决定是否可以滑动        return true;    }    @Override    public int clampViewPositionHorizontal(View child, int left, int dx) {        //child 表示当前正在移动的view        //left 表示当前的view正要移动到左边距为left的地方        //dx 表示和上一次滑动的距离间隔        //返回值就是child要移动的目标位置.可以通过控制返回值,从而控制child只能在ViewGroup的范围中移动.        return left;    }    @Override    public int clampViewPositionVertical(View child, int top, int dy) {        //child 表示当前正在移动的view        //top 表示当前的view正要移动到上边距为top的地方        //dx 表示和上一次滑动的距离间隔        return top;    } }         
重写以上3个方法,可以正常工作了.子View就可以被任意拖动了;
3、控制child的阶之及拖移动范围在父view中
//控制child只能在ViewGroup的
云服务器横向中移动 @Override   public int clampViewPositionHorizontal(View child, int left, int dx) {   final int leftBound = getPaddingLeft();     final int rightBound = getWidth() - mDragView.getWidth();     final int newLeft = Math.min(Math.max(left, leftBound), rightBound);     return newLeft;   }  //控制child只能在ViewGroup的纵向中移动 @Override   public int clampViewPositionVertical(View child, int top, int dy) {     final int topBound = getPaddingTop();     final int bottomBound = getHeight() - mDragView.getHeight();     final int newTop = Math.min(Math.max(top, topBound), bottomBound);     return newTop;   }          
4、开启边界滑动
//开启4个边 mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL); //各个边 public static final int EDGE_LEFT = 1 << 0; public static final int EDGE_RIGHT = 1 << 1; public static final int EDGE_TOP = 1 << 2; public static final int EDGE_BOTTOM = 1 << 3; //当开启边界滑动之后,详解下滑 此方法就会回调 @Override public void onEdgeTouched(int edgeFlags, int pointerId) {     //通常开启边界之后, 都需要手动capture view.之后就可以滑动view了.     mViewDragHelper.captureChildView(getChildAt(1), pointerId); } @Override public boolean tryCaptureView(View child, int pointerId) {     //开启边界之后, 这个方法的返回值可能需要进一步处理.要不然开边界就没啥意思了.     return false; }         
5、释放后的动上回弹效果
有些时候, 当释放的时候, 需要将View回到原来的位置;
//释放的时候, 会回调下面的方法 @Override public void onViewReleased(View releasedChild, float xvel, float yvel) {     //调用这个方法,就可以设置releasedChild回弹得位置.     mViewDragHelper.settleCapturedViewAt(0, 100);//参数就是x,y的坐标     postInvalidate();//注意一定要调用这个方法,否则没效果. } //以下2个方法最终调用的都是forceSettleCapturedViewAt(). mViewDragHelper.settleCapturedViewAt(0, 100); mViewDragHelper.smoothSlideViewTo(getChildAt(1), 0, 100); //所以...发挥你的想象力,看看有什么妙用!!! //如果你还没有忘记的话...前文应该有说过,涉及到scroll,需要重写view的此方法. //此方法一定要重写,否则没效果 @Override public void computeScroll() {   //固定写法   if (mViewDragHelper.continueSettling(true)) {       postInvalidate();//注意此处.   } } 通过上面2个方法的设置, 当手指释放的时候, View就会自动滑动到指定的位置...(不是一下子就到指定的位置哦,有一个滑动的过程.) 注意:如果需要滑动的View,会消耗touch事件,比如:Button,那么需要重写以下方法. @Override public int getViewHorizontalDragRange(View child) {     return child.getMeasuredWidth();//只要返回大于0的值就行 } @Override public int getViewVerticalDragRange(View child) {     return child.getMeasuredHeight();//只要返回大于0的值就行 }         
6、简单api介绍
ViewDragHelper的卡片API
ViewDragHelper create(ViewGroup forParent, Callback cb);         一个静态的创建方法,        参数1:出入的手进使用实现是相应的ViewGroup        参数2:是
高防服务器一个回掉        shouldInterceptTouchEvent(MotionEvent ev)          处理事件分发的(主要是将ViewGroup的事件分发,委托给ViewDragHelper进行处理)        参数1:MotionEvent ev 主要是阶之及拖ViewGroup的事件        processTouchEvent(MotionEvent event) 处理相应TouchEvent的方法,这里要注意一个问题,详解下滑处理相应的动上TouchEvent的时候要将结果返回为true,消费本次事件!否则将无法使用ViewDragHelper处理相应的卡片拖拽事件!        
ViewDragHelper.Callback的API
tryCaptureView(View child, int pointerId)          这是一个抽象类,必须去实现,也只有在这个方法返回true的时候下面的方法才会生效;        参数1:捕获的View(也就是你拖动的这个View)        onViewDragStateChanged(int state)          当状态改变的时候回调,返回相应的状态(这里有三种状态)        STATE_IDLE 闲置状态        STATE_DRAGGING 正在拖动        STATE_SETTLING 放置到某个位置        onViewPositionChanged(View changedView, int left, int top, int dx, int dy)          当你拖动的View位置发生改变的时候回调        参数1:你当前拖动的这个View        参数2:距离左边的距离        参数3:距离右边的距离        参数4:x轴的变化量        参数5:y轴的变化量        onViewCaptured(View capturedChild, int activePointerId)         捕获View的
免费源码下载时候调用的方法        参数1:捕获的View(也就是你拖动的这个View)        onViewReleased(View releasedChild, float xvel, float yvel)          当View停止拖拽的时候调用的方法,一般在这个方法中重置一些参数,比如回弹什么的        参数1:你拖拽的这个View        参数2:x轴的速率        参数3:y轴的速率        clampViewPositionVertical(View child, int top, int dy)          竖直拖拽的时候回调的方法        参数1:拖拽的View        参数2:距离顶部的距离        参数3:变化量        clampViewPositionHorizontal(View child, int left, int dx)          水平拖拽的时候回调的方法        参数1:拖拽的View        参数2:距离左边的距离        参数3:变化量        
二、简单的实现demo
下面是简单实现的demo,可以直接复制使用的

1、BottomView的ViewDragHelper实现
public class BottomView extends LinearLayout {     private ViewDragHelper mDragHelper;     private View view;     private int mDragBorder, verticalRange, mDragState, peekHeight, mDragHeight;     private final double AUTO_OPEN_SPEED_LIMIT = 800.0;     private boolean inflate = false, isExpanded = false, isDragHeightSet = false;     private MotionEvent globalEvent;     View try_view;     public BottomView(Context context) {         super(context);     }     public BottomView(Context context, @Nullable AttributeSet attrs) {         super(context, attrs);         initView(context, attrs);     }     public BottomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {         super(context, attrs, defStyleAttr);         initView(context, attrs);     }     void initView(Context context, AttributeSet attrs) {         peekHeight = 300;     }     @Override     protected void onAttachedToWindow() {         super.onAttachedToWindow();     }     @Override     protected void onFinishInflate() {         super.onFinishInflate();         mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());         view = getChildAt(0);         try_view = findViewById(R.id.ll_try_view);     }     @Override     protected void onLayout(boolean b, int left, int top, int right, int bottom) {         verticalRange = getMeasuredHeight() - peekHeight;         if (!inflate) {             mDragBorder = verticalRange;             inflate = true;         }         view.layout(left, mDragBorder, right, bottom + mDragBorder);     }     @Override     public boolean onInterceptTouchEvent(MotionEvent ev) {         int action = MotionEventCompat.getActionMasked(ev);         if ((action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) && !isDraggingAllowed(ev)) {             mDragHelper.cancel();             return false;         }         return mDragHelper.shouldInterceptTouchEvent(ev);     }     @Override     public boolean onTouchEvent(MotionEvent event) {         if (isDraggingAllowed(event) || isMoving()) {             mDragHelper.processTouchEvent(event);             return true;         }         return super.onTouchEvent(event);     }     @Override     public boolean dispatchTouchEvent(MotionEvent ev) {         globalEvent = ev;         return super.dispatchTouchEvent(ev);     }     boolean isDraggingAllowed(MotionEvent event) {         int[] viewLocations = new int[2];         view.getLocationOnScreen(viewLocations);         int upperLimit = viewLocations[1] + (isDragHeightSet ? mDragHeight : peekHeight);         int lowerLimit = viewLocations[1];         int y = (int) event.getRawY();         return (y > lowerLimit && y < upperLimit);     }     boolean isMoving() {         return (mDragState == ViewDragHelper.STATE_DRAGGING ||                 mDragState == ViewDragHelper.STATE_SETTLING);     }     class DragHelperCallback extends ViewDragHelper.Callback {         @Override         public boolean tryCaptureView(View child, int pointerId) {             return true;         }         @Override         public int clampViewPositionVertical(View child, int top, int dy) {             return top;         }         @Override         public void onViewDragStateChanged(int state) {             super.onViewDragStateChanged(state);             mDragState = state;         }         @Override         public int getViewVerticalDragRange(View child) {             return verticalRange;         }         @Override         public void onViewReleased(View releasedChild, float xvel, float yvel) {             super.onViewReleased(releasedChild, xvel, yvel);             boolean settleToOpen = false;             if (yvel > AUTO_OPEN_SPEED_LIMIT && xvel < yvel) {                 settleToOpen = true;             } else if (yvel < -AUTO_OPEN_SPEED_LIMIT && xvel > yvel) {                 settleToOpen = false;             } else if (mDragBorder > (2 * verticalRange / 3)) {                 settleToOpen = true;             } else if (mDragBorder < (verticalRange / 3)) {                 settleToOpen = false;             }             final int settleDestY = settleToOpen ? verticalRange : 0;             isExpanded = settleToOpen ? false : true;             if (mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), settleDestY)) {                 ViewCompat.postInvalidateOnAnimation(BottomView.this);             }         }         @Override         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {             super.onViewPositionChanged(changedView, left, top, dx, dy);             mDragBorder = top < 0 ? 0 : top > verticalRange ? verticalRange : top;             float offset = 1 - ((float) mDragBorder / verticalRange);            // if (listener != null) listener.onDrag(offset);             requestLayout();         }     }     @Override     public void computeScroll() {         super.computeScroll();         if (mDragHelper.continueSettling(true)) {             ViewCompat.postInvalidateOnAnimation(this);         }     }     public boolean expandOnTouchView() {         if (isDraggingAllowed(globalEvent) && mDragHelper.smoothSlideViewTo(view, 0, 0)) {             isExpanded = true;             ViewCompat.postInvalidateOnAnimation(BottomView.this);             return true;         }         return false;     }     public boolean expandView() {         if (mDragHelper.smoothSlideViewTo(view, 0, 0)) {             isExpanded = true;             ViewCompat.postInvalidateOnAnimation(BottomView.this);             return true;         }         return false;     }     public boolean collapseOnTouchView() {         if (isDraggingAllowed(globalEvent) && mDragHelper.smoothSlideViewTo(view, 0, verticalRange)) {             isExpanded = false;             ViewCompat.postInvalidateOnAnimation(BottomView.this);             return true;         }         return false;     }     public boolean collapseView() {         if (mDragHelper.smoothSlideViewTo(view, 0, verticalRange)) {             isExpanded = false;             ViewCompat.postInvalidateOnAnimation(BottomView.this);             return true;         }         return false;     }     public boolean isViewExpanded() {         return isExpanded;     }     public void setPeekHeight(int peekHeight) {         this.peekHeight = peekHeight;         requestLayout();     }     public void dragHeight(int mDragHeight) {         isDragHeightSet = true;         this.mDragHeight = mDragHeight;     } }         

2、布局文件
总结
面对不懂的知识点,不要害怕,勇敢面对;一起加油
本文转载自微信公众号「Android开发编程」
