快來(lái)玩下拉刷新動(dòng)畫(huà)

前言

說(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)看看效果湃缎。

美團(tuán)下拉刷新動(dòng)畫(huà)
QQ 下拉動(dòng)畫(huà)
WebView下拉刷新動(dòng)畫(huà)

這里放了幾張能看清的動(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 朱沃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苞轿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逗物,更是在濱河造成了極大的恐慌搬卒,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翎卓,死亡現(xiàn)場(chǎng)離奇詭異契邀,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)失暴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)坯门,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人逗扒,你說(shuō)我怎么就攤上這事古戴。” “怎么了矩肩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵现恼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我黍檩,道長(zhǎng)叉袍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任刽酱,我火速辦了婚禮畦韭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肛跌。我一直安慰自己艺配,他們只是感情好察郁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著转唉,像睡著了一般皮钠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赠法,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天麦轰,我揣著相機(jī)與錄音,去河邊找鬼砖织。 笑死款侵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侧纯。 我是一名探鬼主播新锈,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼眶熬!你這毒婦竟也來(lái)了妹笆?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤娜氏,失蹤者是張志新(化名)和其女友劉穎拳缠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贸弥,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窟坐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绵疲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哲鸳。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖最岗,靈堂內(nèi)的尸體忽然破棺而出帕胆,到底是詐尸還是另有隱情朝捆,我是刑警寧澤般渡,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站芙盘,受9級(jí)特大地震影響驯用,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜儒老,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一蝴乔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧驮樊,春花似錦薇正、人聲如沸片酝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雕沿。三九已至,卻和暖如春猴仑,著一層夾襖步出監(jiān)牢的瞬間审轮,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工辽俗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疾渣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓崖飘,卻偏偏與公主長(zhǎng)得像榴捡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坐漏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容