自定义View实现圆环环绕的加载动画

23 篇文章 0 订阅

先看效果图:

这是我在某软件上看到的加载动画,感觉挺不错,就自己研究了一下。下面给大家分享一下该动画的实现过程

一、三个圆环的绘制和运动分析

来看下面这张解析图:

假设每个圆环的初始位置如上图,那么我们可以设定每一个圆环的位置以及它离控件边界的距离(w/6)

为了方便,我们定义控件宽度getWidth()为w,那么左上,右上,正下方圆环的圆心坐标依次为:

(w/4, w/4),(w*3/4, w/4),(w/2, w*3/4)

我们再来看下面这个动画:

发现了吧,实际上每个圆环都在做直线运动,三个圆环组合在一起就呈现出了相互靠近和远离的效果。

所以我们的最终目的是让这三个圆环延三条线段做匀速运动,当某一个圆环移动到了线段的端点时,就要改变它的运动状态,让它延另一条线段移动。

我们作图解析:

梳理一下:

  1. 处在左上角的圆环会延着编号1的线段移动,右上角的圆环会延着编号2的线段移动,正下方的圆环会延着编号3的线段移动。
  2. 彼此到达所在线段端点时(保证到达线段端点耗费的时间都相同),改变自身的运动状态,延着下一条线段继续运动。

二、圆环绘制和运动的代码解释

我们先创建一个MovePoint类,保存每个圆环的圆心坐标,所在直线的斜率与截距和每次移动的步长:

public class MovePoint {
    private float x;
    private float y;
    private float k,b;      //直线函数式中的斜率与截距
    private float moveStep;   //移动步长
    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
        setY(x*k+b);
    }

    public float getY() {
        return y;
    }

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

    public void setCalculate(float k,float b){
        this.k=k;
        this.b=b;
    }

    public float getK() {
        return k;
    }

    public void setK(float k) {
        this.k = k;
    }

    public float getB() {
        return b;
    }

    public void setB(float b) {
        this.b = b;
    }

    public float getMoveStep() {
        return moveStep;
    }

    public void setMoveStep(float moveStep) {
        this.moveStep = moveStep;
    }
}

初始化各属性:

private Paint circlePaint;
private int circleRadius=0; //圆的半径
private MovePoint[] movePoints=new MovePoint[3];    //记录三个圆环的位置,状态,和移动时的函数公式
private float[] x;    //三个圆环的x坐标
private float[] k,b; //三个圆环所在直线的斜率与截距
private int moveTime=70;  //移动到线段端点需要的时间

public MyThreeCircleLoadView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init();
}

private void init(){
    circlePaint=new Paint();
    circlePaint.setColor(Color.RED);
    circlePaint.setStyle(Paint.Style.STROKE);
    circlePaint.setAntiAlias(true);
}

/*
* 在左上角向右边移动; y=getWidth()/4
* 在右上角向左下移动; y=-2*x+getWidth()*7/4
* 在正下方向左上移动; y=2*x-getWidth()/4
* */
private void setMovePoints(){
    x=new float[]{getWidth()/4.0f,getWidth()*3/4.0f,getWidth()/2.0f};
    k=new float[]{0,-2,2};b=new float[]{getWidth()/4.0f,getWidth()*7/4.0f,-getWidth()/4.0f};
    int len=movePoints.length;
    for(int i=0;i<len;i++){
        movePoints[i]=new MovePoint();
        movePoints[i].setCalculate(k[i],b[i]);
        movePoints[i].setX(x[i]);
    }
    for(int i=0;i<len;i++)
        movePoints[i].setMoveStep(getMoveStep(movePoints[(i+1)%len].getX(),movePoints[i].getX(),moveTime));
}

//计算移动步长(速度):两点距离/移动花费的时间
private float getMoveStep(float x1,float x2,int time){
    return (x1-x2)/time;
}

注意移动步长的计算,因为我这里每次改变的是每个圆环的x坐标,而不是实际的线段距离,所以在getMoveStep()中,只需要(x1-x2)获得线段端点间的水平距离。

我们要求每个圆环到达线段端点的时间相等,由匀速运动公式可得v=(x/t),我们给定相同的t,x又是已知的,就可以得到每个圆环在当前线段上的速度v。

每个点都应该用float变量,否则会产生速度计算上的误差

 绘制圆环:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    for (MovePoint movePoint : movePoints) {
        canvas.drawCircle(movePoint.getX(), movePoint.getY(), circleRadius, circlePaint);
        changeCircleState(movePoint);
    }
    move();
}

//改变圆环移动的状态
private void changeCircleState(MovePoint movePoint){
    //从左上角向右移并移动到右上方顶点时
    if (movePoint.getX() >= x[1] && movePoint.getY() == movePoint.getX() * k[0] + b[0]) {
        movePoint.setCalculate(k[1], b[1]);
        movePoint.setMoveStep(getMoveStep(x[2], x[1], moveTime));
    }
    //右上向坐下移动并移动到正下方顶点时
    else if (movePoint.getX() <= x[2] && movePoint.getY() == movePoint.getX() * k[1] + b[1]) {
        movePoint.setCalculate(k[2], b[2]);
        movePoint.setMoveStep(getMoveStep(x[0], x[2], moveTime));
    }
    //从正下方向左上移动并移动到左上方顶点时
    else if (movePoint.getX() <= x[0]) {
        movePoint.setCalculate(k[0], b[0]);
        movePoint.setMoveStep(getMoveStep(x[1], x[0], moveTime));
    }
}

changeCircleState():用于判断圆环是否运动到线段的端点。若运动到了端点,就改变该圆环的运动状态和附属的线段

 移动圆环:

//移动圆环
private void move(){
    for (MovePoint movePoint : movePoints) {
        movePoint.setX(movePoint.getX() + movePoint.getMoveStep());
    }
    postInvalidateDelayed(5);
}

下面是自定义View的完整代码:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.bean.MovePoint;
import androidx.annotation.Nullable;

public class MyThreeCircleLoadView extends View {
    private Paint circlePaint;
    private int circleRadius=0; //圆的半径
    private MovePoint[] movePoints=new MovePoint[3];    //记录三个圆环的位置,状态,和移动时的函数公式
    private float[] x;    //三个圆环的x坐标
    private float[] k,b; //三个圆环所在直线的斜率与截距
    private int moveTime=70;  //移动到线段端点需要的时间
    public MyThreeCircleLoadView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init(){
        circlePaint=new Paint();
        circlePaint.setColor(Color.RED);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setAntiAlias(true);
    }

    /*
    * 在左上角向右边移动; y=getWidth()/4
    * 在右上角向左下移动; y=-2*x+getWidth()*7/4
    * 在正下方向左上移动; y=2*x-getWidth()/4
    * */
    private void setMovePoints(){
        x=new float[]{getWidth()/4.0f,getWidth()*3/4.0f,getWidth()/2.0f};
        k=new float[]{0,-2,2};b=new float[]{getWidth()/4.0f,getWidth()*7/4.0f,-getWidth()/4.0f};
        int len=movePoints.length;
        for(int i=0;i<len;i++){
            movePoints[i]=new MovePoint();
            movePoints[i].setCalculate(k[i],b[i]);
            movePoints[i].setX(x[i]);
        }
        for(int i=0;i<len;i++)
            movePoints[i].setMoveStep(getMoveStep(movePoints[(i+1)%len].getX(),movePoints[i].getX(),moveTime));
    }

    //计算移动步长(速度):两点距离/移动花费的时间
    private float getMoveStep(float x1,float x2,int time){
        return (x1-x2)/time;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        circleRadius=getWidth()/4-getWidth()/6;
        circlePaint.setStrokeWidth(5+getWidth()/100);
        setMovePoints();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (MovePoint movePoint : movePoints) {
            canvas.drawCircle(movePoint.getX(), movePoint.getY(), circleRadius, circlePaint);
            changeCircleState(movePoint);
        }
        move();
    }

    //改变圆环移动的状态
    private void changeCircleState(MovePoint movePoint){
        //从左上角向右移并移动到右上方顶点时
        if (movePoint.getX() >= x[1] && movePoint.getY() == movePoint.getX() * k[0] + b[0]) {
            movePoint.setCalculate(k[1], b[1]);
            movePoint.setMoveStep(getMoveStep(x[2], x[1], moveTime));
        }
        //右上向坐下移动并移动到正下方顶点时
        else if (movePoint.getX() <= x[2] && movePoint.getY() == movePoint.getX() * k[1] + b[1]) {
            movePoint.setCalculate(k[2], b[2]);
            movePoint.setMoveStep(getMoveStep(x[0], x[2], moveTime));
        }
        //从正下方向左上移动并移动到左上方顶点时
        else if (movePoint.getX() <= x[0]) {
            movePoint.setCalculate(k[0], b[0]);
            movePoint.setMoveStep(getMoveStep(x[1], x[0], moveTime));
        }
    }

    //移动圆环
    private void move(){
        for (MovePoint movePoint : movePoints) {
            movePoint.setX(movePoint.getX() + movePoint.getMoveStep());
        }
        postInvalidateDelayed(5);
    }
}

xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <com.myviewtext.MyThreeCircleLoadView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerInParent="true"/>
</RelativeLayout>

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 Android 自定义对号,可以使用自定义 View 实现打钩动画功能。以下是实现方法: 首先,创建一个类来实现自定义 View,这个类需要继承自 View,然后重写 onDraw 方法。在 onDraw 方法中,使用 Canvas 和 Path 对象来绘制对号的形状。 在绘制对号之前,需要先设置对号的起点和终点坐标,可以通过计算 View 的宽度和高度来确定这些坐标。然后,使用 Path 对象来创建对号的形状,具体方法如下: 1. 创建一个 Path 对象。 2. 使用 moveTo 方法将画笔移动到对号的起点。 3. 使用 lineTo 方法将画笔画出对号的一条线段。 4. 使用 moveTo 方法将画笔移动到对号的另一个起点。 5. 使用 lineTo 方法将画笔画出对号的另一条线段。 在绘制 Path 对象之后,可以使用 Paint 对象来设置对号的样式,例如颜色和宽度等。最后,在 onDraw 方法中调用 Canvas 的 drawPath 方法来将对号绘制出来。 另外,为了实现打钩动画,还需要使用 ValueAnimator 对象来控制 Path 的绘制过程。具体方法如下: 1. 创建一个 ValueAnimator 对象,并设置动画的起始值和结束值。 2. 在动画的监听器中,使用 ValueAnimator 的 getAnimatedValue 方法来获取当前动画的进度。 3. 根据当前进度,计算出对号的绘制进度,并使用 PathMeasure 对象来获取对应位置的 Path。 4. 在 onDraw 方法中,使用 Canvas 的 drawPath 方法来绘制当前的 Path。 最后,将自定义 View 添加到布局中即可实现自定义对号的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值