Android ViewDragHelper仿淘宝拖动加载效果

发布时间 - 2026-01-11 02:45:09    点击率:

拖动加载是我在淘宝的商品详情界面发现的,感觉很实用。于是就分析它的实现方式,感觉用ViewDragHelper可以很方便的实现这种效果。下面大致把我的思路分步骤写一下。先上图吧。

首先建工程什么的我就不多说了。咱从ViewDragHelper的实现开始说吧,ViewDragHelper一般用在一个自定义ViewGroup的内部,可以对其子View进行移动操作。

创建自定义ViewGroup:

package com.maxi.viewdraghelpertest.widget; 
 
import android.content.Context; 
import android.support.v4.widget.ViewDragHelper; 
import android.util.AttributeSet; 
import android.view.View; 
import android.widget.LinearLayout; 
 
public class DragHelperLayout extends LinearLayout{ 
  private ViewDragHelper mDragHelper; 
  @SuppressWarnings("static-access") 
  public DragHelperLayout(Context context, AttributeSet attrs) { 
    super(context, attrs); 
    // TODO Auto-generated constructor stub 
    /* 
     * 创建带回调接口的ViewDragHelper 
     */ 
    mDragHelper = ViewDragHelper.create(this, 10.0f,new DragHelperCallback());// 参数一:该类生成的对象(当前的ViewGroup) 
                      // 参数二:敏感度(越大越敏感) 
  } 
  class DragHelperCallback extends ViewDragHelper.Callback { 
 
    @Override 
    public boolean tryCaptureView(View arg0, int arg1) { 
      // TODO Auto-generated method stub 
      return false; 
    } 
     
  } 
} 

然后将触摸事件传递给ViewDragHelper:

@Override 
public boolean onInterceptTouchEvent(MotionEvent event) 
{ 
  return mDragHelper.shouldInterceptTouchEvent(event);//是否应该打断MotionEvent的传递 
} 
 
@Override 
public boolean onTouchEvent(MotionEvent event) 
{ 
  mDragHelper.processTouchEvent(event); 
  return true; 
} 

接着我们开始实现DragHelperCallback,这个ViewDragHelper.Callback回调中可以对ViewGroup中的一些View进行操作,在此我们只对本项目涉及到的相关用法做解析,详细点请自行查阅资料。

class DragHelperCallback extends ViewDragHelper.Callback { 
 
    @Override 
    public boolean tryCaptureView(View arg0, int arg1) { 
      // TODO Auto-generated method stub 
      return true;  //返回true表示可以捕捉ViewGroup中的View 
    } 
    /* 
     * (non-Javadoc) 
     * @see android.support.v4.widget.ViewDragHelper.Callback#clampViewPositionVertical(android.view.View, int, int) 
     * 限定View竖直方向上的活动区域,防止滑出ViewGroup 
     */ 
    @Override 
    public int clampViewPositionVertical(View child, int top, int dy) { 
       int topBound = getPaddingTop(); 
       int bottomBound = getHeight() - child.getHeight() - topBound; 
       int newHeight = Math.min(Math.max(top, topBound), bottomBound); 
       return newHeight; 
    } 
     
  } 

在上面的代码段中我已经做了注释,在clampViewPositionVertical中我们对View的竖直方向活动区域做了限制,防止滑出ViewGroup,当然你可以直接return top;不过为了效果我先这么限定一下。还有一个clampViewPositionHorizontal方法,同样是对其水平边界进行控制的,先不多说啦。这个时候咱们自定义的ViewGroup初期已经完成,先去试试水。

在activity_main.xml中加入

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  tools:context="com.maxi.viewdraghelpertest.MainActivity" > 
 
 
  <com.maxi.viewdraghelpertest.widget.DragHelperLayout 
    android:id="@+id/dhl" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical" 
    android:background="@android:color/darker_gray" 
    > 
  <TextView  
    android:layout_width="match_parent" 
    android:layout_height="100dp" 
    android:background="@android:color/holo_blue_bright" 
  /> 
  <TextView  
    android:layout_width="match_parent" 
    android:layout_height="100dp" 
    android:background="@android:color/holo_orange_dark" 
  /> 
  </com.maxi.viewdraghelpertest.widget.DragHelperLayout> 
 
 
</RelativeLayout> 

运行后的效果:

大家是不是都急了,做个拖动加载怎么搞起这东西了,不要急,这才刚刚开始,大家想想拖动加载是不是就是两个View在同一个ViewGroup里通过ViewDragHelper的滑动操作然后实现的?是不是有思路的?没有思路也没关系,咱慢慢来,想要两个View相关联,就是拖动一个View然后另一个View跟着它走该怎么实现呢?首先我们需要ViewDragHelper回调里的另一个方法onViewPositionChanged,该方法是在View位置发生改变时回调的。为的就是在上面的View上拉的时候让下面的View跟着往上走。来看看我们的实现方法:
首先先将两个View初始化:

private View t1, t2; 
/* 
 * (non-Javadoc) 
 * @see android.view.View#onFinishInflate() 
 * 初始化两个View 
 */ 
@Override 
protected void onFinishInflate() { 
  t1 = getChildAt(0); 
  t2 = getChildAt(1); 
} 

得到两个View后我们在回调中判断哪个位置发生了改变,

@Override 
public void onViewPositionChanged(View changedView, int left, int top, 
    int dx, int dy) { 
  // TODO Auto-generated method stub 
  int childIndex = 1; 
  if (changedView == t2) { 
    childIndex = 2; 
  } 
  viewFollowChanged(childIndex, top); 
} 

上面的代码段中有个方法viewFollowChanged,主要实现的就是View跟着动。

private void viewFollowChanged(int viewIndex, int posTop) { 
  viewH = t1.getMeasuredHeight(); 
  if (viewIndex == 1) { 
    int offsetTopBottom = viewH + t1.getTop() - t2.getTop(); 
    t2.offsetTopAndBottom(offsetTopBottom); 
  } else if (viewIndex == 2) { 
    int offsetTopBottom = t2.getTop() - viewH - t1.getTop(); 
    t1.offsetTopAndBottom(offsetTopBottom); 
  } 
  invalidate(); 
} 

 

在运行是不是发现没有被点击拖动的View会跟着View一起移动,像一个整体双宿双飞。图我就不加了,大家运行看吧。因为我们要获取View的实际大小所以需要以下代码段的支持:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  measureChildren(widthMeasureSpec, heightMeasureSpec); 
 
  int maxWidth = MeasureSpec.getSize(widthMeasureSpec); 
  int maxHeight = MeasureSpec.getSize(heightMeasureSpec); 
  setMeasuredDimension( 
      resolveSizeAndState(maxWidth, widthMeasureSpec, 0), 
      resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); 
} 
 
public static int resolveSizeAndState(int size, int measureSpec, 
    int childMeasuredState) { 
  int result = size; 
  int specMode = MeasureSpec.getMode(measureSpec); 
  int specSize = MeasureSpec.getSize(measureSpec); 
  switch (specMode) { 
  case MeasureSpec.UNSPECIFIED: 
    result = size; 
    break; 
  case MeasureSpec.AT_MOST: 
    if (specSize < size) { 
      result = specSize | MEASURED_STATE_TOO_SMALL; 
    } else { 
      result = size; 
    } 
    break; 
  case MeasureSpec.EXACTLY: 
    result = specSize; 
    break; 
  } 
  return result | (childMeasuredState & MEASURED_STATE_MASK); 
} 

然后我们可以尝试将两个View满屏,android:layout_height="match_parent",把clampViewPositionVertical方法里限制的边界去掉吧,暂时先return top;这样试一下是不是有点像拖动加载了,呵呵哒,可是第一个View下拉的时候由于上面没有View怎么办?我们可以在clampViewPositionVertical中将它限定边界啊!

@Override 
    public int clampViewPositionVertical(View child, int top, int dy) { 
      int slideTop = top; 
      if (child == t1) { 
        if (top > 0) { 
          slideTop = 0; 
        } 
      } else if (child == t2) { 
        if (top < 0) { 
          slideTop = 0; 
        } 
      } 
      return child.getTop() + (slideTop - child.getTop()); 
    } 
 

已经大致成型了,然后就是拖动的时候将View自动置顶或置底,因为自动置顶或置底是在滑动松开之后,所以就需要用到ViewDragHelper回调里的onViewReleased方法,该方法就是在滑动松开之后调用,接下来实现它:

@Override 
    public void onViewReleased(View releasedChild, float xvel, float yvel) { 
      putStickOrDown(releasedChild);// 滑动松开后,需要置顶或置底 
    } 
    private void putStickOrDown(View releasedChild, float yvel) { 
    int finalTop = 0; // 默认是粘到最顶端 
    if (releasedChild == t1) { 
      // 滑动第一个view松开 
      if (yvel < 0)//灵敏度自己调吧 
        finalTop = -viewH; 
    } else { 
      // 滑动第二个view松开 
      if (yvel > 0)//同上 
        finalTop = viewH; 
    } 
    if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) { 
      ViewCompat.postInvalidateOnAnimation(this);// 会在下一个Frame开始的时候,发起一些invalidate操作 
    } 
    } 
 
    @Override 
    public void computeScroll() { 
      if (mDragHelper.continueSettling(true)) { 
        ViewCompat.postInvalidateOnAnimation(this); 
      } 
    } 

ok,是可以自动置顶或置底了。对了,那种拖动粘滞效果可以设置clampViewPositionVertical里的返回值,return child.getTop() + (finalTop - child.getTop()) / num;num值越大越粘滞。

然后淘宝第一个View是可以滑动的滑动到最底部然后才把手势事件交给ViewDragHelper处理的。这块试想如果用ScrollView的话,手势事件肯定会优先被它消费,这样肯定达不到我们想要的效果,所以在此我们需要对ScrollView进行自定义,大致的实现思路是当用户用户从触发屏幕开始判断是不是ScrollView在最底端,如果在最底端然后判断手势是否是向上滑动的如果也是则满足条件将touch事件交给父View就可以了,即requestDisallowInterceptTouchEvent该方法。然后自定义的ViewGroup中的onInterceptTouchEvent方法也要做相应修改,这里用GestureDetectorCompat处理事件,其回调用来判断是否是上下滑动。先声明private GestureDetectorCompat gestureDC;然后再gestureDC = new GestureDetectorCompat(context,new YSlideDetector());

class YSlideDetector extends SimpleOnGestureListener { 
 
  @Override 
  public boolean onScroll(MotionEvent e1, MotionEvent e2, 
      float distanceX, float distanceY) { 
    // TODO Auto-generated method stub 
    return Math.abs(distanceY) > Math.abs(distanceX);//Y方向绝对值大于X方向,上下滑动 
  } 
}<pre name="code" class="java">  @Override 
public boolean onInterceptTouchEvent(MotionEvent event) { 
  boolean is_y_slide = gestureDC.onTouchEvent(event); 
  boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(event); 
  int action = event.getActionMasked(); 
  if (action == MotionEvent.ACTION_DOWN) { 
    mDragHelper.processTouchEvent(event);// action_down时就让mDragHelper开始工作,否则有时候导致异常 
  } 
  return shouldIntercept && is_y_slide; 
} 

OK。就这样。是不是达到了想要的效果了?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# Android  # ViewDragHelper  # 拖动  # 加载  # Android实现ImageView图片缩放和拖动  # Android实现跟随手指拖动并自动贴边的View样式(实例demo)  # Android自定义View实现拖动选择按钮  # Android实现单页面浮层可拖动view的一种方法  # Android通过自定义ImageView控件实现图片的缩放和拖动的实现代码  # Android开发实现可拖动排序的ListView功能【附源码下载】  # Android DragImageView实现下拉拖动图片放大效果  # Android RecyclerView滑动删除和拖动排序  # Android自定义View圆形和拖动圆、跟随手指拖动效果  # android实现可拖动的浮动view  # 回调  # 自定义  # 置顶  # 第一个  # 我就  # 是在  # 在此  # 我们可以  # 对其  # 在上面  # 越大  # 淘宝  # 滑出  # 底端  # 双宿双飞  # 我在  # 有个  # 你可以 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 如何快速查询网站的真实建站时间?  DeepSeek是免费使用的吗 DeepSeek收费模式与Pro版本功能详解  Laravel如何实现事件和监听器?(Event & Listener实战)  Java类加载基本过程详细介绍  Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道  怎么制作一个起泡网,水泡粪全漏粪育肥舍冬季氨气超过25ppm,可以有哪些措施降低舍内氨气水平?  ,网页ppt怎么弄成自己的ppt?  利用vue写todolist单页应用  iOS发送验证码倒计时应用  Android利用动画实现背景逐渐变暗  如何破解联通资金短缺导致的基站建设难题?  Laravel如何实现密码重置功能_Laravel密码找回与重置流程  如何快速搭建高效可靠的建站解决方案?  详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点  如何获取PHP WAP自助建站系统源码?  Laravel队列由Redis驱动怎么配置_Laravel Redis队列使用教程  javascript中的try catch异常捕获机制用法分析  如何在企业微信快速生成手机电脑官网?  高端企业智能建站程序:SEO优化与响应式模板定制开发  Laravel如何使用API Resources格式化JSON响应_Laravel数据资源封装与格式化输出  Python制作简易注册登录系统  PHP正则匹配日期和时间(时间戳转换)的实例代码  JS实现鼠标移上去显示图片或微信二维码  html文件怎么打开证书错误_https协议的html打开提示不安全【指南】  详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)  阿里云网站搭建费用解析:服务器价格与建站成本优化指南  如何确认建站备案号应放置的具体位置?  北京网页设计制作网站有哪些,继续教育自动播放怎么设置?  JavaScript如何实现音频处理_Web Audio API如何工作?  如何快速生成ASP一键建站模板并优化安全性?  Android仿QQ列表左滑删除操作  Laravel如何与Vue.js集成_Laravel + Vue前后端分离项目搭建指南  Python文件操作最佳实践_稳定性说明【指导】  Linux安全能力提升路径_长期防护思维说明【指导】  阿里云高弹*务器配置方案|支持分布式架构与多节点部署  如何在景安服务器上快速搭建个人网站?  Laravel如何实现URL美化Slug功能_Laravel使用eloquent-sluggable生成别名【方法】  装修招标网站设计制作流程,装修招标流程?  Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】  Swift中循环语句中的转移语句 break 和 continue  如何打造高效商业网站?建站目的决定转化率  rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted  原生JS获取元素集合的子元素宽度实例  微信h5制作网站有哪些,免费微信H5页面制作工具?  油猴 教程,油猴搜脚本为什么会网页无法显示?  如何在云主机上快速搭建网站?  Android GridView 滑动条设置一直显示状态(推荐)  Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】  如何用免费手机建站系统零基础打造专业网站?  如何做网站制作流程,*游戏网站怎么搭建?