android仿美团下拉动画,Android从零撸美团(二) - 仿美团下拉刷新自定义动画

这是【从零撸美团】系列文章第二篇。

项目地址:github.com/cachecats/L…

今天写了下拉刷新,框架用的是 SmartRefreshLayout ,不为啥,因为

Github 上它有 9.5k 个 star,中文支持好节省时间。

先上图:36d59f0b7e2e47f2f83ba57cc9354ec2.gif

一、分析

美团的下拉加载动画初看挺简单的,就一个卖萌的小人。细看的话还稍微有点复杂,一共有三个状态。

刚开始下拉的时候,小脑袋从小变大的过程。

下拉到一定程度但还没松手,小人翻了个跟头直到完全出现。再往下拉保持最后完全出现的状态。

松开后左右摇头卖萌直至加载结束回弹回去。

这是三个动画啊!真佩服这些大厂,简单的加载动画都搞这么复杂。。

分析完过程该想怎么实现了。

二、反编译app看实现原理

最简单直白的方法就是反编译美团app,虽然看不到代码但资源文件能还原出来,图片和 xml 文件完美还原。

152ac0cb1883d8c82e70def5556c2363.png

反编译工具是 apktool,使用方法官网上都有就不啰嗦了。

大部分图片都放在 res/drawable-xhdpi-v4 和 res/drawable-xxhdpi-v4 两个文件夹内,仔细找下能看到多张连续的 loading 图片。这里给美团程序猿点个赞,文件命名都很规范,很好找~

1da3ac9d2f7a7d3bcada216482b0a3b0.png

a98d8a05bd6a886f6b21a0126a6069cc.png

看到图片后知道原来它用的是最普通的帧动画啊,也不是太复杂。

拿到资源图片,知道实现原理,就开工吧!

三、实现动画效果

首先自定义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

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:background="@color/white"

android:gravity="center"

android:padding="5dp">

android:id="@+id/iv_refresh_header"

android:layout_width="41dp"

android:layout_height="54dp"

android:scaleX="0"

android:scaleY="0"

android:translationY="0dp" />

复制代码

anim_pull_end.xml

android:oneshot="true">

android:drawable="@drawable/commonui_pull_end_image_frame_01"

android:duration="100" />

android:drawable="@drawable/commonui_pull_end_image_frame_02"

android:duration="100" />

android:drawable="@drawable/commonui_pull_end_image_frame_03"

android:duration="100" />

android:drawable="@drawable/commonui_pull_end_image_frame_04"

android:duration="100" />

android:drawable="@drawable/commonui_pull_end_image_frame_05"

android:duration="100" />

复制代码

anim_pull_refreshing.xml

xmlns:android="http://schemas.android.com/apk/res/android">

复制代码

好啦,以上就是仿美团下拉刷新自定义动画的实现过程。

源码地址:github.com/cachecats/L…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值