前言
说起下拉刷新和上拉加载大家应该都不陌生。从ListView时代的PulltoRefreshView到RecycleView时代的SwipeRefreshLayout,再到Github上封装好的适应各种View,并且同时支持下拉刷新和上拉加载的库;对于实现这样的一个功能已经是轻车熟路了。但是正是由于这种情况,导致了以下几个问题。
- 实现的效果单一,没有自身的特性
这一点使用过Google提供的SwipeRefreshLayout的同学应该深有体会,这个控件的确好用,把它套在RecycleView的外面,实现相应的接口就可以很方便的实现下拉刷新的效果。但是它能够实现的交互效果也很单一,甚至说有点无聊,基本上没有可定制的内容,我们能做的最多就是改一改旋转的ProgressBar的颜色及其背景颜色。
- 集成三方控件,过于冗余
相信现在大部分人遇到列表都是用RecycleView来实现,但是由于SwipeRefreshLayout没有自带上拉加载的功能,所以我们只能曲线救国;要么复写SwipeRefreshLayout自己加上上拉加载的功能;要么放弃使用SwipeRefreshLayout而是从github上找其他的实现方式,后者应该是很多人的选择;但是使用第三方控件,也会带来额外的一些问题,比如三方控件过于的重和庞大,功能过于复杂,有时候我们为了某一个简单的功能,可能要引入一些其他无用的代码,这是不好的。
因此,我们需要一款轻量级的库,他能够让我们更自由的定制下拉加载和上拉刷新时的效果,同时又不会过于的繁重。
UltimateRefreshView
一个让你关注于刷新动画效果的下拉刷新,上拉加载的控件。
老规矩,先来看看效果。这里放了几张能看清的动画,这些动画效果完全不同;但是只用一个UltimateRefreshView就够了。更多动画效果请到
功能
- 支持ListView,RecycleView,ScrollView,WebView
- 一行代码指定是否支持上拉加载,下拉刷新
- 自由定制刷新时头部和尾部的动画效果
使用方式
首先,是引入库
compile 'com.reoobter:ultrapullview:1.0.0'复制代码
其次,实现各自的动画效果
这里我们就以美团APP顶部下拉刷新的动画为例来看看如何实现动画效果
meituan_header_refresh_layout.xml
复制代码
这个布局文件很简单,整个只有一个ImageView。我们的实现思路,就是在不同的结点修改ImageView的内容,从而呈现出整个下拉刷新时所有的动画效果。那么这些结点是哪些呢?
public class MeiTuanHeaderAdapter extends BaseHeaderAdapter { private ImageView loading; private int viewHeight; private float pull_distance=0; public MeiTuanHeaderAdapter(Context context) { super(context); } @Override public View getHeaderView() { View mView = mInflater.inflate(R.layout.meituan_header_refresh_layout, null, false); loading = (ImageView) mView.findViewById(R.id.loading); MeasureTools.measureView(mView); viewHeight = mView.getMeasuredHeight(); return mView; } @Override public void pullViewToRefresh(int deltaY) { //这里乘以0.3 是因为UltimateRefreshView 源码中对于滑动有0.3的阻尼系数,为了保持一致 pull_distance=pull_distance+deltaY*0.3f; float scale = pull_distance / viewHeight; loading.setScaleX(scale); loading.setScaleY(scale); } @Override public void releaseViewToRefresh(int deltaY) { loading.setImageResource(R.drawable.mei_tuan_loading_pre); AnimationDrawable mAnimationDrawable= (AnimationDrawable) loading.getDrawable(); mAnimationDrawable.start(); } @Override public void headerRefreshing() { loading.setImageResource(R.drawable.mei_tuan_loading); AnimationDrawable mAnimationDrawable= (AnimationDrawable) loading.getDrawable(); mAnimationDrawable.start(); } @Override public void headerRefreshComplete() { loading.setImageResource(R.drawable.pull_image); loading.setScaleX(0); loading.setScaleY(0); pull_distance=0; }}复制代码
通过代码我们可以总结出有4个重要的结点
- 下拉进行时,这个时候随着手指滑动,整个顶部的view逐渐显示出来
- 顶部view完全被下拉出来,这个时候顶部view已经完全显示出来了,手指释放(抬起)后将进入下一个结点。
- 正在刷新进行时,刷新进行时,这个结点就是刷新动画执行的时候。
- 刷新完成,在这个结点触发了刷新完成的动作
为了实现美团顶部刷新动画的效果,在第一个结点我们便开始执行动画,根据刷新的位移比,使用scale动画逐渐放大初始图片(绿色椭圆);在第二个结点,这个结点一般都很短暂,这个时候顶部已经完全展示,执行了小人偶翻转出现的动画;在第三个结点,这个结点一般是比较耗时的,在这里用帧动画播放了一个小人偶左右摇摆的动画;最后,在第四个结点,将所有内容初始化到下拉之前的状态,方便下次下拉刷星动画的执行。这样就完成了一次下拉刷新的动画效果。
下面就可以非常方便的把这个动画效果运用到View上。
最后,将动画效果适配到UltimateRefreshView之上
这里就以ListView为例。
首先是布局实现:
复制代码
布局文件很简单,将所要实现的下拉刷新的控件放在UltimateRefreshView控件内即可。
public class ListViewFragment extends Fragment { private UltimateRefreshView mUltimateRefreshView; private int page = 0; private int PER_PAGE_NUM = 15; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_list_view, container, false); initView(view); return view; } private void initView(View view) { View headview = LayoutInflater.from(getContext()).inflate(R.layout.list_headview_layout, null, false); ListView listView = (ListView) view.findViewById(R.id.listView); final Listdatas = new ArrayList<>(); for (int i = 0; i < PER_PAGE_NUM; i++) { datas.add("this is item " + i); } final ArrayAdapter adapter = new ArrayAdapter (getContext(), android.R.layout.simple_list_item_1, datas); listView.setAdapter(adapter); listView.addHeaderView(headview); mUltimateRefreshView = (UltimateRefreshView) view.findViewById(R.id.refreshView); mUltimateRefreshView.setBaseHeaderAdapter(new MeiTuanHeaderAdapter(getContext())); mUltimateRefreshView.setBaseFooterAdapter(); mUltimateRefreshView.setOnHeaderRefreshListener(new OnHeaderRefreshListener() { @Override public void onHeaderRefresh(UltimateRefreshView view) { page = 0; new Handler().postDelayed(new Runnable() { @Override public void run() { datas.clear(); for (int i = page * PER_PAGE_NUM; i < PER_PAGE_NUM; i++) { datas.add("this is item " + i); } adapter.notifyDataSetChanged(); mUltimateRefreshView.onHeaderRefreshComplete(); } }, 2000); } }); mUltimateRefreshView.setOnFooterRefreshListener(new OnFooterRefreshListener() { @Override public void onFooterRefresh(UltimateRefreshView view) { page++; new Handler().postDelayed(new Runnable() { @Override public void run() { for (int i = page * PER_PAGE_NUM; i < (page + 1) * PER_PAGE_NUM; i++) { datas.add("this is item " + i); } adapter.notifyDataSetChanged(); mUltimateRefreshView.onFooterRefreshComplete(); } }, 200); } }); }}复制代码
这里最关键的代码就是:
mUltimateRefreshView.setBaseHeaderAdapter(new MeiTuanHeaderAdapter(getContext())); mUltimateRefreshView.setBaseFooterAdapter();复制代码
这样,就为ListView设定了下拉刷新和上拉加载时的动画效果。
为了方便使用,同时提供了无参的setAdapter方法,当不提供参数时,将使用默认的动画效果。
这里,下拉刷新使用了我们刚才定义的MeiTuanHeaderAdapter这个效果,上拉加载的动画效果则使用了默认的效果;当然,如果你不需要上拉加载效果或下拉刷新效果的话,不设定对应的Adapter即可。即不执行(setBaseFooterAdapter和setBaseHeaderAdapter方法)
最后,为mUltimateRefreshView设置属性动画执行时的监听器,这个回调方法会在刷新动画执行时(也就是上面所说的第三个结点时开始执行时)被调用,在这个方法里我们一般会进行网络请求或一些耗时操作,这里我们用Handler模拟了一个简单的耗时任务,当耗时操作完成时,调用对应的刷新完成方法,这样一次下拉刷新或者是上拉加载就执行完了(也就是上面所说的第四个结点)。
这里重点讨论整个刷新过程中动画效果的实现,下拉或上拉时数据如何刷新不做重点分析。
当我们需要下拉刷新动画时,继承BaseHeaderAdapter类并在各个方法(即下拉事件的各个结点)按动画所需的效果,做不同的实现即可。
同理,当需要上拉加载动画时,继承BaseFooterAdapter即可。BaseFooterAdapter类中结点的定义和BaseHeaderAdapter类中完全一致,只不过滑动方向从下拉变成了上拉而已。这里以简单模仿一下京东上拉加载时的动画效果。
package com.sak.app.adapter;import android.content.Context;import android.view.View;import android.widget.ImageView;import com.bumptech.glide.Glide;import com.sak.app.R;import com.sak.ultilviewlib.adapter.BaseFooterAdapter;/** * Created by engineer on 2017/4/30. */public class JDAppFooterAdapter extends BaseFooterAdapter { private ImageView loading; private Context mContext; public JDAppFooterAdapter(Context context) { super(context); mContext=context; } @Override public View getFooterView() { View footerView = mInflater.inflate(R.layout.jd_footer_refresh_layout, null, false); loading = (ImageView) footerView.findViewById(R.id.loading); return footerView; } @Override public void pullViewToRefresh(int deltaY) { Glide.with(mContext).load(R.drawable.jd_loading_logo).into(loading); } @Override public void releaseViewToRefresh(int deltaY) { } @Override public void footerRefreshing() { } @Override public void footerRefreshComplete() { loading.setImageDrawable(null); }}复制代码
这里我们继承了BaseFooterAdapter,可以看到他和BaseHeaderAdapter 十分相似。这里为了方便,没有做很复杂的实现,在上拉开始执行的时候,取巧的用Glide加载了一张gif 的动画,这样上拉时就会呈现一个简单的动画。现在用户在上拉时一般都会很快完成分页数据的加载,上拉动画的实现其实不用太复杂。
更多内容可以参考中demo的实现。
总结
看以看到,我们设置动画刷新的方法是在mUltimateRefreshView上执行的,也就说无论在mUltimateRefreshView内部嵌套的是ListView,还是RecycleView或者ScrollView都一样。头部和尾部的动画效果完全不受嵌套子View的影响。因此,我们可以将更多的精力用于实现头部和尾部刷新时绚丽的动画效果,而不再纠结于各种滑动事件的处理。
头部和尾部动画的实现,完全和mUltimateRefreshView本身是分离的,极大的减少了耦合。需要什么样的动画,通过setBaseFooterAdapter和setBaseHeaderAdapter方法进行设置即可,十分灵活,不同的头部和尾部动画可以自由搭配。
所以,赶紧来玩吧!
最后,再次给出。欢迎感兴趣的同学 star & fork 。