欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android基于繪制緩沖實現(xiàn)煙花效果

 更新時間:2024年03月18日 09:01:18   作者:時光少年  
這篇文章主要介紹了Android基于繪制緩沖實現(xiàn)煙花效果,文中通過代碼示例和圖文結合介紹的非常詳細,對大家的學習或工作有一定的幫助,感興趣的同學可以自己動手嘗試一下

前言

三月以前,我也寫過《Android 煙花效果》,這篇我相當于做了個基礎框架,在此基礎上擴展和填充,就能擴展出很多效果。不過,當時,我在這篇文章中著重強調了一件事

重點:構建閉合空間

之所以強調這件事的原因是,只有閉合空間的圖形才能填充顏色、圖片紋理。我們知道,Canvas 繪制方法僅僅只有圓、弧、矩形、圓角矩形是可以閉合的,除此之外就是Path了。

想象一下,如果讓你畫一個三角形并填充上顏色,你可能的方法只有通過裁剪Path或者使用Path繪制才行,而Path也有性能問題。

另外,閉合空間的填充也是件不容易的事。

所以,那篇文章中的煙花效果,本質上還不夠完美,因為一些特殊的填充效果還是很難實現(xiàn)。

新方案

目前我覺得可行的方案有兩種

基于數(shù)學和Paint線寬漸變

如:貝塞爾曲線函數(shù) + strokeWidth漸漸增大 + Color 變化

這種方式是利用貝塞爾曲線計算出路徑(不用Path,根據(jù)數(shù)學公式描繪),然后再規(guī)定的時間內讓Paint的strokeWidth隨著貝塞爾曲線 * time的偏移而增大,就能繪制出效果不錯的的煙花條。

基于繪制緩沖

首先,要知道什么是緩沖,緩沖其實就是通常意義上存儲數(shù)據(jù)的對象,比如byte數(shù)組、ByteBuffer等,但如果再聚焦Android 平臺,我們還有FBO、VBO等。當然,最容易被忽略的是Bitmap,Bitmap 其實也是FBO的一種,不過這里我稱之為“可視化緩沖”。

如果追蹤的具體的對象上,除了Bitmap之外,Layer也是緩沖。

為什么使用緩沖可以優(yōu)化煙花效果呢?

我們先了解下緩沖的特性:

  • 占用空間較大,狹義上來說,這種數(shù)據(jù)不僅僅占用空間大,而且(虛擬)內存需要連續(xù)
  • 空間可復用性強,如享元模式的ByteBuffer、alpha離屏渲染buffer、inBitmap等
  • 會產生臟數(shù)據(jù),比如上一次buffer中的數(shù)據(jù),如果沒有清理的話依然會保存
  • 數(shù)據(jù)可復用性強,臟數(shù)據(jù)并不一定“臟”,有時還能復用

我們最終利用的還是空間可復用性和數(shù)據(jù)可復用性,如果我們以每次都在上次的數(shù)據(jù)中繪制,那么,意味著可以繪制出更多效果,間接解決了閉合空間填充問題。

那么,本篇我們選哪種呢?

最終方案

本篇,我們就選擇基于緩沖的方案了,因為總的來說,第一種方式可能需要很多次的繪制,相當考驗CPU。而使用繪制緩沖的的話,我們還可以復用上次的數(shù)據(jù),這就相當于將上一次的繪制畫面保留,然后再一次繪制時,在之前的基礎上進一步完善,這種顯然是利用“空間換取時間”的做法。

詳細設計

本篇使用了繪制緩沖,原則上使用Bitmap是可以的,但是在使用的過程中發(fā)現(xiàn),Bitmap在xformode繪制時性能還是很差,顯然提升流暢度是必要原則。那么,你可能想到利用線程異步繪制,是的,我也打算這么做,但是想到使用線程渲染,那為什么不使用TextureView、SurfaceView或者GLSurfaceView呢?于是,我就沒有再使用Bitmap的想法了。

但是,基于做以往的經(jīng)驗,我選了個兼容性最好性能最差的TextureView,其實我這里本打算選GLSurfaceView的,因為其性能和兼容性都是居中水平,不過涉及到頂點、紋理的一套東西,打算后續(xù)在音視頻專欄寫這類文章,因此本篇就選TexureView了。

簡單說下SurfaceView的問題,性能最好,但其不適合在滑動的頁面調用,因為有些設備會出現(xiàn)畫面漂移和縮放的問題,另外不支持clipchildren等,理論上也是適合本篇的,但是如果app回到后臺,其Surface會自動銷毀,因此,控制線程的邏輯就會有些復雜。

在這里我們看下TextureView源碼,其創(chuàng)建的SurfaceTexture并不是單緩沖模式,但是又有設置緩沖bufferSize大小的操作,此外TextLayer負責提供緩沖,因此,這里至少是雙緩沖。

    mLayer = mAttachInfo.mThreadedRenderer.createTextureLayer();
    boolean createNewSurface = (mSurface == null);
    if (createNewSurface) {
        // Create a new SurfaceTexture for the layer.
        mSurface = new SurfaceTexture(false); //非單緩沖
        nCreateNativeWindow(mSurface);
    }
    mLayer.setSurfaceTexture(mSurface);
    mSurface.setDefaultBufferSize(getWidth(), getHeight());
    mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);

    if (mListener != null && createNewSurface) {
        mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
    }
    mLayer.setLayerPaint(mLayerPaint);
}

下面是我們的詳細流程。

實現(xiàn)煙花邏輯

下面是我們本篇的實現(xiàn)流程。

定義FireExploreView

我們本篇基于TextureView實現(xiàn)繪制邏輯,而TextureView必須要開啟硬件加速,其次我們要實現(xiàn)TextureView.SurfaceTextureListener,用于監(jiān)聽SurfaceTexture的創(chuàng)建和銷毀。理論上,TextureView的SurfaceTexture可以復用的,其次,如果onSurfaceTextureDestroyed返回false,那么SurfaceTexture的銷毀是由你自己控制的,TextureView不會主動銷毀。

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
 
    return false;
}

另外,我們要知道,默認情況下TextureView使用的是TextureLayer,繪制完成之后,需要在RenderThread上使用gl去合成,這也是性能較差的主要原因。尤其是低配設備,使用TextureView也做不到性能優(yōu)化,最終還是得使用SurfaceView或者GLTextureView或者GLSurfaceView,當然我比較推薦GL系列,主要是離屏渲染可以避免MediaCodec切換Surface引發(fā)黑屏和卡住的問題。

當然,這里我們肯定也要使用到線程和Surface了,相關代碼如下

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
    drawThread = new Thread(this);
    this.surfaceTexture = surfaceTexture;
    this.surface = new Surface(this.surfaceTexture);
    this.isRunning = true;
    this.drawThread.start();
}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    isRunning = false;
    if (drawThread != null) {
        try {
            drawThread.interrupt();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
    drawThread = null;
    //不讓TextureView 銷毀SurfaceTexture,這里返回false
    return false; 
}

定義粒子

無論任何時候,不要把粒子不當對象,一些開發(fā)者對粒子對象嗤之以鼻,這顯然是不對的,不受管理的粒子憑什么聽你的指揮。

當然,任何粒子的運動需要符合運動學方程,而二維平面的運動是可以拆分為X軸和Y軸單方向的運動的。

static final float gravity = 0.0f;
static final float fraction = 0.88f;
static final float speed = 50f; //最大速度

    
static class Particle {
    private float opacity;  //透明度
    private float dy; // y 軸速度
    private float dx; // x 軸速度
    private int color; //此顏色
    private float radius; //半徑
    private float y; // y坐標
    private float x; // x坐標

    Particle(float x, float y, float r, int color, float speedX, float speedY) {
        this.x = x;
        this.y = y;
        this.radius = r;
        this.color = color;
        this.dx = speedX;
        this.dy = speedY;
        this.opacity = 1f;
    }

    void draw(Canvas canvas, Paint paint) {
        int save = canvas.save();
        paint.setAlpha((int) (this.opacity * 255));
        paint.setColor(this.color);
        canvas.drawCircle(this.x, this.y, this.radius, paint);
        canvas.restoreToCount(save);
    }

    void update() {
        this.dy += gravity; 
        //加上重力因子,那么就會出現(xiàn)粒子重力現(xiàn)象,這里我們不使用時間了,這樣簡單點

        this.dx *= fraction;  // fraction 是小于1的,用于降低速度
        this.dy *= fraction;  // fraction 是小于1的,用于降低速度

        this.x += this.dx;
        this.y += this.dy;

        this.opacity -= 0.03; //透明度遞減
    }
}

上面是粒子以及更新方法、繪制邏輯。

管理粒子

我們使用List管理粒子

static final int maxParticleCount = 300;
List<Particle> particles = new ArrayList<>(maxParticleCount);

初始化粒子

粒子的初始化是非常重要的,初始化位置的正確與否會影響粒子的整體效果,顯然,這里我們需要注意。

float angleIncrement = (float) ((Math.PI * 2) / maxParticleCount); //平分 360度
float[] hsl = new float[3];

for (int i = 0; i < maxParticleCount; i++) {
    hsl[0] = (float) (Math.random() * 360);
    hsl[1] = 0.5f;
    hsl[2] = 0.5f;
    int hslToColor = HSLToColor(hsl);

    Particle p = new Particle(x, y,
            2.5f,
            hslToColor,
            (float) (Math.cos(angleIncrement * i) * Math.random() * speed),
            (float) (Math.sin(angleIncrement * i) * Math.random() * speed)
    );
    particles.add(p);
}

不過,在這里我們還需要注意的是,這里我們使用HLS,這是一種色彩空間,和RGB不一樣的是,他有Hue(色調)、飽和度、亮度為基準,因此,有利于亮色的表示,因此適合獲取強調亮度的色彩。

與rgb的轉換邏輯如下

public static int HSLToColor(@NonNull float[] hsl) {
    final float h = hsl[0];
    final float s = hsl[1];
    final float l = hsl[2];

    final float c = (1f - Math.abs(2 * l - 1f)) * s;
    final float m = l - 0.5f * c;
    final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));

    final int hueSegment = (int) h / 60;

    int r = 0, g = 0, b = 0;

    switch (hueSegment) {
        case 0:
            r = Math.round(255 * (c + m));
            g = Math.round(255 * (x + m));
            b = Math.round(255 * m);
            break;
        case 1:
            r = Math.round(255 * (x + m));
            g = Math.round(255 * (c + m));
            b = Math.round(255 * m);
            break;
        case 2:
            r = Math.round(255 * m);
            g = Math.round(255 * (c + m));
            b = Math.round(255 * (x + m));
            break;
        case 3:
            r = Math.round(255 * m);
            g = Math.round(255 * (x + m));
            b = Math.round(255 * (c + m));
            break;
        case 4:
            r = Math.round(255 * (x + m));
            g = Math.round(255 * m);
            b = Math.round(255 * (c + m));
            break;
        case 5:
        case 6:
            r = Math.round(255 * (c + m));
            g = Math.round(255 * m);
            b = Math.round(255 * (x + m));
            break;
    }

    r = constrain(r, 0, 255);
    g = constrain(g, 0, 255);
    b = constrain(b, 0, 255);

    return Color.rgb(r, g, b);
}
private static int constrain(int amount, int low, int high) {
    return amount < low ? low : Math.min(amount, high);
}

粒子繪制

繪制當然簡單了,方法實現(xiàn)不是很復雜,調用如下邏輯即可,當然,opacity<=0 的粒子我們并沒有移除,原因是因為remove 時, 可能引發(fā)ArrayList內存重整,這個是相當消耗性能的,因此,還不如遍歷效率高。

protected void drawParticles(Canvas canvas) {
    canvas.drawColor(0x10000000); //為了讓煙花減弱效果,每次加深繪制
    for (int i = 0; i < particles.size(); i++) {
        Particle particle = particles.get(i);
        if (particle.opacity > 0) {
            particle.draw(canvas, mPaint);
            particle.update();
        }
    }
}

緩沖復用

那么,以上就是完整的繪制邏輯了,至于Surface調用邏輯呢,其實也很簡單。

不過這里要注意的是,只有接受到command=true的時候,我們才清理畫布,不然,我們要保留緩沖區(qū)中的數(shù)據(jù)。我們知道,一般View在onDraw的時候,RenderNode給你的Canvas都是清理過的,而這里,我們每次通過lockCanvas拿到的Canvas是帶有上次緩沖數(shù)據(jù)的。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    canvas = surface.lockHardwareCanvas();
} else {
    canvas = surface.lockCanvas(null);
}

if(isCommand){
 
    canvas.drawColor(0x99000000, PorterDuff.Mode.CLEAR); //清理畫布
    explode(getWidth() / 2f, getHeight() / 2f);  //粒子初始化
    isCommand = false;
}
//繪制粒子
drawParticles(canvas);

surface.unlockCanvasAndPost(canvas);

顯然,我們能得到兩條經(jīng)驗:

  • lockCanvas獲取到的Canvas是帶有上次繪制數(shù)據(jù)的
  • 利用緩沖繪制不僅強調結果,而且還強調過程,一般的Canvas繪制僅僅強調結果

Blend效果增強

實際上面的效果還有點差,就是尖端亮度太低,為此,我們可以使用Blend進行增強,我們設置BlendMode為PLUS,另外上面我們的重力是0,現(xiàn)在我們調整一下gravity=0.25f。

PaintCompat.setBlendMode(mPaint, BlendModeCompat.PLUS);

效果

多線程繪制

總的來說,TextureView可以在一些情況下顯著提升性能,當然,前提是你的主線程流暢。

這里的邏輯就是TextureView的用法了,我們就不繼續(xù)深入了,本篇末尾提供源碼。

新問題

評論區(qū)有同學反饋,在真機上很卡,我試了一下,發(fā)現(xiàn)不是卡,而是TextureView 不是單緩沖,兩次緩沖在沒有CLEAR時會有交替閃爍問題。

因此,為了優(yōu)化閃爍問題,我把可視化緩沖Bitmap重新加進來,使用之后在上是沒有問題的,但是由于Android 6.0 之前的系統(tǒng)無法使用lockHardwareCanvas,卡頓是比較明顯的。

為啥模擬器表現(xiàn)比較好,可能刷新率比較低。

性能優(yōu)化

由于使用Bitmap作為緩沖,性能有所降低,我們這里進行如下優(yōu)化

  • 減少繪制區(qū)域大小
  • 移除Surface 清理 canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
  • Android 6.0+版本使用硬件Canvas

縮小繪制區(qū)域收益明顯,后續(xù)考慮先縮小后繪制,再利用Matrix放大。

總結

以上是本篇的內容,也是我們要掌握的技巧,很多時候,我們對Canvas的繪制,過于強調結果,結果設計了很多復雜的算法,其實,基于過程的繪制顯然更加簡單和優(yōu)化。

到這里本篇就結束了,希望本篇對你有所幫助。

源碼

public class FireExploreView extends TextureView implements TextureView.SurfaceTextureListener, Runnable {
    private TextPaint mPaint;
    private SurfaceTexture surfaceTexture;
    private Surface surface;
    private BitmapCanvas mBitmapCanvas;
    private boolean updateOnSizeChanged = false;
    private volatile boolean isRunning = false;
    private final Object lockSurface = new Object();

    {
        initPaint();
    }
    public FireExploreView(Context context) {
        this(context, null);
    }
    public FireExploreView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setSurfaceTextureListener(this);
    }

    private void initPaint() {
        //否則提供給外部紋理繪制
        mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStyle(Paint.Style.FILL);
        PaintCompat.setBlendMode(mPaint, BlendModeCompat.PLUS);

    }

    static final float gravity = 0.21f;
    static final float fraction = 0.88f;
    static final int maxParticleCount = 300;
    List<Particle> particles = new ArrayList<>(maxParticleCount);
    float[] hsl = new float[3];

    volatile boolean isCommand = false;
    static final float speed = 60f;
    Thread drawThread = null;

    public void startExplore() {
        isCommand = true;
    }

    //初始化粒子
    void explode(float x, float y) {
        float angleIncrement = (float) ((Math.PI * 2) / maxParticleCount);
        for (int i = 0; i < maxParticleCount; i++) {
            hsl[0] = (float) (Math.random() * 360);
            hsl[1] = 0.5f;
            hsl[2] = 0.5f;
            int hslToColor = HSLToColor(hsl);

            Particle p = null;
            if (particles.size() > i) {
                p = particles.get(i);
            }

            if (p == null) {
                p = new Particle();
                particles.add(p);
            }
            p.init(x, y,
                    4f,
                    hslToColor,
                    (float) (Math.cos(angleIncrement * i) * Math.random() * speed),
                    (float) (Math.sin(angleIncrement * i) * Math.random() * speed)
            );
        }
    }


    protected void drawParticles(Canvas canvas) {
        canvas.drawColor(0x10000000);
        for (int i = 0; i < particles.size(); i++) {
            Particle particle = particles.get(i);
            if (particle.opacity > 0) {
                particle.draw(canvas, mPaint);
                particle.update();
            }
        }
    }


    static class Particle {
        private float opacity;
        private float dy;
        private float dx;
        private int color;
        private float radius;
        private float y;
        private float x;

        public void init(float x, float y, float r, int color, float speedX, float speedY) {
            this.x = x;
            this.y = y;
            this.radius = r;
            this.color = color;
            this.dx = speedX;
            this.dy = speedY;
            this.opacity = 1f;
        }
        void draw(Canvas canvas, Paint paint) {
            int save = canvas.save();
            paint.setColor(argb((int) (this.opacity * 255),Color.red(this.color),Color.green(this.color),Color.blue(this.color)));
            canvas.drawCircle(this.x, this.y, this.radius, paint);
            canvas.restoreToCount(save);
        }

        void update() {
            this.dy += gravity;

            this.dx *= fraction;
            this.dy *= fraction;

            this.x += this.dx;
            this.y += this.dy;

            this.opacity -= 0.02;
        }


    }


    Matrix matrix = new Matrix();
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                try {
                    this.wait(16);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (!isRunning || Thread.currentThread().isInterrupted()) {
                synchronized (lockSurface) {
                    if (surface != null && surface.isValid()) {
                        surface.release();
                    }
                    surface = null;
                }
                break;
            }

            Canvas canvas = null;
            synchronized (lockSurface) {
                if(mBitmapCanvas == null || updateOnSizeChanged) {
                    updateOnSizeChanged = false;
                    mBitmapCanvas = createBitmapCanvas(getWidth(),getHeight());
                }

                if(isCommand){
                    mBitmapCanvas.bitmap.eraseColor(0x00000000);
                    explode(mBitmapCanvas.getWidth() / 2f, mBitmapCanvas.getHeight() / 2f);
                    isCommand = false;
                }
                //這里其實目前沒有加鎖的必要,考慮到如果有其他SurfaceTexture相關操作會加鎖,這里先加鎖吧
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
                     canvas = surface.lockHardwareCanvas();
                }else {
                    canvas = surface.lockCanvas(null);
                }
                Bitmap bitmap = mBitmapCanvas.getBitmap();
                drawParticles(mBitmapCanvas);
                matrix.reset();
                matrix.setTranslate((getWidth() - bitmap.getWidth()) / 2f, (getHeight() - bitmap.getHeight()) / 2f);
                canvas.drawBitmap(mBitmapCanvas.getBitmap(), matrix, null);
                surface.unlockCanvasAndPost(canvas);
            }
        }

    }

    private BitmapCanvas createBitmapCanvas(int width,int height){
        if(mBitmapCanvas != null){
            mBitmapCanvas.recycle();
        }
        int size = Math.max(Math.min(width,height),1);
        return new BitmapCanvas(Bitmap.createBitmap(size,size, Bitmap.Config.ARGB_8888));
    }

    static class BitmapCanvas extends Canvas{
        Bitmap bitmap;

        public BitmapCanvas(Bitmap bitmap) {
            super(bitmap);
            this.bitmap = bitmap;
        }

        public Bitmap getBitmap() {
            return bitmap;
        }

        public void recycle() {
            if(bitmap == null || bitmap.isRecycled()){
                return;
            }
            bitmap.recycle();
        }
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
        this.drawThread = new Thread(this);
        this.surfaceTexture = surfaceTexture;
        this.surface = new Surface(this.surfaceTexture);
        this.isRunning = true;
        this.drawThread.start();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        updateOnSizeChanged = true;
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        isRunning = false;
        if (drawThread != null) {
            try {
                drawThread.interrupt();
            }catch (Throwable e){
                e.printStackTrace();
            }
        }
        drawThread = null;
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }


    @ColorInt
    public static int HSLToColor(@NonNull float[] hsl) {
        final float h = hsl[0];
        final float s = hsl[1];
        final float l = hsl[2];

        final float c = (1f - Math.abs(2 * l - 1f)) * s;
        final float m = l - 0.5f * c;
        final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));

        final int hueSegment = (int) h / 60;

        int r = 0, g = 0, b = 0;

        switch (hueSegment) {
            case 0:
                r = Math.round(255 * (c + m));
                g = Math.round(255 * (x + m));
                b = Math.round(255 * m);
                break;
            case 1:
                r = Math.round(255 * (x + m));
                g = Math.round(255 * (c + m));
                b = Math.round(255 * m);
                break;
            case 2:
                r = Math.round(255 * m);
                g = Math.round(255 * (c + m));
                b = Math.round(255 * (x + m));
                break;
            case 3:
                r = Math.round(255 * m);
                g = Math.round(255 * (x + m));
                b = Math.round(255 * (c + m));
                break;
            case 4:
                r = Math.round(255 * (x + m));
                g = Math.round(255 * m);
                b = Math.round(255 * (c + m));
                break;
            case 5:
            case 6:
                r = Math.round(255 * (c + m));
                g = Math.round(255 * m);
                b = Math.round(255 * (x + m));
                break;
        }

        r = constrain(r, 0, 255);
        g = constrain(g, 0, 255);
        b = constrain(b, 0, 255);

        return Color.rgb(r, g, b);
    }
    private static int constrain(int amount, int low, int high) {
        return amount < low ? low : Math.min(amount, high);
    }

    public static int argb(
            @IntRange(from = 0, to = 255) int alpha,
            @IntRange(from = 0, to = 255) int red,
            @IntRange(from = 0, to = 255) int green,
            @IntRange(from = 0, to = 255) int blue) {
        return (alpha << 24) | (red << 16) | (green << 8) | blue;
    }
    public void release(){
        synchronized (lockSurface) {
            isRunning = false;
            updateOnSizeChanged = false;
            if (surface != null && surface.isValid()) {
                surface.release();
            }
            surface = null;
        }
    }
}

以上就是Android基于繪制緩沖實現(xiàn)煙花效果的詳細內容,更多關于Android煙花效果的資料請關注腳本之家其它相關文章!

相關文章

最新評論