Andorid的好东西Android启动性能Android项目

小米卸载动画-图标爆炸

2018-08-27  本文已影响263人  小庄bb

前言

前一段事件接触到了小米手机的卸载动画。炫酷效果,瞬间爆炸好吗?好奇心也瞬间爆炸,凭什么我们的图标就只能移来移去,秉着不服气的心情,我决定仔细研究研究它是怎么爆炸的。

正文

上面说了很多次图标爆炸,那到底是怎么爆炸的呢?我们来看效果。


爆炸动画

这是github上的一个开源项目,该项目的连接地址为:https://github.com/tyrantgit/ExplosionField

使用动画

已经有大神为我等菜鸟封装好了git远程引用库,这样很方便就可以使用图标爆炸功能。

dependencies {
   compile 'tyrantgit:explosionfield:1.0.1'
 }
//实现爆炸动画
mExplosionField = ExplosionField.attach2Window(this);
mExplosionField.explode(v);

怎么样,是不是很!简!单!

动画实现原理

那么这看起来如此炫酷的动画到底是如何实现的呢?我们知道调用mExplosionField.explode(v);可以实现爆炸动画。而explode方法是ExplosionField类为我们提供的一个方法它本身又继承于View。首先export方法有什么内容:

public void explode(final View view) {
        Rect r = new Rect();
        //getLocalVisibleRect(Rect r)方法可以把视图的长和宽映射到一个Rect对象上。
        view.getGlobalVisibleRect(r);
        int[] location = new int[2];
        //获取当前坐标
        getLocationOnScreen(location);

        //使矩形与view重叠
        r.offset(-location[0], -location[1]);
        r.inset(-mExpandInset[0], -mExpandInset[1]);

        //执行view抖动的动画
        int startDelay = 100;
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            Random random = new Random();

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                view.setTranslationX((random.nextFloat() - 0.5f) * view.getWidth() * 0.05f);
                view.setTranslationY((random.nextFloat() - 0.5f) * view.getHeight() * 0.05f);

            }
        });
        animator.start();

        //隐藏view动画
        view.animate().setDuration(150).setStartDelay(startDelay).scaleX(0f).scaleY(0f).alpha(0f).start();

        //执行真正的炸裂动画
        explode(Utils.createBitmapFromView(view), r, startDelay, ExplosionAnimator.DEFAULT_DURATION);
    }

可以看到,export方法中无非就是将VIew映射到一个矩形中,然后做抖动动画。其中explode(Utils.createBitmapFromView(view), r, startDelay, ExplosionAnimator.DEFAULT_DURATION);才是真正调用完成爆炸的功能。

 public void explode(Bitmap bitmap, Rect bound, long startDelay, long duration) {
        final ExplosionAnimator explosion = new ExplosionAnimator(this, bitmap, bound);
        explosion.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mExplosions.remove(animation);
            }
        });
        explosion.setStartDelay(startDelay);
        explosion.setDuration(duration);
        mExplosions.add(explosion);
        explosion.start();
    }

explode(Utils.createBitmapFromView(view), r, startDelay, ExplosionAnimator.DEFAULT_DURATION);方法中,重点内容就是ExplosionAnimator类的实例化。这个类是一个继承ValueAnimator的类,它实现了爆炸动画的主要内容。

public class ExplosionAnimator extends ValueAnimator {

    static long DEFAULT_DURATION = 0x400;
    private static final Interpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator(0.6f);
    private static final float END_VALUE = 1.4f;
    private static final float X = Utils.dp2Px(5);
    private static final float Y = Utils.dp2Px(20);
    private static final float V = Utils.dp2Px(2);
    private static final float W = Utils.dp2Px(1);
    private Paint mPaint;
    private Particle[] mParticles;
    private Rect mBound;
    private View mContainer;

    public ExplosionAnimator(View container, Bitmap bitmap, Rect bound) {
        mPaint = new Paint();
        mBound = new Rect(bound);
        int partLen = 15;
        mParticles = new Particle[partLen * partLen];
        Random random = new Random(System.currentTimeMillis());
        int w = bitmap.getWidth() / (partLen + 2);
        int h = bitmap.getHeight() / (partLen + 2);
        for (int i = 0; i < partLen; i++) {
            for (int j = 0; j < partLen; j++) {
                //GetPixel,函数功能,该函数检索指定坐标点的像素的RGB颜色值。
                mParticles[(i * partLen) + j] = generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random);
            }
        }
        mContainer = container;
        setFloatValues(0f, END_VALUE);
        setInterpolator(DEFAULT_INTERPOLATOR);
        setDuration(DEFAULT_DURATION);
    }

    private Particle generateParticle(int color, Random random) {
        Particle particle = new Particle();
        particle.color = color;
        particle.radius = V;
        if (random.nextFloat() < 0.2f) {
            //2 - 5
            particle.baseRadius = V + ((X - V) * random.nextFloat());
        } else {
            //1 - 2
            particle.baseRadius = W + ((V - W) * random.nextFloat());
        }

        float nextFloat = random.nextFloat();
        //0.2 - 0.38
        particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f);
        //0.2 - 0.236 : 0.4 - 0.58
        particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat());

        //bottom可能是x轴位移偏移总量
        //-0.9 - 0.9
        particle.bottom = (mBound.height() * (random.nextFloat() - 0.5f)) * 1.8f;
        float f = nextFloat < 0.2f ? particle.bottom : nextFloat < 0.8f ? particle.bottom * 0.6f : particle.bottom * 0.3f;
        particle.bottom = f;

        particle.mag = 4.0f * particle.top / particle.bottom;
        particle.neg = (-particle.mag) / particle.bottom;

        f = mBound.centerX() + (Y * (random.nextFloat() - 0.5f));
        particle.baseCx = f;
        particle.cx = f;
        f = mBound.centerY() + (Y * (random.nextFloat() - 0.5f));
        particle.baseCy = f;
        particle.cy = f;
        particle.life = END_VALUE / 10 * random.nextFloat();
        particle.overflow = 0.4f * random.nextFloat();
        particle.alpha = 1f;
        return particle;
    }

    public boolean draw(Canvas canvas) {
        if (!isStarted()) {
            return false;
        }
        for (Particle particle : mParticles) {
            particle.advance((float) getAnimatedValue());
            if (particle.alpha > 0f) {
                mPaint.setColor(particle.color);
                mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha));
                canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint);
            }
        }
        mContainer.invalidate();
        return true;
    }

    @Override
    public void start() {
        super.start();
        mContainer.invalidate(mBound);
    }

    private class Particle {
        float alpha;
        int color;
        float cx;
        float cy;
        float radius;
        float baseCx;
        float baseCy;
        float baseRadius;
        float top;
        float bottom;
        float mag;
        float neg;
        float life;
        float overflow;

        //根据属性动画进度,来获得小圆点当前属性
        public void advance(float factor) {
            float f = 0f;
            float normalization = factor / END_VALUE;

            //这里设定当小圆点动画进行到进度,也就是大于或者小于某一个临界值时,置为透明。
            if (normalization < life || normalization > 1f - overflow) {
                alpha = 0f;
                return;
            }
            //计算出在可显示区间的进度
            normalization = (normalization - life) / (1f - life - overflow);

            //计算此时应有的值
            float f2 = normalization * END_VALUE;

            //当进度超过0.7f时,使小圆点开始变透明。
            if (normalization >= 0.7f) {
                f = (normalization - 0.7f) / 0.3f;
            }
            alpha = 1f - f;

            //小圆点下一个位移点
            f = bottom * f2;
            cx = baseCx + f;

            cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;
            radius = V + (baseRadius - V) * f2;
        }
    }
}

以上为ExplosionAnimator类的所有代码,也是实现爆炸动画的主要内容。在其内部存在一个内部类Particle,该类盛放了一个小圆点的所有属性信息。ExplosionAnimator类的实例化过程中将传入的view映射的矩形依照像素为颜色分解成15*15个小圆点将其属性数据存储在Particle实例中,通过generateParticle()方法使得小圆点按照一定的比例获得不同的属性值,决定之后的运动轨迹以及透明度变化。
现在已经搞清楚,图标是怎么变成很多个小球的了,最后来看看,小球运动的动画如何实现。
在上面我们有看到ExplosionAnimator实例化之后紧接着调用了start()方法。这就要看到ExplosionField类的onDraw()方法了。

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (ExplosionAnimator explosion : mExplosions) {
            explosion.draw(canvas);
        }
    }

其中主要内容便是调用了explosion.draw(canvas);

public boolean draw(Canvas canvas) {
        if (!isStarted()) {
            return false;
        }
        for (Particle particle : mParticles) {
            particle.advance((float) getAnimatedValue());
            if (particle.alpha > 0f) {
                mPaint.setColor(particle.color);
                mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha));
                canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint);
            }
        }
        mContainer.invalidate();
        return true;
    }

这个方法才真正实现了对view映射的矩形进行操作,根据getAnimatedValue()动画的进度来进行小圆点状态的刷新,从而实现爆炸动画。

总结

爆炸动画的实现,其实就是一个属性动画的灵活运用,使用属性动画为我们提供一个动画进度,利用再view的刷新机制去完成动画效果。

上一篇下一篇

猜你喜欢

热点阅读