SmartRefreshLayout lottie 打造自己的刷新动画

前言:

看着自己创建时间2016年,CSDN2015年。自己在这最好的年华居然连技术文章或者论文都没发表过,怎么对的起我看过前辈们的心血和付出。在此我还是决定了今后的总结方向不在是单一的笔记和书本,还是为IT大军做一份贡献。

正文:

写这篇文章主要是为了当前日益增多三方库和开发中的一些日常造轮子,加自己的经验总结

效果图:

效果图.gif

库:

SmartRefreshLayout
Lottie
LottieFiles

布局:



 
        
        
    

简单的布局和自定义的HeadRefresh

DesginLottieHeadRefresh:

public class DesginLottieHeadRefresh extends ViewGroup implements RefreshHeader {
    private LottieAnimationView lav;
    private String asset_loading_json = "desgin/newAnimation.json";
    //中心点
    private int mCircleDiameter;
    @VisibleForTesting
    private static final int CIRCLE_DIAMETER = 160;
    private RefreshState mState;

    public DesginLottieHeadRefresh(Context context) {
        this(context, null);
    }

    public DesginLottieHeadRefresh(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (getChildCount() == 0) {
            return;
        }
        final int width = getMeasuredWidth();
        int lottieWidth = lav.getMeasuredWidth();
        int lottieHeight = lav.getMeasuredHeight();
        int leftLav = width / 2 - lottieWidth / 2;
        int topLav = 0;
        lav.layout(leftLav, topLav, leftLav + lottieWidth, topLav + lottieHeight / 2);
    }

    private void initView(Context context) {
        final DisplayMetrics metrics = getResources().getDisplayMetrics();
        mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);
        lav = new LottieAnimationView(context);
        lav.setAnimation(asset_loading_json);
        lav.loop(true);
        addView(lav);

    }

    @NonNull
    @Override
    public View getView() {
        return this;
    }

    @NonNull
    @Override
    public SpinnerStyle getSpinnerStyle() {
        return SpinnerStyle.MatchLayout;
    }

    @Override
    public void setPrimaryColors(int... colors) {

    }

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

    }

    @Override
    public void onMoving(boolean isDragging, float percent, int offset, int height, int extendHeight) {
    }

    @Override
    public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int extendHeight) {
    }

    @Override
    public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int extendHeight) {
        lav.playAnimation();
    }

    @Override
    public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
//        lav.clearAnimation();
        if (lav != null) {
            lav.cancelAnimation();
            lav.clearAnimation();
        }
        return 0;
    }

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

    }

    @Override
    public boolean isSupportHorizontalDrag() {
        return false;
    }

    @Override
    public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
        mState = newState;
        switch (newState) {
            case None:
                lav.setFrame(0);
                lav.setProgress(0);
                break;
            case PullDownToRefresh:
                lav.setVisibility(View.VISIBLE);
                break;
            case PullDownCanceled:
                break;
            case ReleaseToRefresh:
                lav.setVisibility(View.VISIBLE);
                break;
            case Refreshing:
                break;
            case RefreshFinish:
                lav.setVisibility(View.GONE);
                break;
        }

    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
//        canvas.save();
//        lav.draw(canvas);
//        canvas.restore();
    }

    @Override
    public void invalidateDrawable(@NonNull Drawable drawable) {
        super.invalidateDrawable(drawable);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec));
        lav.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY));
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {
        }
    }

实现步骤:

1.implements RefreshHead View或者ViewGroup
2.实现 onMeasure-onLayout 或者实现onDraw(Canvas canvas)
3.Lottiview 加载assets目录下面 .json动画完成初步显示
4.根据Lottie和SmartRefreshLayout api 完成动画连贯和状态更新

RefreshHead > RefreshInternal

 /**
     * 获取实体视图
     * @return 实体视图
     */
    @NonNull
    View getView();

    /**
     * 获取变换方式 {@link SpinnerStyle} 必须返回 非空
     * @return 变换方式
     */
    @NonNull
    SpinnerStyle getSpinnerStyle();

    /**
     * 设置主题颜色
     * @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor
     */
    void setPrimaryColors(@ColorInt int... colors);

    /**
     * 尺寸定义完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用)
     * @param kernel RefreshKernel
     * @param height HeaderHeight or FooterHeight
     * @param extendHeight extendHeaderHeight or extendFooterHeight
     */
    void onInitialized(@NonNull RefreshKernel kernel, int height, int extendHeight);
    /**
     * 手指拖动下拉(会连续多次调用)
     * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight )
     * @param offset 下拉的像素偏移量  0 - offset - (footerHeight+extendHeight)
     * @param height 高度 HeaderHeight or FooterHeight
     * @param extendHeight 扩展高度  extendHeaderHeight or extendFooterHeight
     */
    void onPulling(float percent, int offset, int height, int extendHeight);
    /**
     * 手指释放之后的持续动画(会连续多次调用)
     * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight )
     * @param offset 下拉的像素偏移量  0 - offset - (footerHeight+extendHeight)
     * @param height 高度 HeaderHeight or FooterHeight
     * @param extendHeight 扩展高度  extendHeaderHeight or extendFooterHeight
     */
    void onReleasing(float percent, int offset, int height, int extendHeight);

    /**
     * 释放时刻(调用一次,将会触发加载)
     * @param refreshLayout RefreshLayout
     * @param height 高度 HeaderHeight or FooterHeight
     * @param extendHeight 扩展高度  extendHeaderHeight or extendFooterHeight
     */
    void onReleased(RefreshLayout refreshLayout, int height, int extendHeight);

    /**
     * 开始动画
     * @param refreshLayout RefreshLayout
     * @param height HeaderHeight or FooterHeight
     * @param extendHeight extendHeaderHeight or extendFooterHeight
     */
    void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int extendHeight);

    /**
     * 动画结束
     * @param refreshLayout RefreshLayout
     * @param success 数据是否成功刷新或加载
     * @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态
     */
    int onFinish(@NonNull RefreshLayout refreshLayout, boolean success);

    /**
     * 水平方向的拖动
     * @param percentX 下拉时,手指水平坐标对屏幕的占比(0 - percentX - 1)
     * @param offsetX 下拉时,手指水平坐标对屏幕的偏移(0 - offsetX - LayoutWidth)
     * @param offsetMax 最大的偏移量
     */
    void onHorizontalDrag(float percentX, int offsetX, int offsetMax);

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

代码很简单,源码中有中文注解就不一一说明


LottileAnimation

Lottie官方使用手册

结合官网和部分源码很好实现Lottie在RefreshHead 中的实现 !!!

重点:

View,ViewGroup的生命周期和Wind上面的渲染过程,刚开始的时候去继承View拿到当前.json动画的宽高和在onMeasure中一直是0,0后来改为ViewGroup 子类重新自测measure宽和高得到的也是0,0。这下搞的我翻了波笔记本
笔记本mark入口

后来才决定改为CIRCLE_DIAMETER 和mCircleDiameter根据自己的分辨率和中心点来绘制动画.json的大小
(主要为了适配动画在不同分辨率手机里面的效果)

        final DisplayMetrics metrics = getResources().getDisplayMetrics();
        mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);

小伙伴也可以使用AT_MOST来根据父布局的指定获取当前自定义View的宽和高

OK到这里基本完善了Lottie动画能在Header里面指定的位置跳动了,接下run了一次发现动画确实是在Head里面跳动,但是因为设置了looper(true)的属性本身是不和Refresh onRefreshing 时间冲突,但是后面再次下拉刷新的时候出现了动画的帧数不是原来第一帧,这下纠结了,我一般不喜欢手动导入三方源码修改别人的源码主要以前被(XXX)坑哭过,升级一次,我基本要上重构一次我的项目。好在快速浏览了一遍LottieAnimationView的源码,好在和我猜测的一样 LottieDraw 和LottieAnimator 果然是根据Frame帧来实现动画的过程


LottieCom.png

那么现在来了不是给我机会为所欲为最大帧和最小帧 整个图片绘制过程Progress等

 switch (newState) {
            case None:
                lav.setFrame(0);
                lav.setProgress(0);
                break;
            case PullDownToRefresh:
                lav.setVisibility(View.VISIBLE);
                break;
            case PullDownCanceled:
                break;
            case ReleaseToRefresh:
                lav.setVisibility(View.VISIBLE);
                break;
            case Refreshing:
                break;
            case RefreshFinish:
                lav.setVisibility(View.GONE);
                break;
        }

配合上层接口对代码做了最后的处理
喜欢效果小伙伴可以在去关注SmartRefreshLayout refresh-heads 和fresh-foot代码的实现,其中Vector向量和对View,ViewGroup,Drawable绘制 是很不错的学习源码。
OK 效果做出来了


经验分享:

记得几年前做Android开发的时拿到第三方库或者框架很是头疼和烦躁,后面接触多了能心平气和的写代码,反而觉得开发过程在别人车轮下面还是相对容易的,api 知识体系清楚的情况下,功能实现反而很轻松! 时代在进步,人也在进步,学习是IT的必经之路,找准自己爱好坚持下去就行。
以后博主每周五分享一篇博客(Kotilin React-native Android Flutter Java)

你可能感兴趣的:(SmartRefreshLayout lottie 打造自己的刷新动画)