IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 不使用 Lottie 库,自定义 drawable 实现加载动画 -> 正文阅读

[游戏开发]不使用 Lottie 库,自定义 drawable 实现加载动画

Hello,村长 😊

先看效果

在这里插入图片描述

实现方案

1、继承 Drawable
2、实现 Animatable

先贴上完整代码,注释中稍作解释,如有疑惑欢迎评论😊

import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.Property;
import android.view.animation.CycleInterpolator;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

//作者其他类,可删除,不影响主要功能
import com.primer.common.util.LogHelper;
import com.primer.common.util.UiHelper;

import java.util.ArrayList;
import java.util.List;

public class CommonLoginDrawable extends Drawable implements Animatable {
    // 动画控制
    private List<Property<CommonLoginDrawable, Integer>> mPropertyList;
    private List<ValueAnimator> mValueAnimatorList;
    private static final long POINT_ANIMATION_DURATION = 800;
    
    //点
    private Paint mPointPain;
    private List<Point> mPointList;
    private int mPointRadio = 8;
    private int mPointDivideWidth = 30;

    //文字
    private int mDrawTextLeft;
    private int mDrawTextBottom;
    private String mDrawText = "加载中";
    private Paint mTextPain;
    private int mTextWidth;
    private int mTextHeight;

    public CommonLoginDrawable(Activity activity) {
        initTextPain(activity);
        initPoint();
        initAnimator();
    }

    private void initAnimator() {
        mPropertyList = new ArrayList<>();
        mPropertyList.add(createProperty(PointPosition.POINT_ONE));
        mPropertyList.add(createProperty(PointPosition.POINT_TOW));
        mPropertyList.add(createProperty(PointPosition.POINT_THERE));

        mValueAnimatorList = new ArrayList<>();
        // (mTextHeight / 10) * 5: 粗略计算加载中 ‘点’ Y轴值的最小距离适配,让‘点’的动画尽量浮动在文字中间,幅度也不能太小,否则动画效果不明显
        mValueAnimatorList.add(createAnimator(PropertyValuesHolder.ofInt(mPropertyList.get(PointPosition.POINT_ONE),
                mDrawTextBottom - (mTextHeight / 10) * 5, mDrawTextBottom - ((mTextHeight / 10) * 3))));
        mValueAnimatorList.add(createAnimator(PropertyValuesHolder.ofInt(mPropertyList.get(PointPosition.POINT_TOW),
                mDrawTextBottom - (mTextHeight / 10) * 5, mDrawTextBottom - ((mTextHeight / 10) * 3))));
        mValueAnimatorList.add(createAnimator(PropertyValuesHolder.ofInt(mPropertyList.get(PointPosition.POINT_THERE),
                mDrawTextBottom - (mTextHeight / 10) * 5, mDrawTextBottom - ((mTextHeight / 10) * 3))));
    }

    private void initPoint() {
        mPointList = new ArrayList<>();
        mPointList.add(new Point(mDrawTextLeft + mTextWidth + mPointDivideWidth, mDrawTextBottom, mPointRadio));
        mPointList.add(new Point(mDrawTextLeft + mTextWidth + mPointDivideWidth * 2, mDrawTextBottom, mPointRadio));
        mPointList.add(new Point(mDrawTextLeft + mTextWidth + mPointDivideWidth * 3, mDrawTextBottom, mPointRadio));

        mPointPain = new Paint();
        mPointPain.setStyle(Paint.Style.FILL_AND_STROKE);
        mPointPain.setColor(Color.WHITE);
        mPointPain.setAntiAlias(false);
        mPointPain.setStrokeCap(Paint.Cap.ROUND);
    }

    private void initTextPain(Activity activity) {
        mTextPain = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPain.setTextSize(70);
        mTextPain.setStyle(Paint.Style.FILL);
        mTextPain.setColor(Color.WHITE);
        mTextPain.setAntiAlias(true);
        mTextPain.setStrokeWidth(7);
        mTextPain.setStrokeCap(Paint.Cap.ROUND);
        measureText(activity);
    }


    /**
     * 测量文字居中显示
     *
     * @param activity
     */
    private void measureText(Activity activity) {
        Rect rect = new Rect();
        mTextPain.getTextBounds(mDrawText, 0, mDrawText.length(), rect);
        mTextWidth = rect.width();
        mTextHeight = rect.height();

        LogHelper.e("mTextWidth = " + mTextWidth + "  mTextHeight = " + mTextHeight);
        mDrawTextLeft = UiHelper.getScreenWidth(activity) / 2 - (mTextWidth / 2) - 50;//减去三个点和彼此之前间距的距离(粗略计算)
        mDrawTextBottom = UiHelper.getScreenHeight(activity) / 2 - (mTextHeight / 2);
    }

    private void setPointOneY(int pointY, int index) {
        if (mPointList != null && mPointList.size() > index) {
            mPointList.get(index).setY(pointY);
        }
    }

    private int getPointOneY(int index) {
        if (mPointList != null && mPointList.size() > index) {
            mPointList.get(index).getY();
        }

        return 0;
    }

    /**
     * 绘制
     *
     * @param canvas
     */
    @Override
    public void draw(@NonNull Canvas canvas) {
        canvas.drawColor(Color.BLACK);
        canvas.drawText(mDrawText, mDrawTextLeft, mDrawTextBottom, mTextPain);
        for (Point point : mPointList) {
            canvas.drawCircle(point.getX(), point.getY(), point.getRadio(), mPointPain);
        }
    }

    /**
     * 设置透明度
     *
     * @param alpha
     */
    @Override
    public void setAlpha(int alpha) {
        mTextPain.setAlpha(255);//若要做成渐变的效果,可以额外增加一个 属性值 渐变器,根据需要设置 .setAlpha(alpha)
    }

    /**
     * 颜色过滤
     *
     * @param colorFilter
     */
    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        mTextPain.setColorFilter(colorFilter);
    }

    @SuppressLint("WrongConstant")
    @Override
    public int getOpacity() {
        return PixelFormat.RGBA_8888;//根据对显示质量的需要,可以选中不同的格式
    }

    @Override
    public void start() {
        LogHelper.e("animator start");
        if (mValueAnimatorList != null) {
            for (ValueAnimator animator : mValueAnimatorList) {
                animator.start();
            }
        }
    }

    @Override
    public void stop() {
        LogHelper.d("animator end");
        if (mValueAnimatorList != null) {
            for (ValueAnimator animator : mValueAnimatorList) {
                animator.end();
            }
        }
    }

    @Override
    public boolean isRunning() {
        return mValueAnimatorList != null && mValueAnimatorList.get(PointPosition.POINT_ONE).isRunning();
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        LogHelper.e("onBoundsChange");
        if (isRunning()) {
            stop();
        }

        //不建议在构造函数中启动动画,时机太早,会出现有些动画无法执行
        startAnimatorValue();
    }


    private void startAnimatorValue() {
        if (mValueAnimatorList != null) {
            Handler handler = new Handler(Looper.getMainLooper());
            for (int i = 0; i < mValueAnimatorList.size(); ) {
                int index = i;
                LogHelper.e("index = " + index);
                //使用一个延时执行,出现的动画效果如图所示的波浪式上下浮动
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mValueAnimatorList.get(index).start();
                    }
                }, i * 100);
                i++;
            }
        }
    }

    private ObjectAnimator createAnimator(PropertyValuesHolder pointYHolder) {
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(this, pointYHolder);
        animator.setDuration(POINT_ANIMATION_DURATION);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                 //这是实时更新并正确显示动画的关键,触发重绘
                invalidateSelf();
            }
        });
        //无限循环差值器,当然你可以通过 animator 设置动画执行方式为无限循环,但我觉得差值器逼格更高,哈哈哈开玩笑
        animator.setInterpolator(new CycleInterpolator(ValueAnimator.RESTART));
        animator.setRepeatCount(ValueAnimator.INFINITE);
        start();
        return animator;
    }

    //创建属性值监视器,本质便是默认或者指定差值器,差值器根据不同的算法计算出变化后的值,比如回弹效果、匀速、先加速后减速(高中物理概念,哈哈哈,但是我不会写)
    private Property<CommonLoginDrawable, Integer> createProperty(@PointPosition int index) {
        Property<CommonLoginDrawable, Integer> mRadiusProperty = new Property<CommonLoginDrawable, Integer>(Integer.class, "radius") {
            @Override
            public void set(CommonLoginDrawable object, Integer value) {
                object.setPointOneY(value, index);
            }

            @Override
            public Integer get(CommonLoginDrawable object) {
                return object.getPointOneY(index);
            }
        };

        return mRadiusProperty;
    }

    //额外封装一个‘点’的数据,方便管理(初学 java 那会不是总说‘封装’嘛!)
    private class Point {
        private int x;
        private int y;
        private int radio;

        public Point(int x, int y, int radio) {
            this.x = x;
            this.y = y;
            this.radio = radio;
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getRadio() {
            return radio;
        }

        public void setRadio(int radio) {
            this.radio = radio;
        }
    }

    //点 数组下标索引(使用 final 常量写也可以,使用 @interface 定义的内部变量默认是 final static)
    private @interface PointPosition {
        int POINT_ONE = 0;
        int POINT_TOW = 1;
        int POINT_THERE = 2;
    }
}

作者也是初学者,如注释有误,欢迎指出,谢谢🙏

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-01-24 11:15:39  更:2022-01-24 11:15:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 18:48:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码