Android使用ViewDragHelper实现图片下拽返回示例

作者:Frank Lee 时间:2023-08-08 00:14:12 

微信的图片下拽返回功能在日常使用中非常方便,同时在很多 App 中都见到了类似的设计,可以说一旦习惯这种操作再也回不去了。

这几天逛 GitHub,发现一个很赞的库 https://github.com/iielse/ImageWatcher 高度还原了微信的效果,粗看了下源码,我觉得可以更简单的实现类似的效果,动手实现后,发现 ViewDragHelper + ActvitySceneTransition 的方案简单粗暴,废话不说,先看效果:

Android使用ViewDragHelper实现图片下拽返回示例

什么是 ViewDragHelper

具体实现之前先简单介绍下什么是 ViewDragHelper。

ViewDragHelper 是 support v4 兼容包中的一个工具类,用来简化自定义 ViewGroup 中的手势处理。

使用 ViewDragHelper 可以轻松实现 ViewGroup 里 View 的拖拽操作,这里介绍下使用 ViewDragHelper 里几个重要步骤。

初始化

通过静态方法创建:viewGroup 即为当前容器;sensitivity 为拖拽的灵敏度,默认为 1;callback 为配置拖拽中的各种逻辑处理。


mViewDragHelper = ViewDragHelper.create(viewGroup, callback);
...
or
...
mViewDragHelper = ViewDragHelper.create(viewGroup, sensitivity, callback);

Callback

这里仅列出我们需要使用到的一些回调方法:


public abstract static class Callback {
   /**
    * 当子 View 被拖动改变位置时回调此方法
    *
    * @param changedView 当前子 View
    * @param left 当前子 View 的最新 X 坐标
    * @param top 当前子 View 的最新 Y 坐标
    * @param dx 当前子 View 的最新 X 坐标较上次 X 的位移量
    * @param dy 当前子 View 的最新 Y 坐标较上次 Y 的位移量
    */
   public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,
       int dy) {
   }

/**
    * 当子 View 被释放后回调此方法
    *
    * @param releasedChild 当前子 View
    * @param xvel X 子 View 被释放时,用户手指在屏幕上滑动的横向加速度
    * @param yvel Y 子 View 被释放时,用户手指在屏幕上滑动的纵向加速度
    */
   public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {}

/**
    * 限制子 View 水平拖拽范围。
    *
    * 如果返回 0,则不能进行水平拖动,如果要实现拖拽,返回值 > 0 即可。
    *
    */
   public int getViewHorizontalDragRange(@NonNull View child) {
     return 1;
   }

/**
    * 限制子 View 纵向拖拽范围。
    *
    * 如果返回 0,则不能进行纵向拖动, 我们要实现拖拽,返回值 > 0 即可。
    *
    */
   public int getViewVerticalDragRange(@NonNull View child) {
     return 1;
   }

/**
    * 判断当前触摸的 View 能否被捕获进行拖拽,如果返回 true 则可以进行拖拽。
    */
   public abstract boolean tryCaptureView(@NonNull View child, int pointerId);

/**
    * 限制当前 View 的横向拖拽范围,也可说是我们可以动态修正 View 的 top 坐标,比如我们想限制 View 只在容器内部拖动
    *
    * @param child 当前拖动的 View
    * @param left View 上次的 x 坐标 + 手指移动的 x 轴位移量
    * @param dx 手指移动的位移量
    * @return 修正后的 x 坐标,直接返回 left 表示不限制水平拖拽范围,拖到哪儿就哪儿
    */
   public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
     return left;
   }

/**
    * 限制当前 View 的纵向拖拽范围,也可说是我们可以动态修正 View 的 top 坐标,比如我们想限制 View 只在容器内部拖动
    *
    * @param child 当前拖动的 View
    * @param top View 上次的 y 坐标 + 手指移动的 y 轴位移量
    * @param dx 手指移动的位移量
    * @return 修正后的 x 坐标,直接返回 top 表示不限制纵向拖拽范围,拖到哪儿就哪儿
    */
   public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
     return top;
   }
 }

处理 Touch 事件

复杂的触摸逻辑就让 ViewDragHelper 接管即可。


@Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
   return mViewDragHelper.shouldInterceptTouchEvent(ev);
 }

@Override
 public boolean onTouchEvent(MotionEvent event) {
   mViewDragHelper.processTouchEvent(event);
   return true;
 }

处理 View 自动复位效果

当拖拽操作不满足触发条件时,手指松开,View 需要自动回到初始位置,在 onViewReleased 里调用以下方法即可:


mViewDragHelper.settleCapturedViewAt(originPoint.x, originPoint.y);
invalidate();

同时需要覆写:


@Override
public void computeScroll() {
 if (mViewDragHelper.continueSettling(true)) {
   ViewCompat.postInvalidateOnAnimation(this);
 }
}

具体实现步骤

1. 自定义 DragLayout,内部使用 ViewDragHelper 来处理拖拽操作。

2. 创建 Activity 展示图片,使用 DragLayout 作为根布局:


<com.liyu.fakeweather.widgets.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:id="@+id/drag_layout"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="@color/black"
 android:fitsSystemWindows="true">

<ImageView
   android:id="@+id/picture"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_centerInParent="true"
   android:adjustViewBounds="true"
   android:scaleType="fitCenter" />

</com.liyu.fakeweather.widgets.DragLayout>

并设置 Activity 背景为透明:

activity.getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT);// 当然也可以在 theme 里设置

3. 实现拖拽时动态改变背景透明度以及图片的缩放效果:


@Override
public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
 if (top > originPoint.y) {// 仅当向下拖拽时才处理
   float a = (float) (top - originPoint.y) / (float) (getMeasuredHeight() - originPoint.y);
   setBackgroundColor(ThemeUtil.changeAlpha(0xff000000, 1 - a));// 根据拖拽位移量动态改变背景透明度
   targetView.setScaleX(1 - a);// 缩放图片
   targetView.setScaleY(1 - a);// 缩放图片
   if ((top - originPoint.y) > getMeasuredHeight() / 5) {
     callEvent = true; // 下拽的位移量满足要求,可以触发关闭操作
   } else {
     callEvent = false;// 不能触发关闭操作
   }
 }
}

4. 手指释放时逻辑处理:


@Override
 public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
   if (releasedChild == targetView) {
     if (callEvent || yvel > 8000) {// 除了判断下拽的位移量,还要判断快速下拽的速度,这边 yevl 为用户手指快速滑动的 Y 轴加速度,当加速度大于一定值时也可触发关闭操作
       if (listener != null) {
         listener.onDragFinished();
       }
       callEvent = false;
     } else {
// 当拖拽不满足触发条件时,需要将 View 归位,这里使用自带的方法实现动画效果,传入初始坐标即可。
       mViewDragHelper.settleCapturedViewAt(originPoint.x, originPoint.y);
       invalidate();
     }
   }
 }

5. 图片的转场动画:

使用自带转场动画即可实现图片的打开和关闭动画。


ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(
       (Activity) context, transitView, PictureActivity.TRANSIT_PIC);

ActivityCompat.startActivity(context, intent, optionsCompat.toBundle());

...
ViewCompat.setTransitionName(mImageView, TRANSIT_PIC);
...

图片下拽返回的功能加入到了假装看天气的妹子图模块中,完整示例代码可前往 https://github.com/li-yu/FakeWeather/blob/master/app/src/main/java/com/liyu/fakeweather/widgets/DragLayout.java 查看。

总结

依靠简单而又强大的 ViewDragHelper,不到 200 行的代码也实现了类似的效果。他山之石可以攻玉,翻看其源码,也学到一些很少用到的小技巧,比如获取当前触摸位置的子 View,逆向遍历全部子 View 集合然后判断触摸坐标是否在范围内,真是简单粗暴:


@Nullable
public View findTopChildUnder(int x, int y) {
 final int childCount = mParentView.getChildCount();
 for (int i = childCount - 1; i >= 0; i--) {
   final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
   if (x >= child.getLeft() && x < child.getRight()
       && y >= child.getTop() && y < child.getBottom()) {
     return child;
   }
 }
 return null;
}

来源:http://liyuyu.cn/2018/08/09/Dragback/

标签:Android,ViewDragHelper,下拽,返回
0
投稿

猜你喜欢

  • C#中抽象方法与虚拟方法的区别

    2021-10-30 07:30:15
  • Android开发笔记之:复写按钮方法

    2023-01-18 05:44:12
  • Unity UGUI实现卡片椭圆方向滚动

    2022-06-03 06:02:53
  • Spring Boot web项目的TDD流程

    2023-07-06 16:10:10
  • java实现国产sm4加密算法

    2022-02-14 06:27:08
  • ProtoStuff不支持BigDecimal序列化及反序列化详解

    2022-09-19 06:53:30
  • 详解java一维数组及练习题实例

    2023-05-21 17:31:56
  • Mybatis单个参数的if判断报异常There is no getter for property named 'xxx' in 'class java.lang.Integer'的解决方案

    2023-10-16 14:56:01
  • Flutter Widgets之标签类控件Chip详解

    2023-06-26 14:22:35
  • Android实现背景可滑动登录界面 (不压缩背景弹出键盘)

    2023-11-14 11:09:07
  • C#中csv文件与DataTable互相导入处理实例解析

    2023-01-16 19:32:35
  • java 获取当前路径下的所有xml文档的方法

    2021-08-08 13:54:29
  • 引入mybatis-plus报 Invalid bound statement错误问题的解决方法

    2021-06-01 14:28:00
  • Android使用AIDL实现两个App间通信

    2023-06-21 18:05:45
  • Android编程中沉浸式状态栏的三种实现方式详解

    2022-02-10 11:56:23
  • C#文件分割的方法

    2023-09-17 22:20:56
  • Java线程池的优点及池化技术的应用

    2022-07-01 08:12:23
  • Android入门之实现手工发送一个BroadCast

    2023-08-10 13:54:16
  • 深入理解Java设计模式之外观模式

    2023-05-02 09:48:00
  • Java Validation Api使用方法实例解析

    2023-05-16 05:44:58
  • asp之家 软件编程 m.aspxhome.com