描述
效果圖:

image.png
要實現(xiàn)一張圖片的爆炸效果,有幾個關(guān)鍵的點:
第一點、根據(jù)圖片的寬和高獲取每一個像素的點,并且根據(jù)這個像素點構(gòu)建在這一個像素點ball 對象;
第二點、在獲取ball 的數(shù)組對象的時候,需要在子線程來做這個事情,防止UI卡頓。
第三點、啟動一個動畫來循環(huán)的調(diào)用繪制。
第四點、在繪制的時候需要把所有ball 都一一繪制。
創(chuàng)建一個粒子對象(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;//粒子運動垂直方向加速度
創(chuàng)建一個自定義view ,組合粒子。
- 初始畫筆Paint,bitmap對象,粒子直徑(float類型)
paint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
- 獲取所有的粒子集合
//計算粒子數(shù)
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 接收位圖顏色的數(shù)組
// @param offset 第一個要寫入像素的索引[]
// @param stride 以像素[]為單位的項數(shù),可在其中跳過行(必須是>=位圖的寬度)??梢允秦摰摹?// @param x 要讀取的第一個像素的x坐標
// @param y 要讀取的第一個像素的y坐標
// @param width 從每一行讀取的像素數(shù)
// @param height 要讀取的行數(shù)
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);
}
}
}
- 初始動畫,并且監(jiān)聽動畫變更,改變Ball的位置和invalidate();
調(diào)用invalidate(),會執(zhí)行繪制的方法,onDraw();
// 創(chuàng)建一個線程池,該線程池根據(jù)需要創(chuàng)建新線程,但是將重用以前構(gòu)造的線程可用。這些池通常會提高性能
// 執(zhí)行許多短期異步任務(wù)的程序。
// 對{@code execute}的調(diào)用將重用前面構(gòu)造的
// 線程(如果可用)。如果沒有現(xiàn)有線程可用,則使用一個新線程
// 線程將被創(chuàng)建并添加到池中。線程,
// * 60秒內(nèi)未使用的將被終止并移除
// *緩存。因此,一個閑置足夠長的池將會
// *不消耗任何資源。注意,池與類似
// *屬性,但不同的細節(jié)(例如超時參數(shù))
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) {
//動畫結(jié)束
Log.i("ball", "動畫結(jié)束");
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//動畫每一幀的改變
float value = (float) animation.getAnimatedValue();
// Log.i("ball","動畫value=====" +value);
if (value == animEndValue) {
//動畫執(zhí)行結(jié)束
this.onAnimationFinish(animation);
}
//在動畫改變的時候,調(diào)用繪制
updateBallState();
invalidate();
}
});
- 循環(huán)繪制所有的粒子
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
* 創(chuàng)建時間:2019/4/26
* 描述:優(yōu)化,確認布局的寬和高度,不然會出現(xiàn)問題
*/
public class LZBallView extends View {
//粒子的集合,通過獲取圖片的寬度或者view 的寬度來計算,子線程做這個事情哦
private List<Ball> ballList = new ArrayList<>();
private Paint paint;//畫筆
//粒子的直徑.設(shè)置默認值,后續(xù)通過屬性來設(shè)置
private float d = 20;
//動畫
private ValueAnimator valueAnimator;
//動畫時長,后面也是通過屬性配置的
private long duration = 3000;
private float animBeginValue = 0;
private float animEndValue = 1;
//圖片
private Bitmap mBitmap;
//用于控制粒子的個數(shù),如果是一個像素的一個像素的添加,那么粒子數(shù)目較多,默認等于
private int ballPoor = 10;
//是否已經(jīng)開始了動畫
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);
// 創(chuàng)建一個線程池,該線程池根據(jù)需要創(chuàng)建新線程,但是將重用以前構(gòu)造的線程可用。這些池通常會提高性能
// 執(zhí)行許多短期異步任務(wù)的程序。
// 對{@code execute}的調(diào)用將重用前面構(gòu)造的
// 線程(如果可用)。如果沒有現(xiàn)有線程可用,則使用一個新線程
// 線程將被創(chuàng)建并添加到池中。線程,
// * 60秒內(nèi)未使用的將被終止并移除
// *緩存。因此,一個閑置足夠長的池將會
// *不消耗任何資源。注意,池與類似
// *屬性,但不同的細節(jié)(例如超時參數(shù))
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) {
//動畫結(jié)束
Log.i("ball", "動畫結(jié)束");
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//動畫每一幀的改變
float value = (float) animation.getAnimatedValue();
// Log.i("ball","動畫value=====" +value);
if (value == animEndValue) {
//動畫執(zhí)行結(jié)束
this.onAnimationFinish(animation);
}
//在動畫改變的時候,調(diào)用繪制
updateBallState();
invalidate();
}
});
}
//計算粒子數(shù)
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 接收位圖顏色的數(shù)組
// @param offset 第一個要寫入像素的索引[]
// @param stride 以像素[]為單位的項數(shù),可在其中跳過行(必須是>=位圖的寬度)??梢允秦摰?。
// @param x 要讀取的第一個像素的x坐標
// @param y 要讀取的第一個像素的y坐標
// @param width 從每一行讀取的像素數(shù)
// @param height 要讀取的行數(shù)
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)范圍內(nèi)變化,取大于x的最小整數(shù) 再隨機
return (int) (min + Math.ceil(Math.random() * (max - min)));
}
//動畫執(zhí)行過程中,改變粒子的狀態(tài)
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) {
//執(zhí)行動畫
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);
}
}