Android实现图片粉碎效果

Android使用ValueAnimator实现图片粉碎特效

效果图如下:
Android实现图片粉碎效果

效果看起来还是比较酷炫的,其实实现起来也很简单,主要分三步:
将图片中的每一个像素信息保存并绘制在屏幕上
设置属性动画,动画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);
}

打完收工,以前看到这种效果只知道哇哇哇,但是对于如何实现竟然没有一点思路。这里最重要的思路就是就每一个像素点拿出来去绘制图片,把一个图片拆分成无数个像素,然后对每一个模拟出来的像素点进行控制就可以了。

不过还有很多可以优化的地方,很显然,我们用的图片不能含有太多像素,否则会导致控制的粒子数过多,然后非常的卡,简直没法看。

解决方案有一定的思路,比如每三个像素点取一个数据,或者先对图片进行压缩,再一个一个取像素点数据,然后在一开始把原图绘制在屏幕上,点击时把原图干掉,替换成比较模糊的保存的像素阵列,同时播放属性动画,就可以规避图片太大,像素太多的问题啦。