美文网首页程序员Android技术知识Android开发经验谈
Android 截屏、图片模糊及bitmap相关

Android 截屏、图片模糊及bitmap相关

作者: 丘卡皮 | 来源:发表于2022-07-18 16:03 被阅读0次

    作者:大师傅姑爷
    转载地址:https://juejin.cn/post/7116432182448488479

    简介

    任务:window弹窗的背景模糊

    实现原理:在window弹窗内容显示出来之前先截取手机屏幕,然后模糊,等弹窗出来之后根据弹窗大小自适应截图,设置为背景,从而实现弹窗背景模糊的效果

    难点:

    1. 由于我们的应用没有activity常驻,而且是系统应用,所以和普通应用的截屏不同;
    2. 使用反射的方法调用系统api进行截屏时,获取屏幕高度有个坑,需要特别注意;
    3. 直接使用原生api进行模糊,radius设置为最大25并不能满足业务要求,所以还另外需要对图片进行处理。

    截屏

    截屏的方法有很多种,这里介绍常用的几种:

    1. adb命令截屏

      adb命令截屏需要root权限,而且由于涉及io,所以比较耗时

        private Bitmap getScreenByAdb(String localImgPath) {
      //       adb命令获取屏幕截图,与实际屏幕尺寸相同,但是涉及IO所以耗时
             Process process = null;
             try {
                 process = Runtime.getRuntime().exec("screencap " +  localImgPath);
                 process.waitFor();//比较耗时,尤其屏幕色彩比较复杂时,耗时甚至会到3s
            } catch (Exception e) {
                 e.printStackTrace();
            } finally {
                 if (process != null) {
                     process.destroy();
                }
            }
             FileInputStream fis = null;
             try {
                 fis = new FileInputStream(localImgPath);
            } catch (FileNotFoundException e) {
                 e.printStackTrace();
            }
             Bitmap originalBgBitmap = BitmapFactory.decodeStream(fis);
             return originalBgBitmap;
        }
      ```
    
    2.  反射调用系统api截屏
    
      使用该方法截屏时需要是系统应用,我最后也是采用的这种方式。
    
      ```
         private Bitmap getScreenBySysMethod() {
             DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
             Bitmap originalBgBitmap = null;
             try {
                 Class<?> mClassType = Class.forName("android.view.SurfaceControl");
                 Method nativeScreenshotMethod = mClassType.getDeclaredMethod("screenshot", Rect.class, int.class, int.class, int.class);
                 nativeScreenshotMethod.setAccessible(true);
                 Bitmap sBitmap = (Bitmap) nativeScreenshotMethod.invoke(mClassType, new Rect(), dm.widthPixels,
                         dm.heightPixels, Surface.ROTATION_0);
                 originalBgBitmap = sBitmap.copy(Bitmap.Config.ARGB_8888, true);
            } catch (NoSuchMethodException e) {
                 e.printStackTrace();
            } catch (ClassNotFoundException e) {
                 e.printStackTrace();
            } catch (InvocationTargetException e) {
                 e.printStackTrace();
            } catch (IllegalAccessException e) {
                 e.printStackTrace();
            }
      
             return originalBgBitmap;
        }
    
    

    在使用这个方法截屏时有个坑,当时折腾了我挺长时间,就是在部分全面屏手机上直接使用dm.heightPixels获取的屏幕高度不包含状态栏,看dm.heightPixels的源码,注释写的是The absolute height of the available display size in pixels,所以不包含状态栏也对。这时通过另外一种方式可以获取屏幕高度:

          /**
            * 在activity中获取屏幕的真实高度,由于在部分全面屏手机上,
            * 直接使用DisplayMetrics heightPixels获取屏幕高度没有包含状态栏或者虚拟按键
            * 所以会比实际的小几十个像素
            * @param context
            * @return
            */
           public static int getScreenHeight(Activity context) {
               DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
               context.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
               return displayMetrics.heightPixels;
          }
    
    1. 普通应用截屏

      这个方法一般用于activity或者fragment中调用,因为需要调用getWindow().getDecorView()获取根布局。所以,如果在需要截屏的时候,如果没有activity活动,是无法使用这个方法的。

           private Bitmap getScreenshotView() {
               View view = getWindow().getDecorView();
               view.setDrawingCacheEnabled(true); // 设置缓存,可用于实时截图
               Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
               Canvas canvas = new Canvas(bitmap);
               view.draw(canvas);
               view.setDrawingCacheEnabled(false); // 清空缓存,可用于实时截图
               return bitmap;
          }
    
    同时,由于这个方法在获取截屏时是通过view进行的,所以也可以只对某个view进行局部截图,只要将方法中的view对象换成需要截图的view就可以了。
    

    Bitmap模糊

    GitHub上有现成的模糊控件,可以满足很多场景,比如github.com/mmin18/Real…等。可惜不满足我的需求,我这边只需要对拿到的bitmap进行模糊,直接使用Android原生的就可以,代码如下:

    /**
    * 
    * @param context 上下文
    * @param src 原图
    * @param radius 模糊半径,有效范围0-25
    * @return
    */
    private Bitmap blur(Context context, Bitmap src, float radius) {
      RenderScript rs = RenderScript.create(context);
           final Allocation input = Allocation.createFromBitmap(rs, src);
           final Allocation output = Allocation.createTyped(rs, input.getType());
           final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
           script.setRadius(radius);
           script.setInput(input);
           script.forEach(output);
           output.copyTo(src);
           return src;
    }
    

    如果这个方法模糊度不够的话,还可以这样来做:先将bitmap缩小,再进行模糊,然后再放大,这样得到的模糊度一般是可以满足需求的。

    缩小bitmap

    BitmapTransformUtil.scaleBitmap(originalBgBitmap, 0.2f, 0.2f, true);
    

    放大bitmap

    BitmapTransformUtil.scaleBitmap(blurredBgBitmap, 5.0f, 5.0f, true);
    

    其中scaleBitmap方法如下所示:

       /**
        * 根据指定的宽度比例值和高度比例值进行缩放
        *
        * @param srcBitmap
        * @param scaleWidth
        * @param scaleHeight
        * @param recycleSrc 是否回收Bitmap
        * @return
        */
       public static Bitmap scaleBitmap(Bitmap srcBitmap, float scaleWidth, float scaleHeight, boolean recycleSrc) {
           int width = srcBitmap.getWidth();
           int height = srcBitmap.getHeight();
           Matrix matrix = new Matrix();
           matrix.postScale(scaleWidth, scaleHeight);
           Bitmap bitmap = Bitmap.createBitmap(srcBitmap, 0, 0, width, height, matrix, true);
           if (bitmap != null) {
               /**回收*/
               if (recycleSrc && srcBitmap != null && !srcBitmap.equals(bitmap) && !srcBitmap.isRecycled()) {
                   GlideBitmapPool.putBitmap(srcBitmap);
              }
               return bitmap;
          } else {
               return srcBitmap;
          }
      }
    

    Bitmap截取

    拿到整个手机屏幕的截图后,需要借去部分作为背景,截取bitmap方法如下:

       /**
        * 裁剪一定高度保留上半部分
        *
        * @param srcBitmap 原图
        * @param x         起始坐标x
        * @param y         起始坐标y
        * @param width     目标宽度
        * @param height     目标高度
        * @param recycleSrc 是否回收原图
        * @return
        */
       public static Bitmap cropBitmapTop(Bitmap srcBitmap, int x, int y, int width, int height, boolean recycleSrc) {
    
           /**裁剪关键步骤*/
           Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, x, y, width, height);
    
           /**回收之前的Bitmap*/
           if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
               GlideBitmapPool.putBitmap(srcBitmap);
          }
           return cropBitmap;
      }
    

    Bitmap圆角设置

       /**
        * 设置圆角
        *
        * @param srcBitmap
        * @param recycleSrc 是否回收原图
        * @return
        */
       public static Bitmap getRoundedCornerBitmap(Bitmap srcBitmap, float radius, boolean recycleSrc) {
           Bitmap output = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
           Paint paint = new Paint();
           Rect rect = new Rect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight());
           RectF rectF = new RectF(rect);
           paint.setAntiAlias(true);
           Canvas canvas = new Canvas(output);
           canvas.save();
           canvas.drawARGB(0, 0, 0, 0);
           canvas.drawRoundRect(rectF, radius, radius, paint);
           paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
           canvas.drawBitmap(srcBitmap, rect, rect, paint);
           canvas.restore();
           /**回收之前的Bitmap*/
           if (recycleSrc && srcBitmap != null && !srcBitmap.equals(output) && !srcBitmap.isRecycled()) {
               GlideBitmapPool.putBitmap(srcBitmap);
          }
           return output;
      }
    

    Bitmap保存

        /**
        * 保存bitmap到本地
        * @param bitmap 
        * @param filePath 
        */
       private void saveBitmap(Bitmap bitmap, String filePath){
           FileOutputStream fos = null;
           try {
               fos = new FileOutputStream(filePath);
               bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
               fos.flush();
          } catch (IOException e) {
               e.printStackTrace();
          } finally {
               try {
                   if (fos != null) {
                       fos.close();
                  }
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }
    

    推荐一个bitmap复用池:github.com/amitshekhar…,毕竟bitmap是内存消耗大户,合理使用bitmap可以避免内存抖动和OOM。具体使用方法直接点进去看吧。

    最后贴一个实现效果图吧

    相关文章

      网友评论

        本文标题:Android 截屏、图片模糊及bitmap相关

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