由于项目需要,在项目中做一个飘浮在所有视图顶部的桃花花瓣飘落的动画效果,网上找了和多,基本都是仅飘落效果,很少有带花瓣旋转缩放的,看起来很死板,好不容易在网上看到一个用JS写的基于HTML的代码,将其转为JAVA,运行起来了,效果也不错,但是我的项目要求的是花瓣从右上角为圆心,宽度的1/4为半径,-45°到45°为扇形区域开始飘落的效果,JS的这个却是从左上角飘落,并且不是指定扇形区域,改别人的代码吧,很麻烦,想想还是自己写一个,于是参考JS代码后花了1天时间自己写了一个。
共两个类,源码如下:
Petal.java类,花瓣绘制过程类。
package com.racer.smart.pro.view.petal;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PointF;
import java.util.List;
import java.util.Random;
/**花瓣对象*/
public class Petal {
/**花瓣图片*/
private Bitmap bitmapPetal;
/**花瓣宽高*/
private int petalWidth, petalHeight;
/**图片矩阵*/
private Matrix matrix;
/**花瓣位置*/
private PointF point=new PointF();
/**花瓣下降速度*/
private PointF speed=new PointF();
/**花瓣旋转角度*/
private float rotationAngle;
/** 花瓣X或Y轴缩放比例*/
private float scale;
/**缩小或放大*/
private boolean isZoomingIn = true;
/**屏幕或父视图尺寸*/
private int screenWidth, screenHeight;
/**随机生成器*/
private Random random;
/**活动上下文*/
private Context ctx;
/**图片资源列表*/
private List<Integer> resIdList;
/**
* 实例化PetalObject
* @param ctx 上下文
* @param resIds 图片资源列表
* @param canvasWidth 画布或屏幕宽度。如果为0,则取屏幕宽度
* @param canvasHeight 画布或屏幕高度。如果为0,则取屏幕高度
*/
public Petal(Context ctx, List<Integer> resIds,int canvasWidth, int canvasHeight) {
this.ctx=ctx;
resIdList=resIds;
if(canvasWidth>0){
screenWidth=canvasWidth;
}else{
screenWidth=ctx.getResources().getDisplayMetrics().widthPixels;
}
if(canvasHeight>0){
screenHeight=canvasHeight;
}else{
screenHeight=ctx.getResources().getDisplayMetrics().heightPixels;
}
matrix = new Matrix();
random = new Random();
resetPetal(); //重置花瓣
}
/**
* 绘制花瓣到画布
* @param canvas
*/
public void draw(Canvas canvas) {
if(bitmapPetal!=null) {
matrix.reset(); // 重置矩阵
// 设置旋转和缩放
matrix.postRotate(-rotationAngle, petalWidth / 2, petalHeight / 2);
matrix.postScale(1, scale); // 设置y轴缩放
matrix.postTranslate(point.x, point.y); //设置Bitmap的位置
// 在画布上绘制Bitmap
canvas.drawBitmap(bitmapPetal, matrix, null);
// 如果 Bitmap 飘出屏幕,重置位置
if (point.x + petalWidth < 0 || point.y - petalHeight > screenHeight) {
resetPetal();
}
movePetal();
}
}
/**移动花瓣位置*/
private void movePetal(){
if (isZoomingIn) {
scale += 0.01f;
if (scale >= 1.0f) {
isZoomingIn = false;
}
} else {
scale -= 0.01f;
if (scale <= 0.0f) {
isZoomingIn = true;
}
}
// 更新 Bitmap 的位置
point.x += speed.x;
point.y += speed.y;
// 更新旋转角度。在现有角度上增加一个6以内的随机值
rotationAngle += random.nextInt(6);
rotationAngle%=360f; //将旋转角度限制在0-359之间
}
/**重置花瓣。初始化或下落超出屏幕后都需要重置*/
private void resetPetal() {
if(resIdList!=null && resIdList.size()>0) {
int position = random.nextInt(resIdList.size()); //随机得到图片资源
//限制资源ID不得大于资源列表长度,防止随机数超出范围
if(position>=resIdList.size()) {
position = resIdList.size() - 1;
}
int resId = resIdList.get(position);
// 加载图片
bitmapPetal = BitmapFactory.decodeResource(ctx.getResources(), resId);
if (bitmapPetal != null) {
petalWidth = bitmapPetal.getWidth();
petalHeight = bitmapPetal.getHeight();
}
PointF center = new PointF(screenWidth, 0); // 圆心坐标
// 最小和最大半径
float minRadius = screenWidth / 10f; // 屏幕宽度的1/10
float maxRadius = screenWidth / 3f; // 屏幕宽度的1/3
// 随机半径,限制在minRadius与maxRadius之间
float radius = minRadius + (float) (Math.random() * (maxRadius - minRadius));
// 随机角度。范围从-45度到45度
double angle = Math.random() * Math.PI * 90 - Math.PI * 45;
// 通过随机角度和随机半径计算点的坐标
point.x = (float) (center.x + radius * Math.cos(angle));
point.y = (float) (center.y + radius * Math.sin(angle));
//初始化随机旋转角度
rotationAngle = random.nextInt(360);
//随机初始化花瓣缩放比例
scale = random.nextFloat();
// 随机初始化花瓣x方向速度
speed.x = -(random.nextInt(4) + 0.5f);
// 随机初始化花瓣y方向速度
speed.y = random.nextInt(2) + 1;
}
}
}```
PetalView,java花瓣视图类,用于将多个Petal构建的花瓣实体绘制到视图中显示出来。
```java
package com.racer.smart.pro.view.petal;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 花瓣视图
* 调用步骤:
* <ul>
* <li>1、实例化视图对象</li>
* <li>2、使用{@link #addImage(int)}方法添加花瓣图像</li>
* <li>3、调用{@link #startThread()}方法启动绘制线程</li>
* <li>4、释放视图,停止绘制线程</li>
* </ul>
*/
public class PetalView extends SurfaceView implements SurfaceHolder.Callback {
/**SurfaceView的子类,用于绘制花瓣动画*/
private SurfaceHolder surfaceHolder;
/**绘图线程*/
private DrawThread drawThread;
/**花瓣对象列表*/
private List<Petal> petalList;
/**花瓣图像资源列表*/
private List<Integer> petalImgList;
/**画布或屏幕宽度*/
private int canvasWidth;
/**画布或屏幕高度*/
private int canvasHeight;
/**需要创建的花瓣数量*/
private int petalCount=40; //默认40个
/**创建全部花瓣。true 创建全部,false 延时逐个创建*/
private boolean isCreateAll=true;
/**延时时长*/
private long delay=0;
/**随机生成器*/
private Random random;
private Handler handler;
public PetalView(Context context) {
this(context, null);
}
public PetalView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public PetalView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public PetalView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
drawThread.stopThread();
//handler = null;
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
canvasWidth = getWidth();
canvasHeight = getHeight();
handler=new Handler();
createPetals();
drawThread.start(); // 启动绘制线程
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
drawThread.stopThread(); // 当Surface销毁时被调用,停止绘制线程
}
/**
* 启动绘制线程。
* 这个方法必须在添加完花瓣图像,并且绘图表面创建完成后才能调用。
*/
public void startThread() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
drawThread = new DrawThread();
}
public void stopThread() {
drawThread.stopThread();
}
/**
* 添加花瓣图像资源ID,运行前调用添加
* @param resId
*/
public void addImage(int resId) {
petalImgList.add(resId);
}
/**
* 设置花瓣数量
* @param petalCount
* @param refresh 是否刷新,如果在运行过程中设置花瓣数量,需要刷新才会增加多出来的花瓣
*/
public void setPetalCount(int petalCount,boolean refresh) {
this.petalCount = petalCount;
if(refresh){
createPetals(); //创建多出来的花瓣
}
}
/**
* 运行开始即将所有花瓣创建完成
* @param createAll
*/
public void setCreateAll(boolean createAll) {
isCreateAll = createAll;
}
/**初始化*/
private void init() {
petalList=new ArrayList<>();
petalImgList=new ArrayList<>();
setZOrderOnTop(true); //放置在顶部
getHolder().setFormat(PixelFormat.TRANSLUCENT); //透明框架
}
/**创建花瓣列表*/
private void createPetals() {
if(isCreateAll) {
//需要创建的花瓣数=花瓣总数-已经存在的花瓣数
int createPetalCount = petalCount - petalList.size();
for (int i = 0; i < createPetalCount; i++) {
// 创建新的花瓣
Petal petal = new Petal(getContext(), petalImgList, canvasWidth, canvasHeight);
petalList.add(petal); // 添加到花瓣列表
}
}else{
if(random==null)
random=new Random();
handler.post(runnable);
}
}
/**运行单个花瓣创建*/
private Runnable runnable=new Runnable() {
@Override
public void run() {
if(petalList.size()<petalCount-1) {
// 创建新的花瓣
Petal petal = new Petal(getContext(), petalImgList, canvasWidth, canvasHeight);
petalList.add(petal); // 添加到花瓣列表
if(petalList.size()<petalCount-1) {
delay = random.nextInt(400);
handler.postDelayed(this, delay);
}
}
}
};
/**绘制线程*/
private class DrawThread extends Thread {
private boolean isRunning = true;
@Override
public void run() {
while (isRunning) {
Canvas canvas = surfaceHolder.lockCanvas(); // 锁定画布以进行绘制
if (canvas != null) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // 用透明色清空画布
for (int i = 0; i < petalList.size(); i++) { //循环绘制全部花瓣
Petal petal = petalList.get(i);
petal.draw(canvas); // 绘制花瓣
}
surfaceHolder.unlockCanvasAndPost(canvas); // 解锁画布并提交绘制结果
}
}
}
public void stopThread() {
isRunning = false;
}
}
}
类中为了在频繁绘制过程中不影响主线程运行,重写了SurfaceView类来绘制花瓣。效果如下:
花瓣飘落