自定义一个更好用的SwipeRefreshLayout(弹力拉伸效果详解)(转载)
转自: 自定义一个更好用的SwipeRefreshLayout(弹力拉伸效果详解)
前言
熟悉SwipeRefreshLayout的同学一定知道,SwipeRefreshLayout是android里面专为RecyclerView,NestedScrollView提供下拉刷新动画的一个控件。可是在使用过程中有些局限性,例如只支持上述控件,不支持ListView,GridView等,另外下拉的动画效果很难更改,而且不支持上拉加载……在很多场景的情况下往往不符合我们的需求。
今天为大家分享的是一个支持上拉下拉加载的控件,代码并非纯原创,改造自github作者baoyz的PullRefreshLayout (有印象最早看到的侧滑删除好像也是他写的),也参考了一些官方SwipeRefreshLayout的源码和网上的一些资料,为了尊重原作者,我还是将其命名为——PullRefreshLayout。
效果如下:
原理
其实是一个ViewGroup,通过对手势的处理,使子控件实现拉动的动画效果,并再加上两个子控件,上拉的loading和下拉的loading(把loading用控件来封装可以很方便的更改动画,真是贴心~),在处理手势拉动的时候,通知他们显示出对应的效果。代码很长,有很多小细节需要注意,在这里我只介绍几个关键的位置,源代码会发在文章的最后。
拖拽弹力效果
大家可以看到,拖拽的时候,是有个弹力效果的,也就是说当拖拽的距离大于某个值,拖动的位移就会慢慢减小,最后会变得拖不动看上去有点酷炫,其实实现起来就是高中数学知识啦,看下关键代码
final float scrollTop = yDiff * DRAG_RATE;
float originalDragPercent = scrollTop / mTotalDragDistance;
mDragPercent = Math.min(1f, Math.abs(originalDragPercent));//拖动的百分比
float extraOS = Math.abs(scrollTop) - mTotalDragDistance;//弹簧效果的位移
float slingshotDist = mSpinnerFinalOffset;
//当弹簧效果位移小余0时,tensionSlingshotPercent为0,否则取弹簧位移于总高度的比值,最大为2
float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) / slingshotDist);
//对称轴为tensionSlingshotPercent = 2的二次函数,0到2递增
float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow((tensionSlingshotPercent / 4), 2)) * 2f;
float extraMove = (slingshotDist) * tensionPercent * 2;
targetY = (int) ((slingshotDist * mDragPercent) + extraMove);
解释下几个参数
yDiff —— 根据手势算出的滑动位移
scrollTop —— yDiff乘上一个固定的比率(现在是0.5),可以用来调节“弹簧”的弹性系数
mTotalDragDistance —— 当进度显示100%时的位移
originalDragPercent —— 根据scrollTop与mTotalDragDistance的比值
mDragPercent —— 由于originalDragPercent可能大于1,所以mDragPercent才是拖动的百分比
slingshotDist —— 超过100%后可以被允许拖动的最大距离的二分之一,也是一个常数(现在值 = mSpinnerFinalOffset = mTotalDragDistance)
extraMove —— 弹力距离
targetY —— 想要移动到的目标位置
啊!? 被发现有两个个参数没解释,哈哈,至于tensionSlingshotPercent和tensionPercent,就是弹力效果的关键啦
extraOS 在scrollTop>=0时,是从-mTotalDragDistance开始线性递增的,在scrollTop = mTotalDragDistance时,extraOS = 0
tensionSlingshotPercent 在scrollTop从0到mTotalDragDistance阶段,始终为0,在smTotalDragDistance到3*mTotalDragDistance阶段,线性递增,之后一直为2
extraMove 的变化同tensionSlingshotPercent
tensionPercent 是个二次函数,同样映射到scrollTop的变化,在scrollTop从0到mTotalDragDistance阶段,始终为0,在mTotalDragDistance到3mTotalDragDistance阶段,二次函数递增,在3mTotalDragDistance之后恒为0.5
而targetY,在scrollTop从0到mTotalDragDistance阶段,也就是mDragPercent从0到1,extramMove始终为0,然后二次函数递增,在scrollTop > 3*mTotalDragDistance 变为恒值
总结下来targetY相对于scrollTop对函数图像如下:
其实看到这个图,我想大家就基本上知道具体出来的效果了,再后面就是一些位移的操作,大家可以看文章最后面源码,值得注意的是,之前都是分析scrollTop > 0 的情况,也就是下拉操作,上拉targetY要取负的,而且上拉下拉都是走这套逻辑,所以计算extraOS的时候scrollTop加上了绝对值
loading动画
在前文中我们说过,loading效果其实是交给两个子控件完成的,这样有利于更改loading的动画效果。那么,具体是怎么实现的呢?
在代码中我们可以看到如下几个对象,其中mRefreshView和mLoadView就是我们所说的loading控件,但它们只是一个容器,只控制显隐,而具体的动画实现是交给对应的mRefreshDrawable和mLoadDrawable;
那我们选取其中一个进行分析,在初始化函数中,可以看到如下代码
setRefreshDrawable方法走进去,发现其实就是把一个Drawable对象赋给mRefreshView,那我们来看一下传入的参数PlaneDrawable,这个是我写的那个火箭飞行的动画效果,代码很简单,但是我们发现PlaneDrawable是继承了一个叫RefreshDrawable的类,对,它才是将动画效果解耦于PullRefreshLayout的关键、
我们看下它的代码
import android.content.Context;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; /**
* Created by baoyz on 14/10/29.
*/
public abstract class RefreshDrawable extends Drawable implements Drawable.Callback, Animatable { private PullRefreshLayout mRefreshLayout; public RefreshDrawable(Context context, PullRefreshLayout layout) {
mRefreshLayout = layout;
} public Context getContext(){
return mRefreshLayout != null ? mRefreshLayout.getContext() : null;
} public PullRefreshLayout getRefreshLayout(){
return mRefreshLayout;
} public abstract void setPercent(float percent);
public abstract void setColorSchemeColors(int[] colorSchemeColors); public abstract void offsetTopAndBottom(int offset); @Override
public void invalidateDrawable(Drawable who) {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
} @Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
} @Override
public void unscheduleDrawable(Drawable who, Runnable what) {
final Callback callback = getCallback();
if (callback != null) {
callback.unscheduleDrawable(this, what);
}
} @Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
} @Override
public void setAlpha(int alpha) { } @Override
public void setColorFilter(ColorFilter cf) { }
}
它是一个抽象类,抽象方法有
public abstract void setPercent(float percent);
public abstract void setColorSchemeColors(int[] colorSchemeColors); public abstract void offsetTopAndBottom(int offset);
为了代码简洁,主题颜色我没有用,还剩下setPercent和offsetTopAndBottom,这两个方法会分别在PullRefreshLayout里面拉动的进度改变和被拉动目标控件位移变化时被调用。这样,我们想更改动画效果就简单了,直接写一个类,继承至RefreshDrawable,然后在对应的setPercent和offsetTopAndBottom里面做出相应的动画数据改变,就如PlaneDrawable那样,然后再调用PullRefreshLayout的setRefreshDrawable或setLoadDrawable方法进行设值,是不是很方便?
同时显示两个动画处理
在添加上拉加载效果时,我发现,假如你先下拉然后在不松手的情况下再上拉,那就会同时出现两个loading动画,然而此时list还不在底部,也就是不应该显示上拉loading效果的。这是由于在拉动时,判断子控件是否可以向上滑动的那个方法会返回false,那么如何解决这个问题呢?用一个变量mLastDirection储存本次动画的,如果下次的动画与本次不同,则不进行下次动画,并在ACTION_UP和ACTION_CANCEL时,判断被拉动目标控件的top位置
好了,废话不多说啦,源码的地址https://github.com/SIdQi/Pull...
自定义一个更好用的SwipeRefreshLayout(弹力拉伸效果详解)(转载)的更多相关文章
- 【翻译】Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解)
[翻译]Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解) . . .
- 全网最详细的一款满足多台电脑共用一个鼠标和键盘的工具Synergy(图文详解)
不多说,直接上干货! 前言 如今无论你是在公司做大数据开发还是实验室里搞科研,这个软件确实好用,作为正在通往大数据架构师路上的我们没有几台电脑怎么行?台式机.笔记本,都放在写字台上,笔记本内置键盘鼠标 ...
- Springboot — 用更优雅的方式发HTTP请求(RestTemplate详解)
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...
- 从零实现一个简易的jQuery框架之二—核心思路详解
如何读源码 jQuery整体框架甚是复杂,也不易读懂.但是若想要在前端的路上走得更远.更好,研究分析前端的框架无疑是进阶路上必经之路.但是庞大的源码往往让我们不知道从何处开始下手.在很长的时间里我也被 ...
- 逆向分析一个完整的C++程序包含寄存器与参数传递详解
最近在分析C++ dump 文件的时候觉得有必要将一些必要的反汇编东西总结一下以备别人参考,自己有时间的时候也可以进行更多的改进.下面通过一个简单的C++代码转成汇编代码后的详细解释说明一下C++和汇 ...
- Spring— 用更优雅的方式发HTTP请求(RestTemplate详解)
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...
- 每天一个linux命令(31): /etc/group文件详解
Linux /etc/group文件与/etc/passwd和/etc/shadow文件都是有关于系统管理员对用户和用户组管理时相关的文件.linux /etc/group文件是有关于系统管理员对用户 ...
- 一个完整的WSDL文档及各标签详解
<?xml version="1.0" encoding="UTF8" ?> <wsdl:definitions targetNamespac ...
- 【转】每天一个linux命令(31): /etc/group文件详解
原文网址:http://www.cnblogs.com/peida/archive/2012/12/05/2802419.html Linux /etc/group文件与/etc/passwd和/et ...
随机推荐
- Struts2笔记--Action访问Servlet API
Web应用中通常需要访问的Servlet API就是HttpServletRequest.HttpSession和ServletContext,这三个接口分别代表JSP内置对象中的request.se ...
- git reset,git checkout区别
https://www.cnblogs.com/houpeiyong/p/5890748.html git reset HEAD~1 将HEAD指向倒数第二次提交,最后一次提交删除,文件还在 gi ...
- web-51job(前程无忧)-账户、简历-数据库设计
ylbtech-DatabaseDesgin:web-51job(前程无忧)-账户.简历-数据库设计 1.A,数据库关系图 1.B,数据库设计脚本 /App_Data/1,Account.sql ...
- 求逆序对数总结 & 归并排序
用归并排序方式 最原始的方法的复杂度是O(n^2). 使用归并排序的方式,可以把复杂度降低到O(nlgn). 设A[1..n]是一个包含N个非负整数的数组.如果在i〈 j的情况下,有A〉A[j],则( ...
- 10 种机器学习算法的要点(附 Python)(转载)
一.前言 谷歌董事长施密特曾说过:虽然谷歌的无人驾驶汽车和机器人受到了许多媒体关注,但是这家公司真正的未来在于机器学习,一种让计算机更聪明.更个性化的技术 也许我们生活在人类历史上最关键的时期:从使用 ...
- BP神经网络(手写数字识别)
1实验环境 实验环境:CPU i7-3770@3.40GHz,内存8G,windows10 64位操作系统 实现语言:python 实验数据:Mnist数据集 程序使用的数据库是mnist手写数字数据 ...
- [Angular] ngClass conditional
Using ngClass for conditional styling, here is the usage from the docs: /** * @ngModule CommonModule ...
- jQuery的DOM操作之加入元素和删除元素
加入元素: .append()--在目标元素之后加入元素. .prepend()--在目标元素之前加入元素. .after()--在目标元素之后换行加入元素: .before()--在目标元素之前加入 ...
- 打造你的前端神器-webstorm11
说起前端编辑器,用过dw,sublime,hbuilder,webstorm也不陌生,之前的版本8有用过一下,但是觉得比sublime重量太多,但是随着后来用node的开始,发现需要打造个web前端神 ...
- poj 1390 Blocks (经典区间dp 方块消除)
Blocks Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 4250 Accepted: 1704 Descriptio ...