描述
效果图:
要实现一张图片的爆炸效果,有几个关键的点:
第一点、根据图片的宽和高获取每一个像素的点,并且根据这个像素点构建在这一个像素点ball 对象;
第二点、在获取ball 的数组对象的时候,需要在子线程来做这个事情,防止UI卡顿。
第三点、启动一个动画来循环的调用绘制。
第四点、在绘制的时候需要把所有ball 都一一绘制。
创建一个粒子对象(Ball)。
- 图片像素点颜色值color
- 粒子圆心坐标x
- 粒子圆心坐标y
- 粒子半径r
- 粒子运动水平方向速度vx
- 粒子运动垂直方向速度vy
- 粒子运动水平方式加速度ax
- 粒子运动垂直方向加速度ay
public int color; //图片像素点颜色值
public float x; //粒子圆心坐标x
public float y; //粒子圆心坐标y
public float r; //粒子半径
public float vX;//粒子运动水平方向速度
public float vY;//粒子运动垂直方向速度
public float aX;//粒子运动水平方向加速度
public float aY;//粒子运动垂直方向加速度
创建一个自定义view ,组合粒子。
- 初始画笔Paint,bitmap对象,粒子直径(float类型)
paint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
- 获取所有的粒子集合
//计算粒子数
private void calculateBalls() {
if (mBitmap == null || mBitmap.isRecycled()) return;
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
for (int i = 0; i < width; i += ballPoor) {
for (int j = 0; j < height; j += ballPoor) {
Ball ball = new Ball();
int realWidth = ballPoor;
int realHeight = ballPoor;
if (i + realWidth > width) {
realWidth = width - i;
}
if (j + realHeight > height) {
realHeight = height - j;
}
//这里的像素值,不准确
int[] colors = new int[realWidth * realHeight];
// @param pixels 接收位图颜色的数组
// @param offset 第一个要写入像素的索引[]
// @param stride 以像素[]为单位的项数,可在其中跳过行(必须是>=位图的宽度)。可以是负的。
// @param x 要读取的第一个像素的x坐标
// @param y 要读取的第一个像素的y坐标
// @param width 从每一行读取的像素数
// @param height 要读取的行数
mBitmap.getPixels(colors, 0, realWidth, i, j, realWidth, realHeight);
int color = getColor(colors);
ball.setColor(color);
//线性增加多少,就缩小多少倍,这种来计算
ball.x = i * d / ballPoor + d / 2;
ball.y = j * d / ballPoor + d / 2;
ball.setR((float) d / 2);
//x方向速度20或者-20
ball.setvX((float) (Math.pow(-1, Math.ceil(Math.random() * 1000)) * 20 * Math.random()));
ball.setvY(rangeInt(-10, 20));
ball.setaX(0);
ball.setaY(0.98f);
ballList.add(ball);
}
}
}
- 初始动画,并且监听动画变更,改变Ball的位置和invalidate();
调用invalidate(),会执行绘制的方法,onDraw();
// 创建一个线程池,该线程池根据需要创建新线程,但是将重用以前构造的线程可用。这些池通常会提高性能
// 执行许多短期异步任务的程序。
// 对{@code execute}的调用将重用前面构造的
// 线程(如果可用)。如果没有现有线程可用,则使用一个新线程
// 线程将被创建并添加到池中。线程,
// * 60秒内未使用的将被终止并移除
// *缓存。因此,一个闲置足够长的池将会
// *不消耗任何资源。注意,池与类似
// *属性,但不同的细节(例如超时参数)
valueAnimator = ValueAnimator.ofFloat(animBeginValue, animEndValue);
//线性插值器
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(0);//0 不重复,默认也是0
valueAnimator.setDuration(duration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationFinish(ValueAnimator animation) {
//动画结束
Log.i("ball", "动画结束");
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//动画每一帧的改变
float value = (float) animation.getAnimatedValue();
// Log.i("ball","动画value=====" +value);
if (value == animEndValue) {
//动画执行结束
this.onAnimationFinish(animation);
}
//在动画改变的时候,调用绘制
updateBallState();
invalidate();
}
});
- 循环绘制所有的粒子
for (Ball ball : ballList) {
paint.setColor(ball.getColor());
canvas.drawCircle(ball.getX(), ball.getY(), ball.getR(), paint);
}
改进
在布局文件中使用了自定义的view 的时候,宽度和高度,
如果没有使用确切值的情况,默认是填充满屏幕的。
完整代码:
package com.netease.canvas.split;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
/**
* 作者:liupeng
* 创建时间:2019/4/26
* 描述:优化,确认布局的宽和高度,不然会出现问题
*/
public class LZBallView extends View {
//粒子的集合,通过获取图片的宽度或者view 的宽度来计算,子线程做这个事情哦
private List ballList = new ArrayList<>();
private Paint paint;//画笔
//粒子的直径.设置默认值,后续通过属性来设置
private float d = 20;
//动画
private ValueAnimator valueAnimator;
//动画时长,后面也是通过属性配置的
private long duration = 3000;
private float animBeginValue = 0;
private float animEndValue = 1;
//图片
private Bitmap mBitmap;
//用于控制粒子的个数,如果是一个像素的一个像素的添加,那么粒子数目较多,默认等于
private int ballPoor = 10;
//是否已经开始了动画
private boolean startAnim;
public LZBallView(Context context) {
this(context, null);
}
public LZBallView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
//初始化操作
private void init() {
paint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
// 创建一个线程池,该线程池根据需要创建新线程,但是将重用以前构造的线程可用。这些池通常会提高性能
// 执行许多短期异步任务的程序。
// 对{@code execute}的调用将重用前面构造的
// 线程(如果可用)。如果没有现有线程可用,则使用一个新线程
// 线程将被创建并添加到池中。线程,
// * 60秒内未使用的将被终止并移除
// *缓存。因此,一个闲置足够长的池将会
// *不消耗任何资源。注意,池与类似
// *属性,但不同的细节(例如超时参数)
Executors.newCachedThreadPool().execute(new Runnable() {
@Override
public void run() {
calculateBalls();
}
});
valueAnimator = ValueAnimator.ofFloat(animBeginValue, animEndValue);
//线性插值器
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(0);//0 不重复,默认也是0
valueAnimator.setDuration(duration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationFinish(ValueAnimator animation) {
//动画结束
Log.i("ball", "动画结束");
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//动画每一帧的改变
float value = (float) animation.getAnimatedValue();
// Log.i("ball","动画value=====" +value);
if (value == animEndValue) {
//动画执行结束
this.onAnimationFinish(animation);
}
//在动画改变的时候,调用绘制
updateBallState();
invalidate();
}
});
}
//计算粒子数
private void calculateBalls() {
if (mBitmap == null || mBitmap.isRecycled()) return;
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
for (int i = 0; i < width; i += ballPoor) {
for (int j = 0; j < height; j += ballPoor) {
Ball ball = new Ball();
int realWidth = ballPoor;
int realHeight = ballPoor;
if (i + realWidth > width) {
realWidth = width - i;
}
if (j + realHeight > height) {
realHeight = height - j;
}
//这里的像素值,不准确
int[] colors = new int[realWidth * realHeight];
// @param pixels 接收位图颜色的数组
// @param offset 第一个要写入像素的索引[]
// @param stride 以像素[]为单位的项数,可在其中跳过行(必须是>=位图的宽度)。可以是负的。
// @param x 要读取的第一个像素的x坐标
// @param y 要读取的第一个像素的y坐标
// @param width 从每一行读取的像素数
// @param height 要读取的行数
mBitmap.getPixels(colors, 0, realWidth, i, j, realWidth, realHeight);
int color = getColor(colors);
ball.setColor(color);
//线性增加多少,就缩小多少倍,这种来计算
ball.x = i * d / ballPoor + d / 2;
ball.y = j * d / ballPoor + d / 2;
ball.setR((float) d / 2);
//x方向速度20或者-20
ball.setvX((float) (Math.pow(-1, Math.ceil(Math.random() * 1000)) * 20 * Math.random()));
ball.setvY(rangeInt(-10, 20));
ball.setaX(0);
ball.setaY(0.98f);
ballList.add(ball);
}
}
}
//获取颜色
private int getColor(int[] colors) {
int ar = 0, ag = 0, ab = 0;
for (int color : colors) {
int r = (color & 0xff0000) >> 16;
int g = (color & 0x00ff00) >> 8;
int b = color & 0x0000ff;
ar += r;
ag += g;
ab += b;
}
return Color.rgb(ar / colors.length, ag / colors.length, ab / colors.length);
}
private float rangeInt(int i, int j) {
int max = Math.max(i, j);
int min = Math.min(i, j) - 1;
//在0到(max - min)范围内变化,取大于x的最小整数 再随机
return (int) (min + Math.ceil(Math.random() * (max - min)));
}
//动画执行过程中,改变粒子的状态
private void updateBallState() {
for (Ball ball : ballList) {
ball.setX(ball.getX() + ball.getvX());
ball.setY(ball.getY() + ball.getvY());
ball.setvX(ball.getvX() + ball.getaX());
ball.setvY(ball.getvY() + ball.getaY());
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//执行动画
startAnim = true;
valueAnimator.start();
}
return super.onTouchEvent(event);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int mHeight = MeasureSpec.getSize(heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制时间过长会直接崩溃的
int width = getMeasuredWidth();
int height = getMeasuredHeight();
//画布的平移操作
if (width >= mBitmap.getWidth()) {
if (height >= mBitmap.getHeight()){
canvas.translate(width/2 -mBitmap.getWidth()/2, height/2 -mBitmap.getHeight()/2);
}else{
canvas.translate(width/2 -mBitmap.getWidth()/2, 0);
}
}else {
//view 的宽度是小于图片的宽度的什么都不做
if (height >= mBitmap.getHeight()){
canvas.translate(0, height/2 -mBitmap.getHeight()/2);
}else{
canvas.translate(0, 0);
}
}
if (!startAnim) {
canvas.drawBitmap(mBitmap, 0, 0, paint);
return;
}
for (Ball ball : ballList) {
paint.setColor(ball.getColor());
canvas.drawCircle(ball.getX(), ball.getY(), ball.getR(), paint);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (valueAnimator != null) {
valueAnimator.cancel();
}
}
interface AnimatorUpdateListener extends ValueAnimator.AnimatorUpdateListener {
void onAnimationFinish(ValueAnimator animation);
}
}