Android开发之自定义加载动画详解

yizhihongxing

Android开发之自定义加载动画详解

一、前言

在移动APP的开发中,由于数据的加载速度不可控,我们通常会使用加载动画来占位,让用户知道数据正在努力获取中,以此来提升用户体验。在Android开发中,我们可以通过自定义View来创建各种各样的加载动画,本篇攻略将详细讲解如何自定义加载动画。

二、核心步骤

2.1 绘制动画

自定义加载动画的第一步是绘制动画。需要在View的onDraw()方法中完成绘制。这里我们以绘制小球旋转的动画为例。

public class BallRotationView extends View {

    private Paint mPaint;
    private int mWidth, mHeight;
    private float mRadius = 20; // 小球半径
    private float mDegrees; // 小球旋转角度
    private float[] mPositions = new float[8]; // 8个小球的旋转初始角度

    private AnimatorSet mAnimatorSet;

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

    public BallRotationView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
        initPositions();
        initAnimator();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
    }

    private void initPositions() {
        for (int i = 0; i < 8; i++) {
            mPositions[i] = i * 45;
        }
    }

    private void initAnimator() {
        ObjectAnimator degreeAnimator = ObjectAnimator.ofFloat(this, "degrees", 0, 360);
        degreeAnimator.setDuration(1000);
        degreeAnimator.setInterpolator(new LinearInterpolator());
        degreeAnimator.setRepeatCount(ValueAnimator.INFINITE);

        mAnimatorSet = new AnimatorSet();
        mAnimatorSet.playTogether(degreeAnimator);
        mAnimatorSet.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        canvas.translate(mWidth / 2, mHeight / 2);

        for (int i = 0; i < 8; i++) {
            canvas.rotate(mPositions[i] + mDegrees);
            canvas.drawCircle(0, mWidth / 2 - mRadius * 2, mRadius, mPaint);
        }

        canvas.restore();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
    }

    public void setDegrees(float degrees) {
        mDegrees = degrees;
        invalidate();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mAnimatorSet.cancel();
    }

}

2.2 循环动画

接下来,我们需要实现动画的循环播放。可以通过AnimatorSet来实现多个Animator的组合。

ObjectAnimator degreeAnimator = ObjectAnimator.ofFloat(this, "degrees", 0, 360);
degreeAnimator.setDuration(1000);
degreeAnimator.setInterpolator(new LinearInterpolator());
degreeAnimator.setRepeatCount(ValueAnimator.INFINITE);

mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(degreeAnimator);
mAnimatorSet.start();

2.3 动画控制

当View被移除屏幕时,停止动画,释放所有资源。可以在onDetachedFromWindow()方法中实现。

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    mAnimatorSet.cancel();
}

三、示例说明

3.1 示例一:小球旋转动画

我们可以使用自定义的BallRotationView实现小球旋转动画效果。代码如下:

<com.example.myapplication.BallRotationView
    android:layout_width="wrap_content"
    android:layout_height="50dp"
    android:layout_gravity="center_horizontal" />

3.2 示例二:微信小程序的加载动画

我们可以根据微信小程序的加载动画示意图来自定义仿微信小程序的加载动画,代码如下:

public class WxLoadingView extends View {

    private Paint mPaint;

    private int mWidth, mHeight;
    private float mRadius;
    private float mScale; // 尺寸变化比例

    private int mCount = 3; // 圆点个数
    private int mCurrentIndex = 0; // 当前动画圆点序号
    private int mDuration = 300; // 动画时长
    private boolean mIsAnimatorRunning = false;

    private ValueAnimator mAnimator;

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

    public WxLoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
        startAnimation();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(Color.parseColor("#1296db"));
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
    }

    private void startAnimation() {
        mAnimator = ValueAnimator.ofFloat(1, 0.2f, 1);
        mAnimator.setDuration(mDuration);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.setRepeatMode(ValueAnimator.RESTART);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mScale = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        mIsAnimatorRunning = true;
        mAnimator.start();
    }

    private void stopAnimation() {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.cancel();
            mIsAnimatorRunning = false;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float x = (float) (mWidth / 2);
        float y = (float) (mHeight / 2);
        float space = mRadius / 2;

        for (int i = 0; i < mCount; i++) {
            canvas.save();
            float translateX = x + (i - (mCount - 1) / 2f) * space * mScale;
            float translateY = y;
            canvas.translate(translateX, translateY);
            float scale = mCurrentIndex == i ? 1 : 0.5f;
            canvas.scale(scale, scale);
            canvas.drawCircle(0, 0, mRadius * mScale, mPaint);
            canvas.restore();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        mRadius = mHeight / 5;
    }

    public boolean isAnimatorRunning() {
        return mIsAnimatorRunning;
    }

    public void show() {
        if (!isAnimatorRunning()) {
            startAnimation();
        }
        setVisibility(VISIBLE);
    }

    public void hide() {
        stopAnimation();
        setVisibility(GONE);
    }

    public void setCurrentIndex(int currentIndex) {
        this.mCurrentIndex = currentIndex;
        invalidate();
    }

}

我们可以使用自定义的WxLoadingView实现仿微信小程序的加载动画效果。代码如下:

<com.example.myapplication.WxLoadingView
    android:id="@+id/loading_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_marginTop="24dp" />

四、总结

通过自定义View的方式,我们可以轻松实现各种各样的加载动画效果。不仅能提升用户的体验,也能让APP的界面更具有个性化。希望本篇攻略对您有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Android开发之自定义加载动画详解 - Python技术站

(0)
上一篇 2023年6月25日
下一篇 2023年6月25日

相关文章

  • js通过var定义全局变量与在window对象上直接定义属性的区别说明

    当在JavaScript中定义全局变量时,有两种常见的方法:使用var关键字定义全局变量,或者直接在window对象上定义属性。下面是它们之间的区别说明: 作用域: 使用var关键字定义的全局变量具有函数作用域,而直接在window对象上定义的属性具有全局作用域。这意味着使用var定义的变量只在定义它的函数内部可见,而直接在window对象上定义的属性可以在…

    other 2023年7月28日
    00
  • openssl3.0简介(3)

    以下是关于OpenSSL 3.0的完整攻略,包括基本知识和两个示例。 基本知识 OpenSSL是一个开源的加密库,提供了各种加密算法和协议的实现,例如SSL/TLS、RSA、DSA、AES等。OpenSSL 3.0是OpenSSL的最新版本,它引入了许多新特性和改进,包括: 改进TLS支持:OpenSSL 3.0支持TLS 1.3,并提供了更好的性能和安全性…

    other 2023年5月7日
    00
  • 在电脑上查找不记得文件名的文件的方法教程(图文)

    在电脑上查找不记得文件名的文件的方法教程 如果你经常使用电脑,那么很有可能会有一些文件,你不记得它们的文件名,或者你只知道一些小的片段,例如:你可能记得这个文件在几天前、几周前、几个月前或甚至几年前被创建,或者你可能知道它是在哪个文件夹中被储存。但是,如果你不记得确切的文件名,那么要找到它就非常困难了。下面是我们向你推荐的几种方法,让你能够快速找出你不记得文…

    other 2023年6月26日
    00
  • CentOS关于quota的总结与实践详解

    CentOS关于quota的总结与实践详解 什么是quota quota是一种磁盘空间配额限制机制,可以限制用户或组在使用磁盘空间时的上限。CentOS是一种常见的Linux操作系统,其内置了quota软件包,可以实现对用户或组的配额限制。 安装quota软件包 在CentOS中安装quota软件包十分简单,执行以下命令即可: yum install -y …

    other 2023年6月27日
    00
  • 当面试官问我ArrayList和LinkedList哪个更占空间时,我是这么答的(面试官必问)

    当面试官问我ArrayList和LinkedList哪个更占空间时,我们应该从以下几个方面来考虑: 内存空间 插入/删除操作的性能 随机查找元素的性能 接下来我们将逐一分析这三个方面。 1. 内存空间 在内存方面,ArrayList 和 LinkedList 都不占用固定的空间,它们的空间占用率取决于内容的数量和数据的类型。ArrayList 的底层数据结构…

    other 2023年6月27日
    00
  • Vue3-KeepAlive,多个页面使用keepalive方式

    下面是关于Vue3中使用<keep-alive>组件的完整攻略: 简介 <keep-alive> 组件是Vue中一个十分实用的内置组件,它可以用来缓存组件实例,提高组件的性能。在我们使用Vue3的时候,也可以使用 v-keep-alive 指令来进行缓存操作。 使用方法 在组件中使用 我们可以在需要缓存的组件标签上,加上 v-keep…

    other 2023年6月27日
    00
  • 正则表达式验证用户名、密码、手机号码、身份证(推荐)

    下面是正则表达式验证用户名、密码、手机号码、身份证的完整攻略: 什么是正则表达式 正则表达式是一种文本模式,用来描述、匹配一系列符合某个规则的字符串。它通常是由一个字符序列构成,包含了一些特殊字符,用来表达一定规则的字符串。 用户名验证 在用户名验证中,我们通常要求用户名由字母、数字或者下划线组成,长度在6到20之间。下面是对应的正则表达式: /^[a-zA…

    other 2023年6月27日
    00
  • Vue elementUI表单嵌套表格并对每行进行校验详解

    Vue ElementUI表单嵌套表格并对每行进行校验详解 在Vue和ElementUI的组合中,我们可以使用表单嵌套表格的方式来实现复杂的数据录入和校验功能。本攻略将详细介绍如何实现这一功能,并提供两个示例说明。 步骤一:安装和引入依赖 首先,确保你已经安装了Vue和ElementUI。如果没有安装,可以通过以下命令进行安装: npm install vu…

    other 2023年7月28日
    00
合作推广
合作推广
分享本页
返回顶部