前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android从零撸美团(二) - 仿美团下拉刷新自定义动画

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

作者头像
solocoder
发布2022-04-06 13:01:13
5050
发布2022-04-06 13:01:13
举报
文章被收录于专栏:大前端客栈

这是【从零撸美团】系列文章第二篇。 项目地址:https://github.com/cachecats/LikeMeiTuan

今天写了下拉刷新,框架用的是 SmartRefreshLayout ,不为啥,因为 Github 上它有 9.5kstar,中文支持好节省时间。

先上图:

一、分析

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

  1. 刚开始下拉的时候,小脑袋从小变大的过程。
  2. 下拉到一定程度但还没松手,小人翻了个跟头直到完全出现。再往下拉保持最后完全出现的状态。
  3. 松开后左右摇头卖萌直至加载结束回弹回去。

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

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

二、反编译app看实现原理

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

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

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

看到图片后知道原来它用的是最普通的帧动画啊,也不是太复杂。 拿到资源图片,知道实现原理,就开工吧!

三、实现动画效果

首先自定义View CustomRefreshHeader 继承自 LinearLayout,并实现 SmartRefreshLayoutRefreshHeader 接口。 然后主要就是重写 RefreshHeader 接口中的方法,里面提供了下拉刷新时不同阶段的回调,找到对应的方法码代码就好。

代码语言:javascript
复制
public class CustomRefreshHeader extends LinearLayout implements RefreshHeader {

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

private boolean hasSetPullDownAnim = <span class="hljs-literal">false</span>;

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 <span class="hljs-function"><span class="hljs-title">getView</span></span>() {
    <span class="hljs-built_in">return</span> this;
}

@Override
public SpinnerStyle <span class="hljs-function"><span class="hljs-title">getSpinnerStyle</span></span>() {
    <span class="hljs-built_in">return</span> 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) {
        <span class="hljs-keyword">case</span> PullDownToRefresh: //下拉刷新开始。正在下拉还没松手时调用
            //每次重新下拉时,将图片资源重置为小人的大脑袋
            mImage.setImageResource(R.drawable.commonui_pull_image);
            <span class="hljs-built_in">break</span>;
        <span class="hljs-keyword">case</span> Refreshing: //正在刷新。只调用一次
            //状态切换为正在刷新状态时,设置图片资源为小人卖萌的动画并开始执行
            mImage.setImageResource(R.drawable.anim_pull_refreshing);
            refreshingAnim = (AnimationDrawable) mImage.getDrawable();
            refreshingAnim.start();
            <span class="hljs-built_in">break</span>;
        <span class="hljs-keyword">case</span> ReleaseToRefresh:

            <span class="hljs-built_in">break</span>;
    }
}

/**
 * 下拉过程中不断调用此方法。第一阶段从小变大的小人头动画,和第二阶段翻跟头动画都在这里设置
 */
@Override
public void onPullingDown(<span class="hljs-built_in">float</span> percent, int offset, int headerHeight, int extendHeight) {
    Logger.d(<span class="hljs-string">"percent: "</span> + percent);

    // 下拉的百分比小于100%时,不断调用 <span class="hljs-built_in">set</span>Scale 方法改变图片大小
    <span class="hljs-keyword">if</span> (percent &lt; 1) {
        mImage.setScaleX(percent);
        mImage.setScaleY(percent);

        //是否执行过翻跟头动画的标记
        <span class="hljs-keyword">if</span> (hasSetPullDownAnim) {
            hasSetPullDownAnim = <span class="hljs-literal">false</span>;
        }
    }

    //当下拉的高度达到Header高度100%时,开始加载正在下拉的初始动画,即翻跟头
    <span class="hljs-keyword">if</span> (percent &gt;= 1.0) {
        //因为这个方法是不停调用的,防止重复
        <span class="hljs-keyword">if</span> (!hasSetPullDownAnim) {
            mImage.setImageResource(R.drawable.anim_pull_end);
            pullDownAnim = (AnimationDrawable) mImage.getDrawable();
            pullDownAnim.start();

            hasSetPullDownAnim = <span class="hljs-literal">true</span>;
        }
    }
}

/**
 * 动画结束后调用
 */
@Override
public int onFinish(RefreshLayout layout, boolean success) {
    // 结束动画
    <span class="hljs-keyword">if</span> (pullDownAnim != null &amp;&amp; pullDownAnim.isRunning()) {
        pullDownAnim.stop();
    }
    <span class="hljs-keyword">if</span> (refreshingAnim != null &amp;&amp; refreshingAnim.isRunning()) {
        refreshingAnim.stop();
    }
    //重置状态
    hasSetPullDownAnim = <span class="hljs-literal">false</span>;
    <span class="hljs-built_in">return</span> 0;
}

@Override
public void onReleasing(<span class="hljs-built_in">float</span> percent, int offset, int headerHeight, int extendHeight) {

}

@Override
public void onRefreshReleased(RefreshLayout layout, int headerHeight, int extendHeight) {

}

@Override
public void <span class="hljs-built_in">set</span>PrimaryColors(int... colors) {

}

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

}

@Override
public void onHorizontalDrag(<span class="hljs-built_in">float</span> percentX, int offsetX, int offsetMax) {

}

@Override
public boolean <span class="hljs-function"><span class="hljs-title">isSupportHorizontalDrag</span></span>() {
    <span class="hljs-built_in">return</span> <span class="hljs-literal">false</span>;
}

}

逻辑主要在 onStateChanged()onPullingDown() 方法里,代码中注释写的很详细。 切换状态原理是每次都给 ImageView 设置对应的资源图片或动画文件,然后得到 AnimationDrawable 开启动画,如下:

代码语言:javascript
复制
mImage.setImageResource(R.drawable.anim_pull_end);
pullDownAnim = (AnimationDrawable) mImage.getDrawable();
pullDownAnim.start();

代码中调用:

代码语言:javascript
复制
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

代码语言:javascript
复制
<?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

代码语言:javascript
复制
<?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

代码语言:javascript
复制
<?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>

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

源码地址:https://github.com/cachecats/LikeMeiTuan

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/11/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、分析
  • 二、反编译app看实现原理
  • 三、实现动画效果
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档