Android ViewDragHelper仿淘宝拖动加载效果
拖动加载是我在淘宝的商品详情界面发现的,感觉很实用。于是就分析它的实现方式,感觉用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。就这样。是不是达到了想要的效果了?
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
- 用Metaclass实现一个精简的ORM框架
- HDU 2504 又见GCD(最大公约数与最小公倍数变形题)
- Selenium2+python自动化63-二次封装(click/send_kesy)
- Selenium2+python自动化65-js定位几种方法总结
- HDU 2502 月之数(二进制,规律)
- Tensorflow实战系列:手把手教你使用CNN进行图像分类(附完整代码)
- HDU 2503 a/b + c/d(最大公约数与最小公倍数,板子题)
- python接口自动化6-重定向(Location)
- 2017广东工业大学程序设计竞赛初赛 题解&源码(A,水 B,数学 C,二分 D,枚举 E,dp F,思维题 G,字符串处理 H,枚举)
- python接口自动化7-参数关联
- 深度学习GPU环境Ubuntu16.04+GTX1080+CUDA9+cuDNN7+TensorFlow1.6环境配置
- python接口自动化8-参数化
- HDU 2037 今年暑假不AC(贪心,区间更新,板子题)
- “玲珑杯”ACM比赛 Round #13 题解&源码
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 微服务[学成在线] day20:项目部署与持续集成(DevOps)
- R语言中回归模型预测的不同类型置信区间应用比较分析
- R语言进阶之坐标轴和文本
- R语言广义线性模型(GLM)广义相加模型(GAM):多元平滑回归分析保险投资风险敞口
- 面试高频题:springBoot自动装配的原理你能说出来吗?
- R语言巨灾风险下再保险合同定价研究案例:广义线性模型和帕累托分布分析
- nodejs搭建mqtt服务器
- R语言中GLM(广义线性模型),非线性和异方差可视化分析
- 解决java的http请求库dongliu.requests请求结果中文乱码的问题
- 保姆级教程,手把手教你实现一个SpringBoot的starter
- 微服务[学成在线] day05:消息中间件 RabbitMQ
- 微服务[学成在线] day06:页面发布以及课程管理
- 微服务[学成在线] day07:课程管理开发
- 微服务[学成在线] day08:FastDFS 实现课程图片管理
- 【MySQL入门】之细说脏读、幻读及不可重复读