SmartRefreshLayout lottie 打造自己的刷新動畫

前言:

看著自己簡書創(chuàng)建時間2016年,CSDN2015年虑鼎。自己在這最好的年華居然連技術(shù)文章或者論文都沒發(fā)表過伟端,怎么對的起我看過前輩們的心血和付出杠娱。在此我還是決定了今后的總結(jié)方向不在是單一的筆記和書本廷区,還是為IT大軍做一份貢獻嬉挡。

正文:

寫這篇文章主要是為了當前日益增多三方庫和開發(fā)中的一些日常造輪子胚迫,加自己的經(jīng)驗總結(jié)

效果圖:

效果圖.gif

庫:

SmartRefreshLayout
Lottie
LottieFiles

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
 <com.scwang.smartrefresh.layout.SmartRefreshLayout
        android:id="@+id/sfl"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.example.xxx.lottie.DesginLottieHeadRefresh
            android:id="@+id/headRefresh"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:gravity="center"
            android:text="測試代碼"
            android:layout_height="wrap_content" />
    </com.scwang.smartrefresh.layout.SmartRefreshLayout>
</LinearLayout>

簡單的布局和自定義的HeadRefresh

DesginLottieHeadRefresh:

public class DesginLottieHeadRefresh extends ViewGroup implements RefreshHeader {
    private LottieAnimationView lav;
    private String asset_loading_json = "desgin/newAnimation.json";
    //中心點
    private int mCircleDiameter;
    @VisibleForTesting
    private static final int CIRCLE_DIAMETER = 160;
    private RefreshState mState;

    public DesginLottieHeadRefresh(Context context) {
        this(context, null);
    }

    public DesginLottieHeadRefresh(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (getChildCount() == 0) {
            return;
        }
        final int width = getMeasuredWidth();
        int lottieWidth = lav.getMeasuredWidth();
        int lottieHeight = lav.getMeasuredHeight();
        int leftLav = width / 2 - lottieWidth / 2;
        int topLav = 0;
        lav.layout(leftLav, topLav, leftLav + lottieWidth, topLav + lottieHeight / 2);
    }

    private void initView(Context context) {
        final DisplayMetrics metrics = getResources().getDisplayMetrics();
        mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);
        lav = new LottieAnimationView(context);
        lav.setAnimation(asset_loading_json);
        lav.loop(true);
        addView(lav);

    }

    @NonNull
    @Override
    public View getView() {
        return this;
    }

    @NonNull
    @Override
    public SpinnerStyle getSpinnerStyle() {
        return SpinnerStyle.MatchLayout;
    }

    @Override
    public void setPrimaryColors(int... colors) {

    }

    @Override
    public void onInitialized(@NonNull RefreshKernel kernel, int height, int extendHeight) {

    }

    @Override
    public void onMoving(boolean isDragging, float percent, int offset, int height, int extendHeight) {
    }

    @Override
    public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int extendHeight) {
    }

    @Override
    public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int extendHeight) {
        lav.playAnimation();
    }

    @Override
    public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
//        lav.clearAnimation();
        if (lav != null) {
            lav.cancelAnimation();
            lav.clearAnimation();
        }
        return 0;
    }

    @Override
    public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {

    }

    @Override
    public boolean isSupportHorizontalDrag() {
        return false;
    }

    @Override
    public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
        mState = newState;
        switch (newState) {
            case None:
                lav.setFrame(0);
                lav.setProgress(0);
                break;
            case PullDownToRefresh:
                lav.setVisibility(View.VISIBLE);
                break;
            case PullDownCanceled:
                break;
            case ReleaseToRefresh:
                lav.setVisibility(View.VISIBLE);
                break;
            case Refreshing:
                break;
            case RefreshFinish:
                lav.setVisibility(View.GONE);
                break;
        }

    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
//        canvas.save();
//        lav.draw(canvas);
//        canvas.restore();
    }

    @Override
    public void invalidateDrawable(@NonNull Drawable drawable) {
        super.invalidateDrawable(drawable);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec));
        lav.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY));
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {
        }
    }

實現(xiàn)步驟:

1.implements RefreshHead View或者ViewGroup
2.實現(xiàn) onMeasure-onLayout 或者實現(xiàn)onDraw(Canvas canvas)
3.Lottiview 加載assets目錄下面 .json動畫完成初步顯示
4.根據(jù)Lottie和SmartRefreshLayout api 完成動畫連貫和狀態(tài)更新

RefreshHead > RefreshInternal

 /**
     * 獲取實體視圖
     * @return 實體視圖
     */
    @NonNull
    View getView();

    /**
     * 獲取變換方式 {@link SpinnerStyle} 必須返回 非空
     * @return 變換方式
     */
    @NonNull
    SpinnerStyle getSpinnerStyle();

    /**
     * 設(shè)置主題顏色
     * @param colors 對應(yīng)Xml中配置的 srlPrimaryColor srlAccentColor
     */
    void setPrimaryColors(@ColorInt int... colors);

    /**
     * 尺寸定義完成 (如果高度不改變(代碼修改:setHeader)喷户,只調(diào)用一次, 在RefreshLayout#onMeasure中調(diào)用)
     * @param kernel RefreshKernel
     * @param height HeaderHeight or FooterHeight
     * @param extendHeight extendHeaderHeight or extendFooterHeight
     */
    void onInitialized(@NonNull RefreshKernel kernel, int height, int extendHeight);
    /**
     * 手指拖動下拉(會連續(xù)多次調(diào)用)
     * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight )
     * @param offset 下拉的像素偏移量  0 - offset - (footerHeight+extendHeight)
     * @param height 高度 HeaderHeight or FooterHeight
     * @param extendHeight 擴展高度  extendHeaderHeight or extendFooterHeight
     */
    void onPulling(float percent, int offset, int height, int extendHeight);
    /**
     * 手指釋放之后的持續(xù)動畫(會連續(xù)多次調(diào)用)
     * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight )
     * @param offset 下拉的像素偏移量  0 - offset - (footerHeight+extendHeight)
     * @param height 高度 HeaderHeight or FooterHeight
     * @param extendHeight 擴展高度  extendHeaderHeight or extendFooterHeight
     */
    void onReleasing(float percent, int offset, int height, int extendHeight);

    /**
     * 釋放時刻(調(diào)用一次,將會觸發(fā)加載)
     * @param refreshLayout RefreshLayout
     * @param height 高度 HeaderHeight or FooterHeight
     * @param extendHeight 擴展高度  extendHeaderHeight or extendFooterHeight
     */
    void onReleased(RefreshLayout refreshLayout, int height, int extendHeight);

    /**
     * 開始動畫
     * @param refreshLayout RefreshLayout
     * @param height HeaderHeight or FooterHeight
     * @param extendHeight extendHeaderHeight or extendFooterHeight
     */
    void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int extendHeight);

    /**
     * 動畫結(jié)束
     * @param refreshLayout RefreshLayout
     * @param success 數(shù)據(jù)是否成功刷新或加載
     * @return 完成動畫所需時間 如果返回 Integer.MAX_VALUE 將取消本次完成事件访锻,繼續(xù)保持原有狀態(tài)
     */
    int onFinish(@NonNull RefreshLayout refreshLayout, boolean success);

    /**
     * 水平方向的拖動
     * @param percentX 下拉時褪尝,手指水平坐標對屏幕的占比(0 - percentX - 1)
     * @param offsetX 下拉時,手指水平坐標對屏幕的偏移(0 - offsetX - LayoutWidth)
     * @param offsetMax 最大的偏移量
     */
    void onHorizontalDrag(float percentX, int offsetX, int offsetMax);

    /**
     * 是否支持水平方向的拖動(將會影響到onHorizontalDrag的調(diào)用)
     * @return 水平拖動需要消耗更多的時間和資源朗若,所以如果不支持請返回false
     */
    boolean isSupportHorizontalDrag();

代碼很簡單恼五,源碼中有中文注解就不一一說明


LottileAnimation

Lottie官方使用手冊

結(jié)合官網(wǎng)和部分源碼很好實現(xiàn)Lottie在RefreshHead 中的實現(xiàn) !?扌浮灾馒!

重點:

View,ViewGroup的生命周期和Wind上面的渲染過程,剛開始的時候去繼承View拿到當前.json動畫的寬高和在onMeasure中一直是0,0后來改為ViewGroup 子類重新自測measure寬和高得到的也是0,0。這下搞的我翻了波筆記本
筆記本mark入口

后來才決定改為CIRCLE_DIAMETER 和mCircleDiameter根據(jù)自己的分辨率和中心點來繪制動畫.json的大小
(主要為了適配動畫在不同分辨率手機里面的效果)

        final DisplayMetrics metrics = getResources().getDisplayMetrics();
        mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);

小伙伴也可以使用AT_MOST來根據(jù)父布局的指定獲取當前自定義View的寬和高

OK到這里基本完善了Lottie動畫能在Header里面指定的位置跳動了,接下run了一次發(fā)現(xiàn)動畫確實是在Head里面跳動遣总,但是因為設(shè)置了looper(true)的屬性本身是不和Refresh onRefreshing 時間沖突,但是后面再次下拉刷新的時候出現(xiàn)了動畫的幀數(shù)不是原來第一幀睬罗,這下糾結(jié)了,我一般不喜歡手動導(dǎo)入三方源碼修改別人的源碼主要以前被(XXX)坑哭過旭斥,升級一次容达,我基本要上重構(gòu)一次我的項目。好在快速瀏覽了一遍LottieAnimationView的源碼,好在和我猜測的一樣 LottieDraw 和LottieAnimator 果然是根據(jù)Frame幀來實現(xiàn)動畫的過程


LottieCom.png

那么現(xiàn)在來了不是給我機會為所欲為最大幀和最小幀 整個圖片繪制過程Progress等

 switch (newState) {
            case None:
                lav.setFrame(0);
                lav.setProgress(0);
                break;
            case PullDownToRefresh:
                lav.setVisibility(View.VISIBLE);
                break;
            case PullDownCanceled:
                break;
            case ReleaseToRefresh:
                lav.setVisibility(View.VISIBLE);
                break;
            case Refreshing:
                break;
            case RefreshFinish:
                lav.setVisibility(View.GONE);
                break;
        }

配合上層接口對代碼做了最后的處理
喜歡效果小伙伴可以在去關(guān)注SmartRefreshLayout refresh-heads 和fresh-foot代碼的實現(xiàn)垂券,其中Vector向量和對View花盐,ViewGroup,Drawable繪制 是很不錯的學(xué)習(xí)源碼羡滑。
OK 效果做出來了


經(jīng)驗分享:

記得幾年前做Android開發(fā)的時拿到第三方庫或者框架很是頭疼和煩躁,后面接觸多了能心平氣和的寫代碼算芯,反而覺得開發(fā)過程在別人車輪下面還是相對容易的柒昏,api 知識體系清楚的情況下,功能實現(xiàn)反而很輕松熙揍! 時代在進步职祷,人也在進步,學(xué)習(xí)是IT的必經(jīng)之路届囚,找準自己愛好堅持下去就行有梆。
以后博主每周五分享一篇博客(Kotilin React-native Android Flutter Java)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市意系,隨后出現(xiàn)的幾起案子泥耀,更是在濱河造成了極大的恐慌,老刑警劉巖昔字,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爆袍,死亡現(xiàn)場離奇詭異,居然都是意外死亡作郭,警方通過查閱死者的電腦和手機陨囊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夹攒,“玉大人蜘醋,你說我怎么就攤上這事∮匠ⅲ” “怎么了压语?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長编检。 經(jīng)常有香客問我胎食,道長,這世上最難降的妖魔是什么允懂? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任厕怜,我火速辦了婚禮,結(jié)果婚禮上蕾总,老公的妹妹穿的比我還像新娘粥航。我一直安慰自己,他們只是感情好生百,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布递雀。 她就那樣靜靜地躺著,像睡著了一般蚀浆。 火紅的嫁衣襯著肌膚如雪缀程。 梳的紋絲不亂的頭發(fā)上搜吧,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音杠输,去河邊找鬼赎败。 笑死,一個胖子當著我的面吹牛蠢甲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播据忘,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鹦牛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了勇吊?” 一聲冷哼從身側(cè)響起曼追,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎汉规,沒想到半個月后礼殊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡针史,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年晶伦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啄枕。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡婚陪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出频祝,到底是詐尸還是另有隱情泌参,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布常空,位于F島的核電站沽一,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漓糙。R本人自食惡果不足惜铣缠,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望兼蜈。 院中可真熱鬧攘残,春花似錦、人聲如沸为狸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辐棒。三九已至病曾,卻和暖如春牍蜂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泰涂。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工鲫竞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逼蒙。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓从绘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親是牢。 傳聞我的和親對象是個殘疾皇子僵井,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

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