前言
說(shuō)起下拉刷新和上拉加載大家應(yīng)該都不陌生。從ListView時(shí)代的PulltoRefreshView到RecycleView時(shí)代的SwipeRefreshLayout,再到Github上封裝好的適應(yīng)各種View砍艾,并且同時(shí)支持下拉刷新和上拉加載的庫(kù)茧泪;對(duì)于實(shí)現(xiàn)這樣的一個(gè)功能已經(jīng)是輕車(chē)熟路了清笨。但是正是由于這種情況业稼,導(dǎo)致了以下幾個(gè)問(wèn)題。
- 實(shí)現(xiàn)的效果單一横漏,沒(méi)有自身的特性
這一點(diǎn)使用過(guò)Google提供的SwipeRefreshLayout的同學(xué)應(yīng)該深有體會(huì)谨设,這個(gè)控件的確好用,把它套在RecycleView的外面缎浇,實(shí)現(xiàn)相應(yīng)的接口就可以很方便的實(shí)現(xiàn)下拉刷新的效果扎拣。但是它能夠?qū)崿F(xiàn)的交互效果也很單一,甚至說(shuō)有點(diǎn)無(wú)聊,基本上沒(méi)有可定制的內(nèi)容二蓝,我們能做的最多就是改一改旋轉(zhuǎn)的ProgressBar的顏色及其背景顏色誉券。
- 集成三方控件,過(guò)于冗余
相信現(xiàn)在大部分人遇到列表都是用RecycleView來(lái)實(shí)現(xiàn)刊愚,但是由于SwipeRefreshLayout沒(méi)有自帶上拉加載的功能踊跟,所以我們只能曲線(xiàn)救國(guó);要么復(fù)寫(xiě)SwipeRefreshLayout自己加上上拉加載的功能鸥诽;要么放棄使用SwipeRefreshLayout而是從github上找其他的實(shí)現(xiàn)方式商玫,后者應(yīng)該是很多人的選擇;但是使用第三方控件牡借,也會(huì)帶來(lái)額外的一些問(wèn)題决帖,比如三方控件過(guò)于的重和龐大,功能過(guò)于復(fù)雜蓖捶,有時(shí)候我們?yōu)榱四骋粋€(gè)簡(jiǎn)單的功能,可能要引入一些其他無(wú)用的代碼扁远,這是不好的俊鱼。
因此,我們需要一款輕量級(jí)的庫(kù)畅买,他能夠讓我們更自由的定制下拉加載和上拉刷新時(shí)的效果并闲,同時(shí)又不會(huì)過(guò)于的繁重。
UltimateRefreshView
一個(gè)讓你關(guān)注于刷新動(dòng)畫(huà)效果的下拉刷新谷羞,上拉加載的控件帝火。
老規(guī)矩,先來(lái)看看效果湃缎。
這里放了幾張能看清的動(dòng)畫(huà)犀填,這些動(dòng)畫(huà)效果完全不同;但是只用一個(gè)UltimateRefreshView就夠了嗓违。更多動(dòng)畫(huà)效果請(qǐng)到Github查看
功能
- 支持ListView九巡,RecycleView,ScrollView蹂季,WebView
- 一行代碼指定是否支持上拉加載冕广,下拉刷新
- 自由定制刷新時(shí)頭部和尾部的動(dòng)畫(huà)效果
使用方式
首先,是引入庫(kù)
compile 'com.reoobter:ultrapullview:1.0.0'
其次偿洁,實(shí)現(xiàn)各自的動(dòng)畫(huà)效果
這里我們就以美團(tuán)APP頂部下拉刷新的動(dòng)畫(huà)為例來(lái)看看如何實(shí)現(xiàn)動(dòng)畫(huà)效果
meituan_header_refresh_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:background="@color/white"
android:orientation="vertical">
<ImageView
android:id="@+id/loading"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:scaleX="0"
android:scaleY="0"
android:src="@drawable/pull_image"/>
</LinearLayout>
這個(gè)布局文件很簡(jiǎn)單撒汉,整個(gè)只有一個(gè)ImageView。我們的實(shí)現(xiàn)思路涕滋,就是在不同的結(jié)點(diǎn)修改ImageView的內(nèi)容睬辐,從而呈現(xiàn)出整個(gè)下拉刷新時(shí)所有的動(dòng)畫(huà)效果。那么這些結(jié)點(diǎn)是哪些呢?
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 是因?yàn)閁ltimateRefreshView 源碼中對(duì)于滑動(dòng)有0.3的阻尼系數(shù)溉委,為了保持一致
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;
}
}
通過(guò)代碼我們可以總結(jié)出有4個(gè)重要的結(jié)點(diǎn)
- 下拉進(jìn)行時(shí)鹃唯,這個(gè)時(shí)候隨著手指滑動(dòng),整個(gè)頂部的view逐漸顯示出來(lái)
- 頂部view完全被下拉出來(lái)瓣喊,這個(gè)時(shí)候頂部view已經(jīng)完全顯示出來(lái)了坡慌,手指釋放(抬起)后將進(jìn)入下一個(gè)結(jié)點(diǎn)。
- 正在刷新進(jìn)行時(shí)藻三,刷新進(jìn)行時(shí)洪橘,這個(gè)結(jié)點(diǎn)就是刷新動(dòng)畫(huà)執(zhí)行的時(shí)候。
- 刷新完成棵帽,在這個(gè)結(jié)點(diǎn)觸發(fā)了刷新完成的動(dòng)作
為了實(shí)現(xiàn)美團(tuán)頂部刷新動(dòng)畫(huà)的效果熄求,在第一個(gè)結(jié)點(diǎn)我們便開(kāi)始執(zhí)行動(dòng)畫(huà),根據(jù)刷新的位移比逗概,使用scale動(dòng)畫(huà)逐漸放大初始圖片(綠色橢圓)弟晚;在第二個(gè)結(jié)點(diǎn),這個(gè)結(jié)點(diǎn)一般都很短暫逾苫,這個(gè)時(shí)候頂部已經(jīng)完全展示卿城,執(zhí)行了小人偶翻轉(zhuǎn)出現(xiàn)的動(dòng)畫(huà);在第三個(gè)結(jié)點(diǎn)铅搓,這個(gè)結(jié)點(diǎn)一般是比較耗時(shí)的瑟押,在這里用幀動(dòng)畫(huà)播放了一個(gè)小人偶左右搖擺的動(dòng)畫(huà);最后星掰,在第四個(gè)結(jié)點(diǎn)多望,將所有內(nèi)容初始化到下拉之前的狀態(tài),方便下次下拉刷星動(dòng)畫(huà)的執(zhí)行氢烘。這樣就完成了一次下拉刷新的動(dòng)畫(huà)效果怀偷。
下面就可以非常方便的把這個(gè)動(dòng)畫(huà)效果運(yùn)用到View上。
最后播玖,將動(dòng)畫(huà)效果適配到UltimateRefreshView之上
這里就以ListView為例枢纠。
首先是布局實(shí)現(xiàn):
<FrameLayout 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"
android:background="@color/white"
tools:context=".subfragment.ListViewFragment"
>
<com.sak.ultilviewlib.UltimateRefreshView
android:id="@+id/refreshView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"/>
</com.sak.ultilviewlib.UltimateRefreshView>
</FrameLayout>
布局文件很簡(jiǎn)單,將所要實(shí)現(xiàn)的下拉刷新的控件放在UltimateRefreshView控件內(nèi)即可黎棠。
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 List<String> datas = new ArrayList<>();
for (int i = 0; i < PER_PAGE_NUM; i++) {
datas.add("this is item " + i);
}
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(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);
}
});
}
}
這里最關(guān)鍵的代碼就是:
mUltimateRefreshView.setBaseHeaderAdapter(new MeiTuanHeaderAdapter(getContext()));
mUltimateRefreshView.setBaseFooterAdapter();
這樣晋渺,就為L(zhǎng)istView設(shè)定了下拉刷新和上拉加載時(shí)的動(dòng)畫(huà)效果。
為了方便使用脓斩,同時(shí)提供了無(wú)參的setAdapter方法木西,當(dāng)不提供參數(shù)時(shí),將使用默認(rèn)的動(dòng)畫(huà)效果随静。
這里八千,下拉刷新使用了我們剛才定義的MeiTuanHeaderAdapter這個(gè)效果吗讶,上拉加載的動(dòng)畫(huà)效果則使用了默認(rèn)的效果;當(dāng)然恋捆,如果你不需要上拉加載效果或下拉刷新效果的話(huà)照皆,不設(shè)定對(duì)應(yīng)的Adapter即可。即不執(zhí)行(setBaseFooterAdapter和setBaseHeaderAdapter方法)
最后沸停,為mUltimateRefreshView設(shè)置屬性動(dòng)畫(huà)執(zhí)行時(shí)的監(jiān)聽(tīng)器膜毁,這個(gè)回調(diào)方法會(huì)在刷新動(dòng)畫(huà)執(zhí)行時(shí)(也就是上面所說(shuō)的第三個(gè)結(jié)點(diǎn)時(shí)開(kāi)始執(zhí)行時(shí))被調(diào)用,在這個(gè)方法里我們一般會(huì)進(jìn)行網(wǎng)絡(luò)請(qǐng)求或一些耗時(shí)操作愤钾,這里我們用Handler模擬了一個(gè)簡(jiǎn)單的耗時(shí)任務(wù)瘟滨,當(dāng)耗時(shí)操作完成時(shí),調(diào)用對(duì)應(yīng)的刷新完成方法能颁,這樣一次下拉刷新或者是上拉加載就執(zhí)行完了(也就是上面所說(shuō)的第四個(gè)結(jié)點(diǎn))杂瘸。
這里重點(diǎn)討論整個(gè)刷新過(guò)程中動(dòng)畫(huà)效果的實(shí)現(xiàn),下拉或上拉時(shí)數(shù)據(jù)如何刷新不做重點(diǎn)分析伙菊。
當(dāng)我們需要下拉刷新動(dòng)畫(huà)時(shí)败玉,繼承BaseHeaderAdapter類(lèi)并在各個(gè)方法(即下拉事件的各個(gè)結(jié)點(diǎn))按動(dòng)畫(huà)所需的效果,做不同的實(shí)現(xiàn)即可镜硕。
同理运翼,當(dāng)需要上拉加載動(dòng)畫(huà)時(shí),繼承BaseFooterAdapter即可谦疾。BaseFooterAdapter類(lèi)中結(jié)點(diǎn)的定義和BaseHeaderAdapter類(lèi)中完全一致,只不過(guò)滑動(dòng)方向從下拉變成了上拉而已犬金。這里以簡(jiǎn)單模仿一下京東上拉加載時(shí)的動(dòng)畫(huà)效果念恍。
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 十分相似晚顷。這里為了方便峰伙,沒(méi)有做很復(fù)雜的實(shí)現(xiàn),在上拉開(kāi)始執(zhí)行的時(shí)候该默,取巧的用Glide加載了一張gif 的動(dòng)畫(huà)瞳氓,這樣上拉時(shí)就會(huì)呈現(xiàn)一個(gè)簡(jiǎn)單的動(dòng)畫(huà)。現(xiàn)在用戶(hù)在上拉時(shí)一般都會(huì)很快完成分頁(yè)數(shù)據(jù)的加載栓袖,上拉動(dòng)畫(huà)的實(shí)現(xiàn)其實(shí)不用太復(fù)雜匣摘。
更多內(nèi)容可以參考源碼中demo的實(shí)現(xiàn)。
總結(jié)
看以看到裹刮,我們?cè)O(shè)置動(dòng)畫(huà)刷新的方法是在mUltimateRefreshView上執(zhí)行的音榜,也就說(shuō)無(wú)論在mUltimateRefreshView內(nèi)部嵌套的是ListView,還是RecycleView或者ScrollView都一樣捧弃。頭部和尾部的動(dòng)畫(huà)效果完全不受嵌套子View的影響赠叼。因此擦囊,我們可以將更多的精力用于實(shí)現(xiàn)頭部和尾部刷新時(shí)絢麗的動(dòng)畫(huà)效果,而不再糾結(jié)于各種滑動(dòng)事件的處理嘴办。
頭部和尾部動(dòng)畫(huà)的實(shí)現(xiàn)瞬场,完全和mUltimateRefreshView本身是分離的,極大的減少了耦合涧郊。需要什么樣的動(dòng)畫(huà)贯被,通過(guò)setBaseFooterAdapter和setBaseHeaderAdapter方法進(jìn)行設(shè)置即可,十分靈活底燎,不同的頭部和尾部動(dòng)畫(huà)可以自由搭配刃榨。
所以,趕緊來(lái)玩吧双仍!
最后枢希,再次給出Github 源碼。歡迎感興趣的同學(xué) star & fork 朱沃。