Android 基于绘制缓冲的代码雨效果

前言

看过很多代码雨的前端实现,却很少看到过Android代码雨效果的实现,当然 open gl es的实现是有的。一个主要的原因是,在Android Canvas绘制时,很少有人考虑使用绘制缓冲,我们之前有一篇文章,就实现了基于绘制缓冲的烟花效果,当然,本篇也是利用绘制缓冲来实现这种效果。

为什么需要绘制缓冲呢?原因如下:

绘制缓冲可以保留上次的绘制数据为下次绘制使用。

那,既然使用了绘制缓冲,我们理论上得使用Surface,为什么这么说呢,因为setLayerType开启缓冲会影响到绘制效果,其次绘制也比较耗时,因此为了避免这种情况,使用Surface是必要的优化手段。

当然,相较于上次的《基于绘制缓冲的烟花效果》,这一篇我们的内容其实并不多。

效果预览

我们可以看到,下雨的时候,拖尾部分会慢慢隐藏,主要技巧是多次覆盖半透明黑色,在视觉上反应的是渐渐变暗的alpha动画,上一篇的烟花效果也使用了这种技巧。

实现

下面是具体实现,在这个过程我们会使用到Bitmap作为可视化缓冲,而我们知道,Surface自带双缓冲,但是为什么不用呢?原因是Surface的双缓冲会导致雨滴闪烁,因为front和back会交替绘制,导致绘制效果不连续,因此,只能避免这种问题了。

绘制范围确定

在实现代码雨时,我们肯定要初始化画笔等,但是,一个需要思考的问题是,如何控制雨滴重复利用,其次如何知道绘制多大范围。

首先,我们要处理的一个问题是,需要下"多少行雨",在这里我们可以策略字体,让屏幕宽度除以字体宽度,但是一般情况下字体宽度可以能不一致,一个比较合理的方案是,获取Paint的textSize

java 复制代码
// 雨滴行数
int columCount = (int) (getContentWidth() / mPaint.getTextSize()); 

不过,我们看效果就能发现一个现象,文本绘制之后,位置是不变的,只有"雨滴头"似乎在移动,雨滴头也不会覆盖到原来的位置,因此,我们需要保存每行雨滴头的高度

java 复制代码
drops = new int[columCount];

文案

我们给一段简单的文案,重复绘制即可。

java 复制代码
char[] characters = "张三爱李四,Alice love you".toCharArray();

绘制实现

下面是绘制逻辑的核心代码

将上一次的效果变暗

java 复制代码
canvas.drawColor(argb(0.1f, 0f, 0f, 0f));//加深暗度,使得以前绘制的区域变暗

让高度递增

雨肯定是要从上到下,那么雨滴的高度是需要递增的,另外,移出界面的当然要重置高度到0,不过这里为了保证随机性,我们加点随机变量

java 复制代码
 if (drops[i] * textSize > height && Math.random() > 0.975) {
      drops[i] = 0;  //重制/
 }
  drops[i]++;

文本的绘制

文本的绘制比较随机了,下面绘制,但要保证高度为:当前高度 x textSize,宽度为 i x textSize

java 复制代码
int index = (int) Math.floor(Math.random() * characters.length);
canvas.drawText(characters, index, 1, i * textSize, drops[i] * textSize, paint);

下面是核心代码实现

java 复制代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    canvas = surface.lockHardwareCanvas();
} else {
    canvas = surface.lockCanvas(null);
}
drawChars(mBitmapCanvas, mPaint);

canvas.drawBitmap(mBitmapCanvas.getBitmap(), matrix, null);

surface.unlockCanvasAndPost(canvas);

性能优化

我们虽然使用了lockHardwareCanvas,但是,在大范围绘制还是有些卡顿,绘制缓冲是有性能损耗的,那么怎么才能优化呢?

绘制的优化手段当然是减少重绘,但是最重要的是减少绘制面积。

在这里,我们可以换一种思路,绘制等比例前缩小Bitmap,绘制后在往Surface绘制时放大Bitmap,当然,这种会产生锯齿,不过,解决方法是有的,我们这里使用过滤工具,仅仅相应的优化即可。

java 复制代码
mBitmapPaint =  new TextPaint(Paint.ANTI_ALIAS_FLAG);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setDither(true);

下面我们改造一下,缩小之后,绘制时使用Matrix放大

java 复制代码
Canvas canvas = null;
if (sizeChanged || drops == null) {
    if (mBitmapCanvas != null) {
        mBitmapCanvas.recycle();
    }
    //缩小绘制缓冲面积
    mBitmapCanvas = new BitmapCanvas(Bitmap.createBitmap(getWidth() / 2, getHeight() / 2, Bitmap.Config.RGB_565));
    int columCount = (int) (mBitmapCanvas.getBitmap().getWidth() / mPaint.getTextSize());
    drops = new int[columCount];
    Arrays.fill(drops, 1);
    sizeChanged = false;
}

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

matrix.reset();
//放大
matrix.setScale(2, 2);

canvas.drawBitmap(mBitmapCanvas.getBitmap(), matrix, mBitmapPaint);

surface.unlockCanvasAndPost(canvas);

经过优化之后,流畅度当然会大幅提升。

总结

到这里本篇就结束了,上一篇,我们实现Android上绘制缓冲的烟花效果,本篇,我们通过代码雨加深对绘制缓冲的理解,其次,也提供了优化方法,希望本篇内容对你有所帮助。

下面是源码:

java 复制代码
public class CodeRainView extends SurfaceView implements Runnable, SurfaceHolder.Callback {
    private TextPaint mPaint;
    private TextPaint mBitmapPaint;

    private Surface surface;
    private boolean sizeChanged;
    private BitmapCanvas mBitmapCanvas;

    {
        initPaint();
    }

    public CodeRainView(Context context) {
        this(context, null);
    }

    public CodeRainView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        getHolder().addCallback(this);
    }

    private void initPaint() {
        //否则提供给外部纹理绘制
        mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(20);

        mBitmapPaint =  new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setDither(true);

        PaintCompat.setBlendMode(mPaint, BlendModeCompat.PLUS);
    }


    Thread drawThread = null;


    char[] characters = "张三爱李四,Alice love you".toCharArray();
    int[] drops = null;

    private volatile boolean isRunning = false;
    private final Object lockSurface = new Object();

    Matrix matrix = new Matrix();

    @Override
    public void run() {
        while (true) {
            synchronized (surface) {
                try {
                    Thread.sleep(32);
                } 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;
                if (sizeChanged || drops == null) {
                    if (mBitmapCanvas != null) {
                        mBitmapCanvas.recycle();
                    }
                    mBitmapCanvas = new BitmapCanvas(Bitmap.createBitmap(getWidth() / 2, getHeight() / 2, Bitmap.Config.RGB_565));
                    int columCount = (int) (mBitmapCanvas.getBitmap().getWidth() / mPaint.getTextSize());
                    drops = new int[columCount];
                    Arrays.fill(drops, 1);
                    sizeChanged = false;
                }

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

                matrix.reset();
                matrix.setScale(2, 2);

                canvas.drawBitmap(mBitmapCanvas.getBitmap(), matrix, mBitmapPaint);

                surface.unlockCanvasAndPost(canvas);
            }
        }

    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        this.drawThread = new Thread(this);
        this.surface = holder.getSurface();
        this.isRunning = true;
        this.drawThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Surface drawSurface = surface;
        if (drawSurface == null) {
            return;
        }
        synchronized (drawSurface) {
            isRunning = false;
        }
        if (drawThread != null) {
            try {
                drawThread.interrupt();
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
        drawThread = null;
    }

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

    void drawChars(Canvas canvas, Paint paint) {
        canvas.drawColor(argb(0.1f, 0f, 0f, 0f));
        paint.setColor(0xFF00FF00);
        int height = getHeight();
        float textSize = paint.getTextSize();

        for (int i = 0; i < drops.length; i++) {
            int index = (int) Math.floor(Math.random() * characters.length);
            canvas.drawText(characters, index, 1, i * textSize, drops[i] * textSize, paint);
            if (drops[i] * textSize > height && Math.random() > 0.975) {
                drops[i] = 0;
            }
            drops[i]++;
        }
    }

    public static int argb(float alpha, float red, float green, float blue) {
        return ((int) (alpha * 255.0f + 0.5f) << 24) |
                ((int) (red * 255.0f + 0.5f) << 16) |
                ((int) (green * 255.0f + 0.5f) << 8) |
                (int) (blue * 255.0f + 0.5f);
    }


}
相关推荐
伍哥的传说1 小时前
鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
前端·华为·前端框架·harmonyos·鸿蒙
yugi9878381 小时前
前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
前端
小蜜蜂嗡嗡1 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi001 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
浪裡遊1 小时前
Sass详解:功能特性、常用方法与最佳实践
开发语言·前端·javascript·css·vue.js·rust·sass
旧曲重听12 小时前
最快实现的前端灰度方案
前端·程序人生·状态模式
默默coding的程序猿2 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
夏梦春蝉2 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
归于尽2 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课2 小时前
React useEffect 详解与运用
前端·react.js