Android高手进阶之ViewDragHelper使用详解以及拖动上下滑卡片实现

开发 前端
今天我们就来讲解下ViewDragHelper;ViewDragHelper是针对 ViewGroup 中的拖拽和重新定位 views 操作时提供了一系列非常有用的方法和状态追踪。

[[426553]]

前言

正好项目中有个页面底部拖动上下滑的UI;

今天我们就来讲解下ViewDragHelper;

这几天项目比较忙,文章更新会慢,各位老铁可以看历史记录;

一、viewDragHleper详解

ViewDragHelper是针对 ViewGroup 中的拖拽和重新定位 views 操作时提供了一系列非常有用的方法和状态追踪;

1、ViewDragHelper初始化

  1. public class ViewDragTest extends LinearLayout  { 
  2.     ViewDragHelper mViewDragHelper; 
  3.     @Override 
  4.     protected void onAttachedToWindow() { 
  5.         super.onAttachedToWindow(); 
  6.         //中间参数表示灵敏度,比如滑动了多少像素才视为触发了滑动.值越大越灵敏. 
  7.         mViewDragHelper = ViewDragHelper.create(this, 1f, new DragCallback()); 
  8.     } 
  9.     @Override 
  10.     public boolean onInterceptTouchEvent(MotionEvent ev) { 
  11.         //固定写法 
  12.         int action = MotionEventCompat.getActionMasked(ev); 
  13.         if (action == MotionEvent.ACTION_CANCEL  
  14.         || action == MotionEvent.ACTION_UP) { 
  15.             mViewDragHelper.cancel(); 
  16.             return false
  17.         } 
  18.         return mViewDragHelper.shouldInterceptTouchEvent(ev); 
  19.     } 
  20.     @Override 
  21.     public boolean onTouchEvent(MotionEvent event) { 
  22.         //固定写法 
  23.         mViewDragHelper.processTouchEvent(event); 
  24.         return true
  25.     } 
  26.     @Override 
  27.     public void computeScroll() { 
  28.         //固定写法 
  29.         //此方法用于自动滚动,比如自动回滚到默认位置. 
  30.         if (mViewDragHelper.continueSettling(true)) { 
  31.             ViewCompat.postInvalidateOnAnimation(this); 
  32.         } 
  33.     } 

2、ViewDragHelper.Callback

  1. //这个类的回调方法,才是ViewDragHelper的重点 
  2. private class ViewDragCallback extends ViewDragHelper.Callback{ 
  3.    @Override 
  4.    public boolean tryCaptureView(View child, int pointerId) { 
  5.        //child 表示想要滑动的view 
  6.        //pointerId 表示触摸点的id, 比如多点按压的那个id 
  7.        //返回值表示,是否可以capture,也就是是否可以滑动.可以根据不同的child决定是否可以滑动 
  8.        return true
  9.    } 
  10.    @Override 
  11.    public int clampViewPositionHorizontal(View child, int leftint dx) { 
  12.        //child 表示当前正在移动的view 
  13.        //left 表示当前的view正要移动到左边距为left的地方 
  14.        //dx 表示和上一次滑动的距离间隔 
  15.        //返回值就是child要移动的目标位置.可以通过控制返回值,从而控制child只能在ViewGroup的范围中移动. 
  16.        return left
  17.    } 
  18.    @Override 
  19.    public int clampViewPositionVertical(View child, int topint dy) { 
  20.        //child 表示当前正在移动的view 
  21.        //top 表示当前的view正要移动到上边距为top的地方 
  22.        //dx 表示和上一次滑动的距离间隔 
  23.        return top
  24.    } 

重写以上3个方法,可以正常工作了.子View就可以被任意拖动了;

3、控制child的移动范围在父view中

  1. //控制child只能在ViewGroup的横向中移动 
  2. @Override   
  3. public int clampViewPositionHorizontal(View child, int leftint dx) { 
  4.   final int leftBound = getPaddingLeft();   
  5.   final int rightBound = getWidth() - mDragView.getWidth();   
  6.   final int newLeft = Math.min(Math.max(left, leftBound), rightBound);   
  7.   return newLeft;   
  8. }  
  9. //控制child只能在ViewGroup的纵向中移动 
  10. @Override   
  11. public int clampViewPositionVertical(View child, int topint dy) {   
  12.   final int topBound = getPaddingTop();   
  13.   final int bottomBound = getHeight() - mDragView.getHeight();   
  14.   final int newTop = Math.min(Math.max(top, topBound), bottomBound);   
  15.   return newTop;   
  16. }  

4、开启边界滑动

  1. //开启4个边 
  2. mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL); 
  3. //各个边 
  4. public static final int EDGE_LEFT = 1 << 0; 
  5. public static final int EDGE_RIGHT = 1 << 1; 
  6. public static final int EDGE_TOP = 1 << 2; 
  7. public static final int EDGE_BOTTOM = 1 << 3; 
  8. //当开启边界滑动之后, 此方法就会回调 
  9. @Override 
  10. public void onEdgeTouched(int edgeFlags, int pointerId) { 
  11.     //通常开启边界之后, 都需要手动capture view.之后就可以滑动view了. 
  12.     mViewDragHelper.captureChildView(getChildAt(1), pointerId); 
  13. @Override 
  14. public boolean tryCaptureView(View child, int pointerId) { 
  15.     //开启边界之后, 这个方法的返回值可能需要进一步处理.要不然开边界就没啥意思了. 
  16.     return false

5、释放后的回弹效果

有些时候, 当释放的时候, 需要将View回到原来的位置;

  1. //释放的时候, 会回调下面的方法 
  2. @Override 
  3. public void onViewReleased(View releasedChild, float xvel, float yvel) { 
  4.     //调用这个方法,就可以设置releasedChild回弹得位置. 
  5.     mViewDragHelper.settleCapturedViewAt(0, 100);//参数就是x,y的坐标 
  6.     postInvalidate();//注意一定要调用这个方法,否则没效果. 
  7. //以下2个方法最终调用的都是forceSettleCapturedViewAt(). 
  8. mViewDragHelper.settleCapturedViewAt(0, 100); 
  9. mViewDragHelper.smoothSlideViewTo(getChildAt(1), 0, 100); 
  10. //所以...发挥你的想象力,看看有什么妙用!!! 
  11. //如果你还没有忘记的话...前文应该有说过,涉及到scroll,需要重写view的此方法. 
  12. //此方法一定要重写,否则没效果 
  13. @Override 
  14. public void computeScroll() { 
  15.   //固定写法 
  16.   if (mViewDragHelper.continueSettling(true)) { 
  17.       postInvalidate();//注意此处. 
  18.   } 
  19. 通过上面2个方法的设置, 当手指释放的时候, View就会自动滑动到指定的位置...(不是一下子就到指定的位置哦,有一个滑动的过程.) 
  20. 注意:如果需要滑动的View,会消耗touch事件,比如:Button,那么需要重写以下方法. 
  21. @Override 
  22. public int getViewHorizontalDragRange(View child) { 
  23.     return child.getMeasuredWidth();//只要返回大于0的值就行 
  24. @Override 
  25. public int getViewVerticalDragRange(View child) { 
  26.     return child.getMeasuredHeight();//只要返回大于0的值就行 

6、简单api介绍

ViewDragHelper的API

  1. ViewDragHelper create(ViewGroup forParent, Callback cb); 
  • 一个静态的创建方法,
  • 参数1:出入的是相应的ViewGroup
  • 参数2:是一个回掉
  1. shouldInterceptTouchEvent(MotionEvent ev)  
  • 处理事件分发的(主要是将ViewGroup的事件分发,委托给ViewDragHelper进行处理)
  • 参数1:MotionEvent ev 主要是ViewGroup的事件
  • processTouchEvent(MotionEvent event) 处理相应TouchEvent的方法,这里要注意一个问题,处理相应的TouchEvent的时候要将结果返回为true,消费本次事件!否则将无法使用ViewDragHelper处理相应的拖拽事件!

ViewDragHelper.Callback的API

  1. tryCaptureView(View child, int pointerId)  
  • 这是一个抽象类,必须去实现,也只有在这个方法返回true的时候下面的方法才会生效;
  • 参数1:捕获的View(也就是你拖动的这个View)
  1. onViewDragStateChanged(int state)  
  • 当状态改变的时候回调,返回相应的状态(这里有三种状态)
  • STATE_IDLE 闲置状态
  • STATE_DRAGGING 正在拖动
  • STATE_SETTLING 放置到某个位置
  1. onViewPositionChanged(View changedView, int leftint topint dx, int dy)  
  • 当你拖动的View位置发生改变的时候回调
  • 参数1:你当前拖动的这个View
  • 参数2:距离左边的距离
  • 参数3:距离右边的距离
  • 参数4:x轴的变化量
  • 参数5:y轴的变化量
  1. onViewCaptured(View capturedChild, int activePointerId) 
  • 捕获View的时候调用的方法
  • 参数1:捕获的View(也就是你拖动的这个View)
  1. onViewReleased(View releasedChild, float xvel, float yvel)  
  • 当View停止拖拽的时候调用的方法,一般在这个方法中重置一些参数,比如回弹什么的
  • 参数1:你拖拽的这个View
  • 参数2:x轴的速率
  • 参数3:y轴的速率
  1. clampViewPositionVertical(View child, int topint dy)  
  • 竖直拖拽的时候回调的方法
  • 参数1:拖拽的View
  • 参数2:距离顶部的距离
  • 参数3:变化量
  1. clampViewPositionHorizontal(View child, int leftint dx)  
  • 水平拖拽的时候回调的方法
  • 参数1:拖拽的View
  • 参数2:距离左边的距离
  • 参数3:变化量

二、简单的实现demo

下面是简单实现的demo,可以直接复制使用的

1、BottomView的ViewDragHelper实现

  1. public class BottomView extends LinearLayout { 
  2.     private ViewDragHelper mDragHelper; 
  3.     private View view
  4.     private int mDragBorder, verticalRange, mDragState, peekHeight, mDragHeight; 
  5.     private final double AUTO_OPEN_SPEED_LIMIT = 800.0; 
  6.     private boolean inflate = false, isExpanded = false, isDragHeightSet = false
  7.     private MotionEvent globalEvent; 
  8.     View try_view; 
  9.     public BottomView(Context context) { 
  10.         super(context); 
  11.     } 
  12.     public BottomView(Context context, @Nullable AttributeSet attrs) { 
  13.         super(context, attrs); 
  14.         initView(context, attrs); 
  15.     } 
  16.     public BottomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 
  17.         super(context, attrs, defStyleAttr); 
  18.         initView(context, attrs); 
  19.     } 
  20.     void initView(Context context, AttributeSet attrs) { 
  21.         peekHeight = 300; 
  22.     } 
  23.     @Override 
  24.     protected void onAttachedToWindow() { 
  25.         super.onAttachedToWindow(); 
  26.     } 
  27.     @Override 
  28.     protected void onFinishInflate() { 
  29.         super.onFinishInflate(); 
  30.         mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback()); 
  31.         view = getChildAt(0); 
  32.         try_view = findViewById(R.id.ll_try_view); 
  33.     } 
  34.     @Override 
  35.     protected void onLayout(boolean b, int leftint topint rightint bottom) { 
  36.         verticalRange = getMeasuredHeight() - peekHeight; 
  37.         if (!inflate) { 
  38.             mDragBorder = verticalRange; 
  39.             inflate = true
  40.         } 
  41.         view.layout(left, mDragBorder, right, bottom + mDragBorder); 
  42.     } 
  43.     @Override 
  44.     public boolean onInterceptTouchEvent(MotionEvent ev) { 
  45.         int action = MotionEventCompat.getActionMasked(ev); 
  46.         if ((action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) && !isDraggingAllowed(ev)) { 
  47.             mDragHelper.cancel(); 
  48.             return false
  49.         } 
  50.         return mDragHelper.shouldInterceptTouchEvent(ev); 
  51.     } 
  52.     @Override 
  53.     public boolean onTouchEvent(MotionEvent event) { 
  54.         if (isDraggingAllowed(event) || isMoving()) { 
  55.             mDragHelper.processTouchEvent(event); 
  56.             return true
  57.         } 
  58.         return super.onTouchEvent(event); 
  59.     } 
  60.     @Override 
  61.     public boolean dispatchTouchEvent(MotionEvent ev) { 
  62.         globalEvent = ev; 
  63.         return super.dispatchTouchEvent(ev); 
  64.     } 
  65.     boolean isDraggingAllowed(MotionEvent event) { 
  66.         int[] viewLocations = new int[2]; 
  67.         view.getLocationOnScreen(viewLocations); 
  68.         int upperLimit = viewLocations[1] + (isDragHeightSet ? mDragHeight : peekHeight); 
  69.         int lowerLimit = viewLocations[1]; 
  70.         int y = (int) event.getRawY(); 
  71.         return (y > lowerLimit && y < upperLimit); 
  72.     } 
  73.     boolean isMoving() { 
  74.         return (mDragState == ViewDragHelper.STATE_DRAGGING || 
  75.                 mDragState == ViewDragHelper.STATE_SETTLING); 
  76.     } 
  77.     class DragHelperCallback extends ViewDragHelper.Callback { 
  78.         @Override 
  79.         public boolean tryCaptureView(View child, int pointerId) { 
  80.             return true
  81.         } 
  82.         @Override 
  83.         public int clampViewPositionVertical(View child, int topint dy) { 
  84.             return top
  85.         } 
  86.         @Override 
  87.         public void onViewDragStateChanged(int state) { 
  88.             super.onViewDragStateChanged(state); 
  89.             mDragState = state; 
  90.         } 
  91.         @Override 
  92.         public int getViewVerticalDragRange(View child) { 
  93.             return verticalRange; 
  94.         } 
  95.         @Override 
  96.         public void onViewReleased(View releasedChild, float xvel, float yvel) { 
  97.             super.onViewReleased(releasedChild, xvel, yvel); 
  98.             boolean settleToOpen = false
  99.             if (yvel > AUTO_OPEN_SPEED_LIMIT && xvel < yvel) { 
  100.                 settleToOpen = true
  101.             } else if (yvel < -AUTO_OPEN_SPEED_LIMIT && xvel > yvel) { 
  102.                 settleToOpen = false
  103.             } else if (mDragBorder > (2 * verticalRange / 3)) { 
  104.                 settleToOpen = true
  105.             } else if (mDragBorder < (verticalRange / 3)) { 
  106.                 settleToOpen = false
  107.             } 
  108.             final int settleDestY = settleToOpen ? verticalRange : 0; 
  109.             isExpanded = settleToOpen ? false : true
  110.             if (mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), settleDestY)) { 
  111.                 ViewCompat.postInvalidateOnAnimation(BottomView.this); 
  112.             } 
  113.         } 
  114.         @Override 
  115.         public void onViewPositionChanged(View changedView, int leftint topint dx, int dy) { 
  116.             super.onViewPositionChanged(changedView, lefttop, dx, dy); 
  117.             mDragBorder = top < 0 ? 0 : top > verticalRange ? verticalRange : top
  118.             float offset = 1 - ((float) mDragBorder / verticalRange); 
  119.            // if (listener != null) listener.onDrag(offset); 
  120.             requestLayout(); 
  121.         } 
  122.     } 
  123.     @Override 
  124.     public void computeScroll() { 
  125.         super.computeScroll(); 
  126.         if (mDragHelper.continueSettling(true)) { 
  127.             ViewCompat.postInvalidateOnAnimation(this); 
  128.         } 
  129.     } 
  130.     public boolean expandOnTouchView() { 
  131.         if (isDraggingAllowed(globalEvent) && mDragHelper.smoothSlideViewTo(view, 0, 0)) { 
  132.             isExpanded = true
  133.             ViewCompat.postInvalidateOnAnimation(BottomView.this); 
  134.             return true
  135.         } 
  136.         return false
  137.     } 
  138.     public boolean expandView() { 
  139.         if (mDragHelper.smoothSlideViewTo(view, 0, 0)) { 
  140.             isExpanded = true
  141.             ViewCompat.postInvalidateOnAnimation(BottomView.this); 
  142.             return true
  143.         } 
  144.         return false
  145.     } 
  146.     public boolean collapseOnTouchView() { 
  147.         if (isDraggingAllowed(globalEvent) && mDragHelper.smoothSlideViewTo(view, 0, verticalRange)) { 
  148.             isExpanded = false
  149.             ViewCompat.postInvalidateOnAnimation(BottomView.this); 
  150.             return true
  151.         } 
  152.         return false
  153.     } 
  154.     public boolean collapseView() { 
  155.         if (mDragHelper.smoothSlideViewTo(view, 0, verticalRange)) { 
  156.             isExpanded = false
  157.             ViewCompat.postInvalidateOnAnimation(BottomView.this); 
  158.             return true
  159.         } 
  160.         return false
  161.     } 
  162.     public boolean isViewExpanded() { 
  163.         return isExpanded; 
  164.     } 
  165.     public void setPeekHeight(int peekHeight) { 
  166.         this.peekHeight = peekHeight; 
  167.         requestLayout(); 
  168.     } 
  169.     public void dragHeight(int mDragHeight) { 
  170.         isDragHeightSet = true
  171.         this.mDragHeight = mDragHeight; 
  172.     } 

2、布局文件

总结

面对不懂的知识点,不要害怕,勇敢面对;一起加油

本文转载自微信公众号「Android开发编程」

 

责任编辑:姜华 来源: Android开发编程
相关推荐

2021-09-09 06:55:43

AndroidViewDragHel原理

2021-08-23 06:27:46

AndroidctivitysetContentV

2021-07-14 14:27:01

AndroidAOPhugo

2021-08-25 07:43:17

AndroidSurfaceViewTextureView

2021-09-14 07:06:12

Android磁盘缓存

2021-09-12 07:30:10

配置

2021-08-09 20:29:27

Android沉浸式状态栏

2021-09-07 06:40:25

AndroidLiveData原理

2021-08-10 20:41:33

AndroidApp流程

2021-09-01 06:48:16

AndroidGlide缓存

2021-10-03 15:08:32

Android

2015-07-28 17:11:00

编程技术提升

2014-07-15 17:17:31

AdapterAndroid

2021-08-17 13:41:11

AndroidView事件

2021-09-02 07:00:01

Glide流程Android

2022-04-24 15:26:38

服务卡鸿蒙

2009-07-16 13:51:43

2021-08-11 17:15:17

AndroidActivity场景

2022-05-09 11:52:38

Java卡片服务卡片

2021-08-26 07:38:41

AndroidMediaPlayerTextureView
点赞
收藏

51CTO技术栈公众号