欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Android利用绘制缓冲实现代码雨效果,

来源: 开发者 投稿于  被查看 12557 次 评论:19

Android利用绘制缓冲实现代码雨效果,


目录
  • 前言
  • 效果预览
  • 实现
    • 绘制范围确定
    • 文案
    • 绘制实现
    • 让高度递增
    • 文本的绘制
    • SurfaceView使用
  • 性能优化
    • 总结

      前言

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

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

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

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

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

      效果预览

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

      实现

      下面是具体实现,在这个过程我们会使用到Bitmap作为可视化缓冲,而我们知道,Surface自带双缓冲,但是为什么不用呢?

      原因是Surface的双缓冲会导致雨滴闪烁,因为front和back会交替绘制,导致绘制效果不连续,因此,只能避免这种问题了。

      绘制范围确定

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

      首先,我们要处理的一个问题是,需要下“多少行雨”,在这里我们可以测量字体,让屏幕宽度除以字体宽度,但是一般情况下字体宽度可以能不一致,一个比较合理的方案是,获取Paint的textSize,当然,也可以遍历字符,获取最大的宽度或者高度。

      // 雨滴行数
      int columCount = (int) (getContentWidth() / mPaint.getTextSize()); 
      

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

      drops = new int[columCount];
      

      文案

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

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

      绘制实现

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

      将上一次的效果变暗

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

      让高度递增

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

       if (drops[i] * textSize > height && Math.random() > 0.975) {
            drops[i] = 0;  //重置高度的倍数
       }
        drops[i]++;
      

      文本的绘制

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

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

      下面是核心代码实现

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

      SurfaceView使用

      本篇我们使用了SurfaceView,SurfaceView性能好,但是兼容性差,不适合可滑动和需要调整大小的界面,当然,我们最需要注意的一点是,使用SurfaceView时,是没有办法控制Surface的销毁的,因此,在回调函数中,必须要加锁。

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

      性能优化

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

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

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

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

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

      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上绘制缓冲的烟花效果,本篇,我们通过代码雨加深对绘制缓冲的理解,其次,也提供了优化方法,希望本篇内容对你有所帮助。

      下面是源码:

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

      以上就是Android利用绘制缓冲实现代码雨效果的详细内容,更多关于Android代码雨的资料请关注3672js教程其它相关文章!

      您可能感兴趣的文章:
      • Android自定义View实现数字雨效果的全过程
      • android仿微信表情雨下落效果的实现方法
      • Android自定义View实现黑客帝国数字雨效果
      • Android实现红包雨动画效果
      • Android实现字母雨的效果

      用户评论