這是【從零擼美團】系列文章第二篇。 專案地址:github.com/cachecats/L…
Android從零擼美團(一) - 統一管理 Gradle 依賴 提取到單獨檔案中
Android從零擼美團(三) - Android多標籤tab滑動切換 - 自定義View快速實現高度定製封裝
Android從零擼美團(四) - 美團首頁佈局解析及實現 - Banner+自定義View+SmartRefreshLayout下拉重新整理上拉載入更多
今天寫了下拉重新整理,框架用的是 SmartRefreshLayout ,不為啥,因為
Github 上它有 9.5k
個 star
,中文支援好節省時間。
先上圖:
一、分析
美團的下拉載入動畫初看挺簡單的,就一個賣萌的小人。細看的話還稍微有點複雜,一共有三個狀態。
- 剛開始下拉的時候,小腦袋從小變大的過程。
- 下拉到一定程度但還沒鬆手,小人翻了個跟頭直到完全出現。再往下拉保持最後完全出現的狀態。
- 鬆開後左右搖頭賣萌直至載入結束回彈回去。
這是三個動畫啊!真佩服這些大廠,簡單的載入動畫都搞這麼複雜。。
分析完過程該想怎麼實現了。
二、反編譯app看實現原理
最簡單直白的方法就是反編譯美團app,雖然看不到程式碼但資原始檔能還原出來,圖片和 xml 檔案完美還原。
反編譯工具是 apktool,使用方法官網上都有就不囉嗦了。
大部分圖片都放在 res/drawable-xhdpi-v4
和 res/drawable-xxhdpi-v4
兩個資料夾內,仔細找下能看到多張連續的 loading 圖片。這裡給美團程式猿點個贊,檔案命名都很規範,很好找~
看到圖片後知道原來它用的是最普通的幀動畫啊,也不是太複雜。 拿到資源圖片,知道實現原理,就開工吧!
三、實現動畫效果
首先自定義View CustomRefreshHeader
繼承自 LinearLayout
,並實現 SmartRefreshLayout
的 RefreshHeader
介面。
然後主要就是重寫 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…