描述
效果圖:
要實(shí)現(xiàn)一張圖片的爆炸效果错邦,有幾個(gè)關(guān)鍵的點(diǎn):
第一點(diǎn)土榴、根據(jù)圖片的寬和高獲取每一個(gè)像素的點(diǎn)荸哟,并且根據(jù)這個(gè)像素點(diǎn)構(gòu)建在這一個(gè)像素點(diǎn)ball 對(duì)象锦积;
第二點(diǎn)芒帕、在獲取ball 的數(shù)組對(duì)象的時(shí)候,需要在子線程來(lái)做這個(gè)事情丰介,防止UI卡頓背蟆。
第三點(diǎn)、啟動(dòng)一個(gè)動(dòng)畫(huà)來(lái)循環(huán)的調(diào)用繪制哮幢。
第四點(diǎn)带膀、在繪制的時(shí)候需要把所有ball 都一一繪制。
創(chuàng)建一個(gè)粒子對(duì)象(Ball)橙垢。
- 圖片像素點(diǎn)顏色值color
- 粒子圓心坐標(biāo)x
- 粒子圓心坐標(biāo)y
- 粒子半徑r
- 粒子運(yùn)動(dòng)水平方向速度vx
- 粒子運(yùn)動(dòng)垂直方向速度vy
- 粒子運(yùn)動(dòng)水平方式加速度ax
- 粒子運(yùn)動(dòng)垂直方向加速度ay
public int color; //圖片像素點(diǎn)顏色值
public float x; //粒子圓心坐標(biāo)x
public float y; //粒子圓心坐標(biāo)y
public float r; //粒子半徑
public float vX;//粒子運(yùn)動(dòng)水平方向速度
public float vY;//粒子運(yùn)動(dòng)垂直方向速度
public float aX;//粒子運(yùn)動(dòng)水平方向加速度
public float aY;//粒子運(yùn)動(dòng)垂直方向加速度
創(chuàng)建一個(gè)自定義view 垛叨,組合粒子。
- 初始畫(huà)筆Paint柜某,bitmap對(duì)象嗽元,粒子直徑(float類型)
paint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
- 獲取所有的粒子集合
//計(jì)算粒子數(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;
}
//這里的像素值,不準(zhǔn)確
int[] colors = new int[realWidth * realHeight];
// @param pixels 接收位圖顏色的數(shù)組
// @param offset 第一個(gè)要寫(xiě)入像素的索引[]
// @param stride 以像素[]為單位的項(xiàng)數(shù)喂击,可在其中跳過(guò)行(必須是>=位圖的寬度)剂癌。可以是負(fù)的翰绊。
// @param x 要讀取的第一個(gè)像素的x坐標(biāo)
// @param y 要讀取的第一個(gè)像素的y坐標(biāo)
// @param width 從每一行讀取的像素?cái)?shù)
// @param height 要讀取的行數(shù)
mBitmap.getPixels(colors, 0, realWidth, i, j, realWidth, realHeight);
int color = getColor(colors);
ball.setColor(color);
//線性增加多少佩谷,就縮小多少倍,這種來(lái)計(jì)算
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);
}
}
}
- 初始動(dòng)畫(huà)辞做,并且監(jiān)聽(tīng)動(dòng)畫(huà)變更琳要,改變Ball的位置和invalidate();
調(diào)用invalidate(),會(huì)執(zhí)行繪制的方法秤茅,onDraw();
// 創(chuàng)建一個(gè)線程池稚补,該線程池根據(jù)需要?jiǎng)?chuàng)建新線程,但是將重用以前構(gòu)造的線程可用框喳。這些池通常會(huì)提高性能
// 執(zhí)行許多短期異步任務(wù)的程序课幕。
// 對(duì){@code execute}的調(diào)用將重用前面構(gòu)造的
// 線程(如果可用)。如果沒(méi)有現(xiàn)有線程可用五垮,則使用一個(gè)新線程
// 線程將被創(chuàng)建并添加到池中乍惊。線程,
// * 60秒內(nèi)未使用的將被終止并移除
// *緩存。因此放仗,一個(gè)閑置足夠長(zhǎng)的池將會(huì)
// *不消耗任何資源润绎。注意,池與類似
// *屬性,但不同的細(xì)節(jié)(例如超時(shí)參數(shù))
valueAnimator = ValueAnimator.ofFloat(animBeginValue, animEndValue);
//線性插值器
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(0);//0 不重復(fù)莉撇,默認(rèn)也是0
valueAnimator.setDuration(duration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationFinish(ValueAnimator animation) {
//動(dòng)畫(huà)結(jié)束
Log.i("ball", "動(dòng)畫(huà)結(jié)束");
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//動(dòng)畫(huà)每一幀的改變
float value = (float) animation.getAnimatedValue();
// Log.i("ball","動(dòng)畫(huà)value=====" +value);
if (value == animEndValue) {
//動(dòng)畫(huà)執(zhí)行結(jié)束
this.onAnimationFinish(animation);
}
//在動(dòng)畫(huà)改變的時(shí)候呢蛤,調(diào)用繪制
updateBallState();
invalidate();
}
});
- 循環(huán)繪制所有的粒子
for (Ball ball : ballList) {
paint.setColor(ball.getColor());
canvas.drawCircle(ball.getX(), ball.getY(), ball.getR(), paint);
}
改進(jìn)
在布局文件中使用了自定義的view 的時(shí)候,寬度和高度棍郎,
如果沒(méi)有使用確切值的情況其障,默認(rèn)是填充滿屏幕的。
完整代碼:
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)建時(shí)間:2019/4/26
* 描述:優(yōu)化涂佃,確認(rèn)布局的寬和高度励翼,不然會(huì)出現(xiàn)問(wèn)題
*/
public class LZBallView extends View {
//粒子的集合,通過(guò)獲取圖片的寬度或者view 的寬度來(lái)計(jì)算辜荠,子線程做這個(gè)事情哦
private List<Ball> ballList = new ArrayList<>();
private Paint paint;//畫(huà)筆
//粒子的直徑.設(shè)置默認(rèn)值汽抚,后續(xù)通過(guò)屬性來(lái)設(shè)置
private float d = 20;
//動(dòng)畫(huà)
private ValueAnimator valueAnimator;
//動(dòng)畫(huà)時(shí)長(zhǎng),后面也是通過(guò)屬性配置的
private long duration = 3000;
private float animBeginValue = 0;
private float animEndValue = 1;
//圖片
private Bitmap mBitmap;
//用于控制粒子的個(gè)數(shù)伯病,如果是一個(gè)像素的一個(gè)像素的添加殊橙,那么粒子數(shù)目較多,默認(rèn)等于
private int ballPoor = 10;
//是否已經(jīng)開(kāi)始了動(dòng)畫(huà)
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)建一個(gè)線程池狱从,該線程池根據(jù)需要?jiǎng)?chuàng)建新線程,但是將重用以前構(gòu)造的線程可用叠纹。這些池通常會(huì)提高性能
// 執(zhí)行許多短期異步任務(wù)的程序季研。
// 對(duì){@code execute}的調(diào)用將重用前面構(gòu)造的
// 線程(如果可用)。如果沒(méi)有現(xiàn)有線程可用誉察,則使用一個(gè)新線程
// 線程將被創(chuàng)建并添加到池中与涡。線程,
// * 60秒內(nèi)未使用的將被終止并移除
// *緩存。因此持偏,一個(gè)閑置足夠長(zhǎng)的池將會(huì)
// *不消耗任何資源驼卖。注意,池與類似
// *屬性鸿秆,但不同的細(xì)節(jié)(例如超時(shí)參數(shù))
Executors.newCachedThreadPool().execute(new Runnable() {
@Override
public void run() {
calculateBalls();
}
});
valueAnimator = ValueAnimator.ofFloat(animBeginValue, animEndValue);
//線性插值器
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(0);//0 不重復(fù)酌畜,默認(rèn)也是0
valueAnimator.setDuration(duration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationFinish(ValueAnimator animation) {
//動(dòng)畫(huà)結(jié)束
Log.i("ball", "動(dòng)畫(huà)結(jié)束");
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//動(dòng)畫(huà)每一幀的改變
float value = (float) animation.getAnimatedValue();
// Log.i("ball","動(dòng)畫(huà)value=====" +value);
if (value == animEndValue) {
//動(dòng)畫(huà)執(zhí)行結(jié)束
this.onAnimationFinish(animation);
}
//在動(dòng)畫(huà)改變的時(shí)候,調(diào)用繪制
updateBallState();
invalidate();
}
});
}
//計(jì)算粒子數(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;
}
//這里的像素值卿叽,不準(zhǔn)確
int[] colors = new int[realWidth * realHeight];
// @param pixels 接收位圖顏色的數(shù)組
// @param offset 第一個(gè)要寫(xiě)入像素的索引[]
// @param stride 以像素[]為單位的項(xiàng)數(shù)桥胞,可在其中跳過(guò)行(必須是>=位圖的寬度)】加ぃ可以是負(fù)的贩虾。
// @param x 要讀取的第一個(gè)像素的x坐標(biāo)
// @param y 要讀取的第一個(gè)像素的y坐標(biāo)
// @param width 從每一行讀取的像素?cái)?shù)
// @param height 要讀取的行數(shù)
mBitmap.getPixels(colors, 0, realWidth, i, j, realWidth, realHeight);
int color = getColor(colors);
ball.setColor(color);
//線性增加多少,就縮小多少倍沥阱,這種來(lái)計(jì)算
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ù) 再隨機(jī)
return (int) (min + Math.ceil(Math.random() * (max - min)));
}
//動(dòng)畫(huà)執(zhí)行過(guò)程中,改變粒子的狀態(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í)行動(dòng)畫(huà)
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);
//繪制時(shí)間過(guò)長(zhǎng)會(huì)直接崩潰的
int width = getMeasuredWidth();
int height = getMeasuredHeight();
//畫(huà)布的平移操作
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);
}
}