1. 程式人生 > >Android從零擼美團(二) - 仿美團下拉重新整理自定義動畫

Android從零擼美團(二) - 仿美團下拉重新整理自定義動畫

這是【從零擼美團】系列文章第二篇。 專案地址:github.com/cachecats/L…

今天寫了下拉重新整理,框架用的是 SmartRefreshLayout ,不為啥,因為 Github 上它有 9.5kstar,中文支援好節省時間。

先上圖:

在這裡插入圖片描述

一、分析

美團的下拉載入動畫初看挺簡單的,就一個賣萌的小人。細看的話還稍微有點複雜,一共有三個狀態。

  1. 剛開始下拉的時候,小腦袋從小變大的過程。
  2. 下拉到一定程度但還沒鬆手,小人翻了個跟頭直到完全出現。再往下拉保持最後完全出現的狀態。
  3. 鬆開後左右搖頭賣萌直至載入結束回彈回去。

這是三個動畫啊!真佩服這些大廠,簡單的載入動畫都搞這麼複雜。。

分析完過程該想怎麼實現了。

二、反編譯app看實現原理

最簡單直白的方法就是反編譯美團app,雖然看不到程式碼但資原始檔能還原出來,圖片和 xml 檔案完美還原。

在這裡插入圖片描述

反編譯工具是 apktool,使用方法官網上都有就不囉嗦了。

大部分圖片都放在 res/drawable-xhdpi-v4res/drawable-xxhdpi-v4 兩個資料夾內,仔細找下能看到多張連續的 loading 圖片。這裡給美團程式猿點個贊,檔案命名都很規範,很好找~

在這裡插入圖片描述

在這裡插入圖片描述

看到圖片後知道原來它用的是最普通的幀動畫啊,也不是太複雜。 拿到資源圖片,知道實現原理,就開工吧!

三、實現動畫效果

首先自定義View CustomRefreshHeader 繼承自 LinearLayout,並實現 SmartRefreshLayoutRefreshHeader 介面。 然後主要就是重寫 RefreshHeader 介面中的方法,裡面提供了下拉重新整理時不同階段的回撥,找到對應的方法碼程式碼就好。

public class CustomRefreshHeader extends LinearLayout implements RefreshHeader {

    private ImageView mImage;
    private AnimationDrawable pullDownAnim;
    private AnimationDrawable refreshingAnim;

    private boolean hasSetPullDownAnim = false
; public CustomRefreshHeader(Context context) { this(context, null, 0); } public CustomRefreshHeader(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CustomRefreshHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); View view = View.inflate(context, R.layout.widget_custom_refresh_header, this); mImage = (ImageView) view.findViewById(R.id.iv_refresh_header); } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Translate; } @Override public void onStartAnimator(RefreshLayout layout, int height, int extendHeight) { } /** * 狀態改變時呼叫。在這裡切換第三階段的動畫賣萌小人 * @param refreshLayout * @param oldState * @param newState */ @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { switch (newState) { case PullDownToRefresh: //下拉重新整理開始。正在下拉還沒鬆手時呼叫 //每次重新下拉時,將圖片資源重置為小人的大腦袋 mImage.setImageResource(R.drawable.commonui_pull_image); break; case Refreshing: //正在重新整理。只調用一次 //狀態切換為正在重新整理狀態時,設定圖片資源為小人賣萌的動畫並開始執行 mImage.setImageResource(R.drawable.anim_pull_refreshing); refreshingAnim = (AnimationDrawable) mImage.getDrawable(); refreshingAnim.start(); break; case ReleaseToRefresh: break; } } /** * 下拉過程中不斷呼叫此方法。第一階段從小變大的小人頭動畫,和第二階段翻跟頭動畫都在這裡設定 */ @Override public void onPullingDown(float percent, int offset, int headerHeight, int extendHeight) { Logger.d("percent: " + percent); // 下拉的百分比小於100%時,不斷呼叫 setScale 方法改變圖片大小 if (percent < 1) { mImage.setScaleX(percent); mImage.setScaleY(percent); //是否執行過翻跟頭動畫的標記 if (hasSetPullDownAnim) { hasSetPullDownAnim = false; } } //當下拉的高度達到Header高度100%時,開始載入正在下拉的初始動畫,即翻跟頭 if (percent >= 1.0) { //因為這個方法是不停呼叫的,防止重複 if (!hasSetPullDownAnim) { mImage.setImageResource(R.drawable.anim_pull_end); pullDownAnim = (AnimationDrawable) mImage.getDrawable(); pullDownAnim.start(); hasSetPullDownAnim = true; } } } /** * 動畫結束後呼叫 */ @Override public int onFinish(RefreshLayout layout, boolean success) { // 結束動畫 if (pullDownAnim != null && pullDownAnim.isRunning()) { pullDownAnim.stop(); } if (refreshingAnim != null && refreshingAnim.isRunning()) { refreshingAnim.stop(); } //重置狀態 hasSetPullDownAnim = false; return 0; } @Override public void onReleasing(float percent, int offset, int headerHeight, int extendHeight) { } @Override public void onRefreshReleased(RefreshLayout layout, int headerHeight, int extendHeight) { } @Override public void setPrimaryColors(int... colors) { } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public boolean isSupportHorizontalDrag() { return false; } } 複製程式碼

邏輯主要在 onStateChanged()onPullingDown() 方法裡,程式碼中註釋寫的很詳細。 切換狀態原理是每次都給 ImageView 設定對應的資源圖片或動畫檔案,然後得到 AnimationDrawable 開啟動畫,如下:

mImage.setImageResource(R.drawable.anim_pull_end);
pullDownAnim = (AnimationDrawable) mImage.getDrawable();
pullDownAnim.start();
複製程式碼

程式碼中呼叫:

smartRefreshLayout.setRefreshHeader(new CustomRefreshHeader(getActivity()));
        smartRefreshLayout.setOnRefreshLoadmoreListener(new OnRefreshLoadmoreListener() {
            @Override
            public void onLoadmore(RefreshLayout refreshlayout) {
                Logger.d("onLoadmore");
                smartRefreshLayout.finishLoadmore(2000, true);
            }

            @Override
            public void onRefresh(RefreshLayout refreshlayout) {
                Logger.d("onRefresh");
                smartRefreshLayout.finishRefresh(2000, true);
            }
        });
複製程式碼

貼出資源佈局檔案: widget_custom_refresh_header.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:background="@color/white"
    android:gravity="center"
    android:padding="5dp">

    <ImageView
        android:id="@+id/iv_refresh_header"
        android:layout_width="41dp"
        android:layout_height="54dp"
        android:scaleX="0"
        android:scaleY="0"
        android:translationY="0dp" />

</LinearLayout>
複製程式碼

anim_pull_end.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">

    <item
        android:drawable="@drawable/commonui_pull_end_image_frame_01"
        android:duration="100" />

    <item
        android:drawable="@drawable/commonui_pull_end_image_frame_02"
        android:duration="100" />

    <item
        android:drawable="@drawable/commonui_pull_end_image_frame_03"
        android:duration="100" />

    <item
        android:drawable="@drawable/commonui_pull_end_image_frame_04"
        android:duration="100" />

    <item
        android:drawable="@drawable/commonui_pull_end_image_frame_05"
        android:duration="100" />

</animation-list>
複製程式碼

anim_pull_refreshing.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list android:oneshot="false"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:duration="50" android:drawable="@drawable/commonui_refreshing_image_frame_01" />
    <item android:duration="50" android:drawable="@drawable/commonui_refreshing_image_frame_02" />
    <item android:duration="50" android:drawable="@drawable/commonui_refreshing_image_frame_03" />
    <item android:duration="50" android:drawable="@drawable/commonui_refreshing_image_frame_02" />
    <item android:duration="50" android:drawable="@drawable/commonui_refreshing_image_frame_05" />
    <item android:duration="50" android:drawable="@drawable/commonui_refreshing_image_frame_06" />
    <item android:duration="50" android:drawable="@drawable/commonui_refreshing_image_frame_07" />
    <item android:duration="50" android:drawable="@drawable/commonui_refreshing_image_frame_06" />
</animation-list>
複製程式碼

好啦,以上就是仿美團下拉重新整理自定義動畫的實現過程。 原始碼地址:github.com/cachecats/L…