这是【从零撸美团】系列文章第二篇。
项目地址:https://github.com/cachecats/LikeMeiTuan
今天写了下拉刷新,框架用的是 SmartRefreshLayout ,不为啥,因为
Github 上它有 9.5k
个 star
,中文支持好节省时间。
先上图:
美团的下拉加载动画初看挺简单的,就一个卖萌的小人。细看的话还稍微有点复杂,一共有三个状态。
这是三个动画啊!真佩服这些大厂,简单的加载动画都搞这么复杂。。
分析完过程该想怎么实现了。
最简单直白的方法就是反编译美团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
anim_pull_end.xml
anim_pull_refreshing.xml
好啦,以上就是仿美团下拉刷新自定义动画的实现过程。
源码地址:https://github.com/cachecats/LikeMeiTuan
【从零撸美团】专题地址:https://www.jianshu.com/c/6fa186bd2f18