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

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日

相关文章

  • jsjson转字符串

    以下是详细讲解“JS中JSON转字符串的完整攻略”的标准Markdown格式文本: JS中JSON转字符串的完整攻略 在JavaScript中,可以使用JSON对象将JavaScript对象转换为JSON字符串。本文将介绍JSON对象的基本概念、使用方法和两个示例说明。 1. JSON对象基本概念 JSON(JavaScript Object Notatio…

    other 2023年5月10日
    00
  • vue中Axios的封装与API接口的管理详解

    Vue中Axios的封装与API接口的管理详解 在Vue项目中,使用Axios进行API请求是比较常见的方法,但是在实际开发过程中,如果不进行封装和管理,会出现以下问题: 在多处代码中重复使用相同的Axios配置。 每次请求都需要手动编写URL、参数、请求方式等信息,难以管理。 当后台API接口发生变化时,需要修改所有使用该接口的代码。 因此,对Axios进…

    other 2023年6月25日
    00
  • 教你如何区分Spring与Structs2中间件

    区分Spring与Struts2中间件 介绍 Spring和Struts2都是Java Web开发中常用的中间件框架。虽然它们都用于构建Web应用程序,但在设计和使用上有一些区别。本攻略将详细讲解如何区分Spring和Struts2中间件,并提供两个示例说明。 区别 1. 设计理念 Spring:Spring是一个轻量级的Java框架,它提供了一个容器来管理…

    other 2023年8月6日
    00
  • C#正则表达式的递归匹配分析

    C#正则表达式的递归匹配分析 正则表达式中的递归匹配是指在匹配一个字符串时,需要重复匹配一个模式,并且该模式中还可以包含其他模式,因此需要对这些模式进行递归匹配。在C#中,使用Regex类来进行正则匹配,通过正则表达式语法中的特殊字符来实现递归匹配。 正则表达式中使用递归匹配 匹配简单的递归语法 简单的递归语法可以使用正则表达式中的括号来实现。例如,匹配一个…

    other 2023年6月27日
    00
  • idea一招搞定同步所有配置(导入或导出所有配置)

    下面我将详细讲解 “idea一招搞定同步所有配置(导入或导出所有配置)” 的完整攻略。 一、背景介绍 首先,需要知道的是,IntelliJ IDEA 是一款功能丰富、使用方便的 Java 集成开发环境,也是开发者必不可少的工具之一。在使用 IntelliJ IDEA 的过程中,我们往往需要配置各种插件、主题、快捷键等等,这些配置信息非常重要,我们希望能够在不…

    other 2023年6月25日
    00
  • Win11无限重启怎么办 Win11系统自动重启解决办法

    Win11无限重启怎么办 问题描述 在使用Win11系统时,有时可能会出现无限重启的情况,即计算机会在启动过程中不断地重启。这种情况会给用户带来极大的困扰,用户需要采取一些解决办法来解决。 解决办法 1.关闭自动重启 如果Win11系统在启动过程中循环重启,用户可以在计算机进入“安全模式”后,关闭自动重启功能。具体方法如下: 在计算机启动时按下 F8 按键,…

    other 2023年6月26日
    00
  • vue之v-for

    Vue.js是一款流行的JavaScript框架,它提供了许多方便的指令来简化开发过程。其中,v-for指令可以用于循环渲染列表数据。本文将介绍如何使用v-for指令,并提供两个示例说明。 基本用法 v-for指令可以用于循环渲染数组或对象中的数据。以下是一个示例,演示如何使用v-for指令循环渲染数组中的数据: <ul> <li v-fo…

    other 2023年5月9日
    00
  • MySQL 5.7.9 服务无法启动-“NET HELPMSG 3534”的解决方法

    MySQL 5.7.9 服务无法启动-“NET HELPMSG 3534”的解决方法 当我们尝试启动MySQL 5.7.9服务时,可能会遇到以下错误: “error 1067 the process terminated unexpectedly”,然后我们通过命令行方式尝试手动启动该服务,而结果提示了 “NET HELPMSG 3534” 错误。如下所示:…

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