美文网首页
Android开源爆炸效果分析

Android开源爆炸效果分析

作者: gczxbb | 来源:发表于2018-11-24 23:17 被阅读34次

最近决定好好整理一下,包括简书文章、自己平时关注研究和实现的代码。之前做过很多东西,都记录的比较散,要么文档不全,要么看过的源码没有留下痕迹,结果继续看和新的一样,那是忘得真快啊,花点时间复习一下,总结也是对知识的一种尊重,就从之前看过的一个开源爆炸特效开始吧。以后每天坚持知识总结并将零散的技术文档搬到简书。加油。
开源爆炸效果github地址


正题

一个视图要实现爆炸,关注以下几点。

1:爆炸图层。
2:爆炸区域确定,以视图中心区域为起始点向外发散
3:将视图View实现分割取色。
4:爆炸碎片特性初始化。
5:动画实现,这个最复杂,也是核心。使用随机+概率。

爆炸图层

自定义视图ExplosionField作为动画图层,加入到对应资源节点com.android.internal.R.id.content的FrameLayout视图。

public static ExplosionField attach2Window(Activity activity) {
    ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
    ExplosionField explosionField = new ExplosionField(activity);
    rootView.addView(explosionField, new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    return explosionField;
}

ExplosionField是MATCH_PARENT,与setContentView的视图同一父视图FrameLayout,它在上层。
setContentView加入一个待爆炸的视图,+监听。


爆炸区域

点击视图,爆炸效果是从视图中心区域向外发散。动画起点在爆炸视图的中心区域。

public void explode(final View view) {
    Rect r = new Rect();
    view.getGlobalVisibleRect(r);
    int[] location = new int[2];
    getLocationOnScreen(location);
    r.offset(-location[0], -location[1]);
    r.inset(-mExpandInset[0], -mExpandInset[1]);//Rect(287, 685 - 793, 1191)扩大一定区域
    int startDelay = 100;
    view.animate().setDuration(150).setStartDelay(startDelay).scaleX(0f).scaleY(0f).alpha(0f).start();//View缩放动画
    explode(Utils.createBitmapFromView(view), r, startDelay, ExplosionAnimator.DEFAULT_DURATION);
}

View是待爆炸视图,getGlobalVisibleRect方法获取该视图以最顶层父视图DecorView为原点的坐标区域。这点注意,并不是以他父视图左上位坐标原点。
我获取到的该区域是Rect(375, 1015 - 705, 1345)。
getLocationOnScreen获取以屏幕左上为原点,爆炸图层的坐标,它以MATCH_PARENT加入。
我获取到该坐标是(0,242),242是系统标题栏和状态栏高度。
视图区域Rect#offset,坐标原点转换,前Rect坐标原点是DecorView视图,转换成装载setContentView的父视图。
我获取到的该区域是Rect(375, 773 - 705, 1103)。

与setContentView同样大小的覆盖图层ExplosionField中确定了下层爆炸视图的位置坐标。整个ExplosionField是爆炸区域,这么做的目的是找到爆炸视图位置从而确定爆炸中心点的坐标。

爆炸视图区域.jpg

黄色是ExplosionField,绿色是最终确定的区域Rect。
下层待爆炸视图设置一个缩小动画,在150毫秒很短的时间内缩放消失。

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();
}

几乎在同时,爆炸图层启动一个爆炸动画ExplosionAnimator 。爆炸图层支持下层多个视图同时爆炸。


Bitmap分割采样取色

1:从View中获取Bitmap。
2:Bitmap取色。

//从View获取Bitmap
public static Bitmap createBitmapFromView(View view) {
    if (view instanceof ImageView) {
        Drawable drawable = ((ImageView) view).getDrawable();
        if (drawable != null && drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }
    }
    view.clearFocus();
    Bitmap bitmap = createBitmapSafely(view.getWidth(),
            view.getHeight(), Bitmap.Config.ARGB_8888, 1);
    if (bitmap != null) {
        synchronized (sCanvas) {
            Canvas canvas = sCanvas;
            canvas.setBitmap(bitmap);
            view.draw(canvas);
            canvas.setBitmap(null);
        }
    }
    return bitmap;
}

源码中放置的是ImageView,从BitmapDrawable获取。否则,利用Canvas获取。
我设置了一个ImageView背景蓝色,src是一个圆形G图片,边角透明,并未取到View边角蓝色部分,而是整个图片资源src的Bitmap像素。
创建动画时,将Bitmap分成255份。一个二维数组,存放每份爆炸碎片的特性利用Bitmap#getPixel获取坐标x,y处的颜色值。当然,分的碎片越多,颜色值搜集的就越全。

public ExplosionAnimator(View container, Bitmap bitmap, Rect bound) {
    mPaint = new Paint();
    mBound = new Rect(bound);
    int partLen = 15;//定义装255个碎片的数组
    mParticles = new Particle[partLen * partLen];
    Random random = new Random(System.currentTimeMillis());
    int w = bitmap.getWidth() / (partLen + 2);//宽高分17份
    int h = bitmap.getHeight() / (partLen + 2);
    //0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,分块越多,取的视图颜色值越全面
    for (int i = 0; i < partLen; i++) {
        for (int j = 0; j < partLen; j++) {
            mParticles[(i * partLen) + j] = generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random);//根据分块,截取所有块的右下角的点的Bitmap颜色,
        }
    }
    mContainer = container;
    setFloatValues(0f, END_VALUE);//动画范围值
    setInterpolator(DEFAULT_INTERPOLATOR);//加速差值器
    setDuration(DEFAULT_DURATION);
}

Particle定义了每个碎片的特性,包括颜色,大小,运动轨迹等。
这部分的重点在横纵向遍历Bitmap的像素点区域,实现Bitmap色值采样,取每个区域的右下角色值。


爆炸碎片属性与动画逻辑实现

//产生碎片
private Particle generateParticle(int color, Random random) {
    Particle particle = new Particle();
    particle.color = color;// 颜色设置,每个碎片一种颜色
    particle.radius = V;
    //基础半径计算
    if (random.nextFloat() < 0.2f) {
        particle.baseRadius = V + ((X - V) * random.nextFloat());
    } else {
        particle.baseRadius = W + ((V - W) * random.nextFloat());
    }
    //nextFloat是通过随机数进行爆炸球x和y轴移动轨迹比例的控制
    float nextFloat = random.nextFloat();
    //(0.2f到0.36f)height,20%
    //(0.2f到0.36f)height+(0f到0.72f)height,80%
    particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f);
    particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat());
    //针对nextFloat<0.2时,(-0.9到0.9)height,20%
    //针对0.2<nextFloat<0.8时,(-0.45到0.45)height,60%
    //针对0.8<nextFloat<时,(-0.27到0.27)height,20%
    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;

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

    //计算爆炸球初始圆心坐标,不考虑透明时爆炸球开始出现的圆心点,所有球都集中在一小块区域中
    f = mBound.centerX()+ (Y * (random.nextFloat() - 0.5f));//XY的中心点+随机值,不要都挤成一坨
    particle.baseCx = f;
    particle.cx = f;
    f = mBound.centerY() + (Y * (random.nextFloat() - 0.5f));
    particle.baseCy = f;
    particle.cy = f;

    //计算life,overflow
    particle.life = END_VALUE / 10 * random.nextFloat();//(0到0.14)
    particle.overflow = 0.4f * random.nextFloat();//(0到0.4f)
    particle.alpha = 1f;
    return particle;
}

每个碎片产生一个Particle
属性如下

color,颜色
radius,圆形碎片半径,动画中改变。
baseRadius,基础半径,根据随机数概率,20%的碎片一个随机数计算方法,80%的是另一个计算方法,动画时静态。
mBound,上面计算的最终绿色区域
top,20%波动范围在(0.2f到0.36f)的区域高度height,80%的波动范围在(0.2f到0.36f)height+(0f到0.72f)height。
bottom,20%的碎片在(-0.9到0.9)height,60%的在(-0.45到0.45)height,还有20%的在(-0.27到0.27)height。它代表碎片动画时在x轴的运动圆心位置,概率+随机数。可以看出,在爆炸向外扩散时,60%的在中间位置分布。
neg、mag和top,一起用于计算动画时碎片在y轴的运动圆心位置。
cy,cx,动画过程中碎片的圆心位置,动态。
alpha,碎片透明度。
baseCx,baseCy,碎片基础圆心位置。静态,初始位置圆心。
overflow,life,根据动画进度,控制动画运行中开始阶段和结束阶段的状态变化。

动画运行时,重绘爆炸图层ExplosionField。触发动画draw方法。

public boolean draw(Canvas canvas) {
    if (!isStarted()) {
        return false;
    }
    //Log.d("factor_anim", "frac:" + frac + "," + particle.cx + "," + particle.cy + "," + particle.radius);
    //为每一个碎片画一个圆
    for (Particle particle : mParticles) {
        float frac = (float) getAnimatedValue();
        particle.advance(frac);
        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;
}

Particle#advance根据当前进度,设置当前碎片属性,位置,半径,透明度。设置完成后,drawCircle绘制圆形碎片
mContainer是爆炸图层,只要动画未结束,一连串的绘制将展现爆炸效果。

//根据动画运行比例,修改内容包括,透明度,圆心位置,圆心半径
public void advance(float factor) {
    float f = 0f;
    float normalization = factor / END_VALUE;
    //运行比例在(0到0.14)和(0.6到1)时,设置透明
    if (normalization < life || normalization > 1f - overflow) {//life(0到0.14)与overflow(0到0.4f) ,
        alpha = 0f;//life是运行比例的十分之一x随机数,overflow是0.4x随机数,
        return;
    }
    //这个阶段不设置透明,取出首尾,再计算比例
    normalization = (normalization - life) / (1f - life - overflow);
    float f2 = normalization * END_VALUE;//(0-1.4)
    //在重计算的比例大于0.7时,后面的0.3比例开始让他逐渐变透明。使透明度有一个过度的阶段。
    if (normalization >= 0.7f) {
        f = (normalization - 0.7f) / 0.3f;
    }
    alpha = 1f - f;
    f = bottom * f2;//bottom不变,运动到底部位置x(0-1.4),动画结束时,x轴移动最大位移将达1.4倍

    //基础baseXx不变,圆心点x坐标,
    //增加的权重值范围是(0-底部位置的1.4倍)
    cx = baseCx + f;
    cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;
    radius = V + (baseRadius - V) * f2;
}

1,frac是运行比例,根据运行比例,normalization(0-1),根据life和overflow控制运行比例, 每个爆炸球的life和overflow不同,life是随机数0到0.14,overflow是0到0.4,因此,在动画运行在(0到0.14)和(0.6到1),所有的球陆续出现与消失。达到的效果是在一个时间段完成,但不同时。
2,在中间的时间段,球体可见,去除首尾,重置运行比例,目的是从可见的时机开始按照比例计算位置和半径。
3,后期30%的运行比例,透明度逐渐改变,避免瞬间消失的感觉效果。
4,圆心位置x坐标计算,每个碎片bottom是个定值,前面介绍过,所有碎片分布情况不同,有概率。f2是运行比例计算的权值(0到1.4),因此,右侧的碎片最远到达0.9heightx1.4的位置。
5,所有碎片初始位置原点在带爆炸视图的中心区域内。
6,cy是y轴方向的位置,该算法与top,bottom,neg,mag和运行比例相关,比较复杂。最终效果是碎片们先从初始位置圆点在y轴上向上移动,返回,向下移动,超过初始圆点,继而消失。
7,动画中,根据运行比例和基础半径,计算radius。


任重而道远

相关文章

网友评论

      本文标题:Android开源爆炸效果分析

      本文链接:https://www.haomeiwen.com/subject/rvjyqqtx.html