转自: 自定义一个更好用的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(弹力拉伸效果详解)(转载)的更多相关文章

  1. 【翻译】Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解)

    [翻译]Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解) . . .

  2. 全网最详细的一款满足多台电脑共用一个鼠标和键盘的工具Synergy(图文详解)

    不多说,直接上干货! 前言 如今无论你是在公司做大数据开发还是实验室里搞科研,这个软件确实好用,作为正在通往大数据架构师路上的我们没有几台电脑怎么行?台式机.笔记本,都放在写字台上,笔记本内置键盘鼠标 ...

  3. Springboot — 用更优雅的方式发HTTP请求(RestTemplate详解)

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...

  4. 从零实现一个简易的jQuery框架之二—核心思路详解

    如何读源码 jQuery整体框架甚是复杂,也不易读懂.但是若想要在前端的路上走得更远.更好,研究分析前端的框架无疑是进阶路上必经之路.但是庞大的源码往往让我们不知道从何处开始下手.在很长的时间里我也被 ...

  5. 逆向分析一个完整的C++程序包含寄存器与参数传递详解

    最近在分析C++ dump 文件的时候觉得有必要将一些必要的反汇编东西总结一下以备别人参考,自己有时间的时候也可以进行更多的改进.下面通过一个简单的C++代码转成汇编代码后的详细解释说明一下C++和汇 ...

  6. Spring— 用更优雅的方式发HTTP请求(RestTemplate详解)

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...

  7. 每天一个linux命令(31): /etc/group文件详解

    Linux /etc/group文件与/etc/passwd和/etc/shadow文件都是有关于系统管理员对用户和用户组管理时相关的文件.linux /etc/group文件是有关于系统管理员对用户 ...

  8. 一个完整的WSDL文档及各标签详解

    <?xml version="1.0" encoding="UTF8" ?> <wsdl:definitions targetNamespac ...

  9. 【转】每天一个linux命令(31): /etc/group文件详解

    原文网址:http://www.cnblogs.com/peida/archive/2012/12/05/2802419.html Linux /etc/group文件与/etc/passwd和/et ...

随机推荐

  1. SQL 统计某一列出现的总和

    现有数据如上图所示,要求统计出日期相同的Count总数,并且加一列统计前面日期Count的总和 SELECT SUM([Count]) AS DayTotal, SUM(SUM([Count])) o ...

  2. 在前端页面调用sevlet的路径

    1.路径:相对路径和绝对路径 <!-- 使用相对路径访问Servletpath --> <a href="servlet/ServletPath">这是Se ...

  3. form表单提交之前判断

    1.使用onsubmit方法 <form name="Form" action="t" method="post" onsubmit= ...

  4. iOS -- tableView顶部留白

    最近iOS 11 下来之后需要适配系统,故此测试系统发现同一种机器上tableView与顶部控件,或者导航栏之间的有大量的留白: 这个问题是有的界面有,有的界面无: 修改与导航栏或者其他控件之间的距离 ...

  5. 2017.5.24 在intelliJ IDEA 中生成war包

    1.勾选Build on make file -> project structure -> Artifacts 2.compile module "***" 选择项目 ...

  6. 转: Servlet 工作原理解析 from ibm

    评点: 比较深入的讲了servlet容器, 作者许令波 (这个文章好像来自他自己的书中java web...) https://www.ibm.com/developerworks/cn/java/j ...

  7. 系统封装 如何加载PE到Easyboot进行合盘

    1 直接使用别人的PE. 如何加载PE到Easyboot首先需要知道验证你下载的PE的ISO镜像能否启动 如果答案是可以的,以自由天空的MINIPE为例,虽然可以启动,但是完全没有菜单提示,我们想要作 ...

  8. UVA 111 (复习dp, 14.07.09)

     History Grading  Background Many problems in Computer Science involve maximizing some measure accor ...

  9. 【转载】 json字符串与JSON对象

    ajax中,我们自己拼接的是一个JSON对象,以为它是无数据类型的,所以JS根据其格式默认其实对象, 你要是往后台发,要先把它装换成JSON字符. 从ajax的服务器发过的,一定是字符串,你想要把它解 ...

  10. 【引用】python 静态函数 类函数 实例函数

    1.关于定义类的一些奇特之处  今天在Python中定义一个类,很奇怪,不需要事先声明它的成员变量吗?暂时不知,先记录下来: class Account(object):    "一个简单的 ...