Android实现图片粉碎效果
Android使用ValueAnimator实现图片粉碎特效
效果图如下:
- 效果看起来还是比较酷炫的,其实实现起来也很简单,主要分三步:
- 将图片中的每一个像素信息保存并绘制在屏幕上
- 设置属性动画,动画update中,更新每一个像素的位置信息,并重新绘制
- 设置View的点击事件,触发动画
保存像素信息
我们这里需要让破碎后的粒子做水平、垂直方向的初速度不同的*落体运动,所以需要保存每一个粒子的颜色值、坐标及X、Y方向上的速度等。
Ball.java
public class Ball {
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;// 粒子垂直加速度
}
接下来需要将图片中的所有像素信息映射成一个Ball对象。
我们通过自定义View来实现效果:
public class SplitView extends View {
//保存每一个像素对应的Ball对象
private List<Ball> mBalls = new ArrayList<>();
//用来绘制的画笔
private Paint mPaint;
//每一个粒子的直径
//该参数控制原来每一个像素点在屏幕上的缩放比例,此时为放大两倍
private int d = 2;
public SplitView(Context context) {
this(context, null);
}
public SplitView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SplitView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
//初始化画笔、Ball列表、属性动画
private void init() {
//画笔
mPaint = new Paint();
//解析图片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kenan);
//像素个数=bitmap的宽*高
for (int i = 0; i < bitmap.getWidth(); i++) {
for (int j = 0; j < bitmap.getHeight(); j++) {
Ball ball = new Ball();
ball.color = bitmap.getPixel(i, j);
//得到粒子圆心坐标
ball.x = i * d + d / 2;
ball.y = j * d + d / 2;
ball.r = d / 2;
//x方向速度在(-20,20)
ball.vX = (float) (Math.pow(-1, Math.ceil(Math.random() * 1000)) * 20 * Math.random());
ball.vY = rangInt(-15,35);
ball.aX = 0f;
ball.aY = 0.98f;
mBalls.add(ball);
}
}
//属性动画
//...
}
}
通过Bitmap.getPixel(x,y)可以获得当前像素的颜色值,而i, j则分别是当前像素的x, y坐标;并给每一个粒子生成了随机初始速度。
我们来看下**rangInt(i, j)**方法:
private int rangInt(int i, int j) {
int max = Math.max(i, j);
int min = Math.min(i, j) - 1;
return (int) (min + Math.ceil(Math.random() * (max - min)));
}
最终的结果就是Y方向的初始速度在(-16, 34)的区间内。
接下来我们通过onDraw方法将所有的像素点绘制在屏幕上。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//平移画布,使图片绘制在屏幕中心
canvas.translate(400,500);
for (Ball ball : mBalls) {
mPaint.setColor(ball.color);
canvas.drawCircle(ball.x, ball.y, ball.r, mPaint);
}
}
至此,我们已完成了绘制工作,接下来定义属性动画。
定义属性动画
我们使用ValueAnimator从0到1的浮点数进行线性插值,在onAnimationUpdate中更新粒子的位置和速度信息,然后调用invalidate方法通知界面重绘。
private ValueAnimator mAnimator;
private void init() {
//...
//属性动画
mAnimator = ValueAnimator.ofFloat(0,1); // 这里的数值随便设置
mAnimator.setRepeatCount(-1); // 设置动画无限循环
mAnimator.setDuration(2000); // 一次动画时长两秒
//设置线性插值器(Android默认插值器效果是先快后慢)
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//更新粒子坐标信息
updateBall();
//刷新界面,会调用onDraw
invalidate();
}
});
}
private void updateBall() {
//更新粒子的位置
for (Ball ball : mBalls){
ball.x += ball.vX;
ball.y += ball.vY;
ball.vX += ball.aX;
ball.vY += ball.aY;
}
}
设置点击监听,触发动画
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN){
mAnimator.start();
}
return super.onTouchEvent(event);
}
打完收工,以前看到这种效果只知道哇哇哇,但是对于如何实现竟然没有一点思路。这里最重要的思路就是就每一个像素点拿出来去绘制图片,把一个图片拆分成无数个像素,然后对每一个模拟出来的像素点进行控制就可以了。
不过还有很多可以优化的地方,很显然,我们用的图片不能含有太多像素,否则会导致控制的粒子数过多,然后非常的卡,简直没法看。
解决方案有一定的思路,比如每三个像素点取一个数据,或者先对图片进行压缩,再一个一个取像素点数据,然后在一开始把原图绘制在屏幕上,点击时把原图干掉,替换成比较模糊的保存的像素阵列,同时播放属性动画,就可以规避图片太大,像素太多的问题啦。