SmartRefreshLayout 仿美团下拉刷新

先上图:

今天写了下拉刷新,框架用的是 SmartRefreshLayout 

从零撸美团项目地址:https://github.com/cachecats/LikeMeiTuan

一、分析

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

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

二、反编译app看实现原理

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

反编译工具是 apktool

大部分图片都放在 res/drawable-xhdpi-v4res/drawable-xxhdpi-v4 两个文件夹内,

仔细找下能看到多张连续的 loading 图片。

看到图片后知道原来它用的是最普通的帧动画拿到资源图片,知道了实现原理

三、实现动画效果

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

/**
 * @author Amarao
 * @date 2018/9/12
 * 下拉刷新动画
 * 调用举例:smartRefresh.setRefreshHeader(new CustomRefreshHeader(this));
 */
public class CustomRefreshHeader extends LinearLayout implements RefreshHeader {

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

    private boolean hasSetPullDownAnim = false;

    public CustomRefreshHeader(Context context) {
        super(context,null,0);
        View view = View.inflate(context, R.layout.custom_refresh_header,this);
        mImage = (ImageView) view.findViewById(R.id.hot_iv_refresh_header);
    }

    public CustomRefreshHeader(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs,0);
    }

    public CustomRefreshHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        View view = View.inflate(context, R.layout.custom_refresh_header,this);
        mImage = (ImageView) view.findViewById(R.id.hot_iv_refresh_header);
        LogUtils.e("CustomRefreshHeader3");
    }

    /**
     * 获取真实视图(必须返回,不能为null)
     */
    @NonNull
    @Override
    public View getView() {
        return this;
    }

    /**
     * 获取变换方式(必须指定一个:平移、拉伸、固定、全屏)
     */
    @NonNull
    @Override
    public SpinnerStyle getSpinnerStyle() {
        return SpinnerStyle.Translate;
    }

    /**
     * 设置主题颜色 (如果自定义的Header没有注意颜色,本方法可以什么都不处理)
     * @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor
     */
    @Override
    public void setPrimaryColors(int... colors) {

    }


    /**
     * 尺寸定义初始化完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用)
     * @param kernel RefreshKernel 核心接口(用于完成高级Header功能)
     * @param height HeaderHeight or FooterHeight
     * @param maxDragHeight 最大拖动高度
     */
    @Override
    public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {

    }

    /**
     * 开始动画(开始刷新或者开始加载动画)
     * @param refreshLayout RefreshLayout
     * @param height HeaderHeight or FooterHeight
     * @param maxDragHeight 最大拖动高度
     */
    @Override
    public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {

    }

    /**
     * 状态改变时调用。在这里切换第三阶段的动画卖萌小人
     * @param refreshLayout
     * @param oldState
     * @param newState
     */
    @Override
    public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
        switch (newState) {
            case PullDownToRefresh:
                /*
                 * 下拉刷新开始。正在下拉还没松手时调用
                 * 每次重新下拉时,将图片资源重置为小人的大脑袋*/
                mImage.setImageResource(R.drawable.anim_pull_end);
                break;
            case Refreshing:
                /*
                 * 正在刷新。只调用一次
                 * 状态切换为正在刷新状态时,设置图片资源为小人卖萌的动画并开始执行*/
                mImage.setImageResource(R.drawable.anim_pull_refreshing);
                refreshingAnim = (AnimationDrawable) mImage.getDrawable();
                refreshingAnim.start();
                break;
            case ReleaseToRefresh:

                break;
        }
    }

    /**
     * 手指拖动下拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing)
     * @param isDragging true 手指正在拖动 false 回弹动画
     * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight )
     * @param offset 下拉的像素偏移量  0 - offset - (footerHeight+maxDragHeight)
     * @param height 高度 HeaderHeight or FooterHeight
     * @param maxDragHeight 最大拖动高度
     */
    @Override
    public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {
        //LogUtils.e("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;
            }
        }
    }

    /**
     * 动画结束
     * @param refreshLayout RefreshLayout
     * @param success 数据是否成功刷新或加载
     * @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态
     */
    @Override
    public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
        // 结束动画
        if (pullDownAnim != null && pullDownAnim.isRunning()) {
            pullDownAnim.stop();
        }
        if (refreshingAnim != null && refreshingAnim.isRunning()) {
            refreshingAnim.stop();
        }
        //重置状态
        hasSetPullDownAnim = false;

        /*if (success ){
            LogUtils.e("TRUE");
        }else {
            LogUtils.e("FALSE");
        }*/
        return 0;
    }

    /**
     * 释放时刻(调用一次,将会触发加载)
     * @param refreshLayout RefreshLayout
     * @param height 高度 HeaderHeight or FooterHeight
     * @param maxDragHeight 最大拖动高度
     */
    @Override
    public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {

    }

    @Override
    public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {

    }

    /**
     * 是否支持水平方向的拖动(将会影响到onHorizontalDrag的调用)
     * @return 水平拖动需要消耗更多的时间和资源,所以如果不支持请返回false
     */
    @Override
    public boolean isSupportHorizontalDrag() {
        return false;
    }

}

逻辑主要在 onStateChanged()onMoving() 方法里,代码中注释写的很详细。
切换状态原理是每次都给 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>

好啦,以上就是仿美团下拉刷新自定义动画的实现过程。
源码地址:https://github.com/cachecats/LikeMeiTuan

【从零撸美团】专题地址:https://www.jianshu.com/c/6fa186bd2f18


 

猜你喜欢

转载自blog.csdn.net/jinmie0193/article/details/82811187