当前位置: 首页 > news >正文

Android实现漂亮的波纹动画

Android实现漂亮的波纹动画

本文章讲述如何使用二维画布canvas和camera、矩阵实现二、三维波纹动画效果(波纹大小变化、画笔透明度变化、画笔粗细变化)

一、UI界面

界面主要分为三部分
第一部分:输入框,根据输入x轴、Y轴、Z轴倾斜角度绘制波纹动画立体效果
第二部分:点击按钮PLAY:开始绘制动画;点击按钮STOP:停止绘制动画
第三部分:绘制波纹动画的自定义view
在这里插入图片描述

二、主要代码实现

1. 新建一个WaveView类继承自View

声明类成员变量和实现构造函数初始化

public class WaveView extends View{public WaveView(Context context) {super(context);init();}public WaveView(Context context, AttributeSet attrs) {super(context, attrs);init();}public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}

2. 添加动画

(1)圆圈等比变大动画

ValueAnimator radiusAnimator = ValueAnimator.ofFloat(mRippleInfo.minRadius, mRippleInfo.maxRadius);
radiusAnimator.setDuration(mRippleInfo.durationTime); // 动画持续时间
radiusAnimator.setStartDelay(i * rippleDelay); // 延迟启动
radiusAnimator.setRepeatCount(ValueAnimator.INFINITE);
radiusAnimator.setRepeatMode(ValueAnimator.RESTART);
radiusAnimator.addUpdateListener(animation -> {mRadiusArray[index] = (float) animation.getAnimatedValue();invalidate(); // 重绘视图
});

(2)透明度渐变动画

ValueAnimator alphaAnimator = ObjectAnimator.ofFloat( 0.1f, 0.8f, 0.8f,0.4f, 0);
alphaAnimator.setDuration(mRippleInfo.durationTime); // 动画持续时间
alphaAnimator.setStartDelay(i * rippleDelay); // 延迟启动
alphaAnimator.setRepeatCount(ValueAnimator.INFINITE);
alphaAnimator.setRepeatMode(ValueAnimator.RESTART);
alphaAnimator.addUpdateListener(animation -> {mAlphaArray[index] = (float) animation.getAnimatedValue();invalidate(); // 重绘视图
});

(3)画笔由细变粗动画

ValueAnimator strokeAnimator = ObjectAnimator.ofFloat(mRippleInfo.minStrokenWidth, mRippleInfo.maxStrokenWidth);
strokeAnimator.setDuration(mRippleInfo.durationTime); // 动画持续时间
strokeAnimator.setStartDelay(i * rippleDelay); // 延迟启动
strokeAnimator.setRepeatCount(ValueAnimator.INFINITE);
strokeAnimator.setRepeatMode(ValueAnimator.RESTART);
strokeAnimator.addUpdateListener(animation -> {mStrokenArray[index] = (float) animation.getAnimatedValue();invalidate(); // 重绘视图
});

3. 应用矩阵变换

需要注意坐标变换中心点,这里是以画布左下角作为坐标中心点

 mRectF.set(0, 0, width, height);
// 应用相机变换
mCamera.save();
mCamera.rotateX(30);  // 绕X轴旋转,顺时针为正
mCamera.rotateY(0);  // 绕Y轴旋转
mCamera.rotateZ(30);  // 绕Z轴旋转
mCamera.getMatrix(mMatrix);  // 获取变换后的矩阵
mCamera.restore();mMatrix.preTranslate(0, -height);//1️以左下角为坐标中心点
mMatrix.postTranslate(0, height);mMatrix.mapRect(mRectF);
float newCenterX = mRectF.centerX();// 获取变换后的图像的新中心点
float newCenterY = mRectF.centerY();
float translateX = centerX - newCenterX;// 计算需要的平移量
float translateY = centerY - newCenterY;
mMatrix.postTranslate(translateX, translateY);// 将平移应用到矩阵

4. 重写onDraw方法

@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (!mIsDrawing){return;}canvas.save();// 计算中心点centerX = getWidth() / 2.0f;centerY = getHeight() / 2.0f;canvas.concat(mMatrix);// 将矩阵应用到画布上//绘制多圈波纹for (int i = 0; i < mRippleInfo.rippleCount; i++) {if (mRadiusArray[i] > 0) {paint.setStrokeWidth(mStrokenArray[i]);paint.setAlpha((int)(255 * mAlphaArray[i]));canvas.drawCircle(centerX, centerY,mRadiusArray[i],paint);}}// 中心点绘制 logocanvas.drawBitmap(mLogoBitmap, (getWidth()- mRippleInfo.minRadius)/2, (getHeight()- mRippleInfo.minRadius)/2, null);canvas.restore();}

三、效果展示

二维波纹动画

waveAnimation_2D

三维波纹动画

waveAnimation_3D

四、 完整代码实现:

activity_main.xml实现如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:padding="16dp"android:gravity="center"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:layout_marginBottom="8dp"><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="vertical"android:gravity="center_horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="X轴倾斜" /><EditTextandroid:id="@+id/editText_X"android:layout_width="match_parent"android:layout_height="wrap_content"android:inputType="number"android:text="0"/></LinearLayout><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="vertical"android:gravity="center_horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Y轴倾斜" /><EditTextandroid:id="@+id/editText_Y"android:layout_width="match_parent"android:layout_height="wrap_content"android:inputType="number"android:text="0"/></LinearLayout><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="vertical"android:gravity="center_horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Z轴倾斜" /><EditTextandroid:id="@+id/editText_Z"android:layout_width="match_parent"android:layout_height="wrap_content"android:inputType="number"android:text="0"/></LinearLayout></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center"android:paddingTop="16dp"><Buttonandroid:id="@+id/playButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Play"android:layout_marginEnd="8dp"/><Buttonandroid:id="@+id/stopButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Stop"android:layout_marginEnd="8dp"/></LinearLayout><FrameLayoutandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:layout_gravity="center"><!-- WaveView --><com.example.waveanimationbysingleview.animation.WaveViewandroid:id="@+id/wave_view"android:layout_width="match_parent"android:layout_height="606dp"android:layout_gravity="center" /></FrameLayout></LinearLayout>

自定义view——WaveView.java实现如下:

package com.example.waveanimationbysingleview.animation;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;import com.example.waveanimationbysingleview.R;/*** @author xibao* @since 2025/02/26*/
public class WaveView extends View{private RippleAnimationInfo mRippleInfo;private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);private float centerX, centerY;private float[] mRadiusArray;  // 存储当前绘制每圈光波的半径private float[] mAlphaArray;   //存储当前绘制每圈光波的透明度private float[] mStrokenArray; //存储当前绘制每圈光波的画笔宽度private final Camera mCamera = new Camera();private final Matrix mMatrix = new Matrix();private final RectF mRectF = new RectF();private Boolean mIsDrawing = false;private Bitmap mLogoBitmap;private AnimatorSet mAnimatorSet = new AnimatorSet();//多动画组合类private List<Animator> mAnimatorList = new ArrayList<>();public WaveView(Context context) {super(context);init();}public WaveView(Context context, AttributeSet attrs) {super(context, attrs);init();}public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {paint.setAntiAlias(true);//抗锯齿paint.setStrokeCap(Paint.Cap.ROUND);paint.setStrokeWidth(2);paint.setStyle(Paint.Style.STROKE);// 填充样式 描边paint.setColor(Color.parseColor("#fde48a"));this.setVisibility(View.VISIBLE);//可见initRippleAnimationInfos();// 加载 logo 图片mLogoBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.logo);mLogoBitmap = Bitmap.createScaledBitmap(mLogoBitmap, mRippleInfo.minRadius, mRippleInfo.minRadius, true);}private void initRippleAnimationInfos() {int[] colors = {0xFF5CC0C6};//int[] colors = {0xFF5CC0C6, 0xFF5CC0C6, 0xFFA353CB, 0xFFCFB852, 0xFFCFB852, 0xFFA353CB, 0xFF5CC0C6};//头尾颜色一样过渡自然mRippleInfo = new RippleAnimationInfo(30,300,1,10,3000, new Vector3D(0,0,0), colors,5);//初始化动画属性mRadiusArray = new float[mRippleInfo.rippleCount];mAlphaArray = new float[mRippleInfo.rippleCount];mStrokenArray = new float[mRippleInfo.rippleCount];initAnimation();}private void initAnimation() {int rippleDelay = mRippleInfo.durationTime/mRippleInfo.rippleCount;for (int i = 0; i < mRippleInfo.rippleCount; i++) {//动画1-半径变大final int index = i;ValueAnimator radiusAnimator = ValueAnimator.ofFloat(mRippleInfo.minRadius, mRippleInfo.maxRadius);radiusAnimator.setDuration(mRippleInfo.durationTime); // 动画持续时间radiusAnimator.setStartDelay(i * rippleDelay); // 延迟启动radiusAnimator.setRepeatCount(ValueAnimator.INFINITE);radiusAnimator.setRepeatMode(ValueAnimator.RESTART);radiusAnimator.addUpdateListener(animation -> {mRadiusArray[index] = (float) animation.getAnimatedValue();invalidate(); // 重绘视图});//动画2-画笔变粗ValueAnimator strokeAnimator = ObjectAnimator.ofFloat(mRippleInfo.minStrokenWidth, mRippleInfo.maxStrokenWidth);strokeAnimator.setDuration(mRippleInfo.durationTime); // 动画持续时间strokeAnimator.setStartDelay(i * rippleDelay); // 延迟启动strokeAnimator.setRepeatCount(ValueAnimator.INFINITE);strokeAnimator.setRepeatMode(ValueAnimator.RESTART);strokeAnimator.addUpdateListener(animation -> {mStrokenArray[index] = (float) animation.getAnimatedValue();invalidate(); // 重绘视图});//动画3-颜色淡出ValueAnimator alphaAnimator = ObjectAnimator.ofFloat( 0.1f, 0.8f, 0.8f,0.4f, 0);alphaAnimator.setDuration(mRippleInfo.durationTime); // 动画持续时间alphaAnimator.setStartDelay(i * rippleDelay); // 延迟启动alphaAnimator.setRepeatCount(ValueAnimator.INFINITE);alphaAnimator.setRepeatMode(ValueAnimator.RESTART);alphaAnimator.addUpdateListener(animation -> {mAlphaArray[index] = (float) animation.getAnimatedValue();invalidate(); // 重绘视图});mAnimatorList.add(radiusAnimator);mAnimatorList.add(strokeAnimator);mAnimatorList.add(alphaAnimator);}mAnimatorSet.playTogether(mAnimatorList);}private void initMatrix(float width, float height, float centerX, float centerY) {// if (mMatrixInited) {//    return;// }// mMatrixInited = true;mRectF.set(0, 0, width, height);// 应用相机变换mCamera.save();mCamera.rotateX(mRippleInfo.matrixVector3D.x);  // 向Y轴旋转mCamera.rotateY(mRippleInfo.matrixVector3D.y);  // 向Z轴旋转mCamera.rotateZ(mRippleInfo.matrixVector3D.z);  // 向Z轴旋转3mCamera.getMatrix(mMatrix);  // 获取变换后的矩阵mCamera.restore();mMatrix.preTranslate(0, -height);//1️以左下角为坐标中心点mMatrix.postTranslate(0, height);mMatrix.mapRect(mRectF);float newCenterX = mRectF.centerX();// 获取变换后的图像的新中心点float newCenterY = mRectF.centerY();float translateX = centerX - newCenterX;// 计算需要的平移量float translateY = centerY - newCenterY;mMatrix.postTranslate(translateX, translateY);// 将平移应用到矩阵}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);centerX = w / 2.0f;centerY = h / 2.0f;initMatrix(w,h,centerX,centerY);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (!mIsDrawing){return;}canvas.save();// 计算中心点centerX = getWidth() / 2.0f;centerY = getHeight() / 2.0f;canvas.concat(mMatrix);// 将矩阵应用到画布上for (int i = 0; i < mRippleInfo.rippleCount; i++) {if (mRadiusArray[i] > 0) {paint.setStrokeWidth(mStrokenArray[i]);paint.setAlpha((int)(255 * mAlphaArray[i]));canvas.drawCircle(centerX, centerY,mRadiusArray[i],paint);}}// 绘制 logocanvas.drawBitmap(mLogoBitmap, (getWidth()- mRippleInfo.minRadius)/2, (getHeight()- mRippleInfo.minRadius)/2, null);canvas.restore();}public void stopAnimation() {if (mAnimatorSet != null) {mAnimatorSet.cancel();mAnimatorList.clear();}}public void startAnimation(int x,int y,int z) {if(mAnimatorSet != null) {mRippleInfo.matrixVector3D.x = x;mRippleInfo.matrixVector3D.y = y;mRippleInfo.matrixVector3D.z = z;initMatrix(getWidth(),getHeight(),getWidth()/2,getHeight()/2);mAnimatorSet.start();mIsDrawing = true;}}
}

自定义三维坐标系类vectror3D.java

package com.example.waveanimationbysingleview.animation;public class Vector3D {public int x, y, z;public Vector3D(int x, int y, int z) {this.x = x;this.y = y;this.z = z;}// 获取X坐标public int getX() { return x; }// 获取Y坐标public int getY() {return y;}// 获取Z坐标public int getZ() {return z;}@Overridepublic String toString() {return "Vector3D{" +"x=" + x +", y=" + y +", z=" + z +'}';}
}

光波动画属性类RippleAnimationInfo.java

package com.example.waveanimationbysingleview.animation;public class RippleAnimationInfo {public final int minRadius;//波纹半径public final int maxRadius;// 最大波纹半径public final int minStrokenWidth;//画笔最小宽度public final int maxStrokenWidth;//画笔最大宽度public final int durationTime;//动画时长public final Vector3D matrixVector3D;//三维倾斜角度集合public final int[] color;//画笔颜色:支持多色渐变绘制public final  int rippleCount; //波纹数量public RippleAnimationInfo(int minRadius, int maxRadius, int minStrokenWidth, int maxStrokenWidth, int durationTime, Vector3D vector3D, int[] color, int rippleCount){this.minRadius = minRadius;this.maxRadius = maxRadius;this.minStrokenWidth = minStrokenWidth;this.maxStrokenWidth = maxStrokenWidth;this.durationTime = durationTime;this.matrixVector3D = vector3D;this.color = color;this.rippleCount = rippleCount;}
}

http://www.mrgr.cn/news/93471.html

相关文章:

  • 【OMCI实践】wireshark解析脚本omci.lua文件(独家分享)
  • 前端实现版本更新自动检测✅
  • DeepSeek V3 源码:从入门到放弃!
  • 现代密码学体系架构设计原则与实践:基于Python的实现与GPU加速GUI演示
  • 【docker】安装mysql,修改端口号并重启,root改密
  • Anolis服务器Arm64架构服务器配置(其他版本服务器解决方式思路一质)
  • JDK ZOOKEEPER KAFKA安装
  • Vue 系列之:组件通讯
  • 在线教育网站项目第二步 :学习roncoo-education,服务器为ubuntu22.04.05
  • react中的fiber和初次渲染
  • 树莓派3B+的初步使用
  • 【VBA】WPS/PPT设置标题字体
  • Java本地缓存深度实践:框架选型与一致性保障(下)
  • 【星云 Orbit•STM32F4】13. 探索定时器:基本定时器
  • 数据库的安装(mysql)
  • Flink深入浅出之02:编程模型、数据源、算子、connector
  • 【人工智能】数据挖掘与应用题库(501-600)
  • 算法·搜索
  • Spring提供的SPEL表达式
  • 算法之 前缀和