美文网首页SurfaceView相关知识
关于Android SurfaceView截屏总结

关于Android SurfaceView截屏总结

作者: 戎码虫 | 来源:发表于2019-07-01 00:05 被阅读0次

    普通View截图

    普通视图
    获取View截图
     /**
         * 获取控件截图(黑色背景)
         *
         * @param view view
         * @return Bitmap
         */
        public static Bitmap getViewBitmapNoBg(View view) {
            view.setDrawingCacheEnabled(true);
            view.buildDrawingCache(true);
            Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
            // clear drawing cache
            view.setDrawingCacheEnabled(false);
            return bitmap;
        }
    
        /**
         * @param view 需要截取图片的view(含有底色)
         * @return Bitmap
         */
        public static Bitmap getViewBitmap(Activity activity, View view) {
            View screenView = activity.getWindow().getDecorView();
            screenView.setDrawingCacheEnabled(true);
            screenView.buildDrawingCache();
    
            //获取屏幕整张图片
            Bitmap bitmap = screenView.getDrawingCache();
    
            if (bitmap != null) {
                //需要截取的长和宽
                int outWidth = view.getWidth();
                int outHeight = view.getHeight();
    
                //获取需要截图部分的在屏幕上的坐标(view的左上角坐标)
                int[] viewLocationArray = new int[2];
                view.getLocationOnScreen(viewLocationArray);
    
                //从屏幕整张图片中截取指定区域
                bitmap = Bitmap.createBitmap(bitmap, viewLocationArray[0], viewLocationArray[1], outWidth, outHeight);
            }
            return bitmap;
        }
    
    获取ViewGroup截图
       /**
         * @param viewGroup viewGroup
         * @return Bitmap
         */
        public static Bitmap getViewGroupBitmapNoBg(ViewGroup viewGroup) {
            // 创建对应大小的bitmap(重点)
            Bitmap bitmap = Bitmap.createBitmap(viewGroup.getWidth(), viewGroup.getHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            viewGroup.draw(canvas);
            return bitmap;
        }
    
        /**
         * @param viewGroup viewGroup
         * @return Bitmap
         */
        public static Bitmap getViewGroupBitmap(Activity activity, ViewGroup viewGroup) {
            View screenView = activity.getWindow().getDecorView();
            screenView.setDrawingCacheEnabled(true);
            screenView.buildDrawingCache();
    
            //获取屏幕整张图片
            Bitmap bitmap = screenView.getDrawingCache();
    
            if (bitmap != null) {
                //需要截取的长和宽
                int outWidth = viewGroup.getWidth();
                int outHeight = viewGroup.getHeight();
    
                //获取需要截图部分的在屏幕上的坐标(view的左上角坐标)
                int[] viewLocationArray = new int[2];
                viewGroup.getLocationOnScreen(viewLocationArray);
    
                //从屏幕整张图片中截取指定区域
                bitmap = Bitmap.createBitmap(bitmap, viewLocationArray[0], viewLocationArray[1], outWidth, outHeight);
            }
            return bitmap;
        }
    
    获取Activity截图
      /**
         * 根据指定的Activity截图(带空白的状态栏)
         *
         * @param activity 要截图的Activity
         * @return Bitmap       
         */
        public static Bitmap shotActivity(Activity activity) {
            View view = activity.getWindow().getDecorView();
            view.setDrawingCacheEnabled(true);
            view.buildDrawingCache();
            Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
            view.setDrawingCacheEnabled(false);
            view.destroyDrawingCache();
            return bitmap;
        }
    
        /**
         * 根据指定的Activity截图(去除状态栏)
         *
         * @param activity 要截图的Activity
         * @return Bitmap       
         */
        public static Bitmap shotActivityNoStatusBar(Activity activity) {
            // 获取windows中最顶层的view
            View view = activity.getWindow().getDecorView();
            view.buildDrawingCache();
            // 获取状态栏高度
            Rect rect = new Rect();
            view.getWindowVisibleDisplayFrame(rect);
            int statusBarHeights = rect.top;
    
            DisplayMetrics displayMetrics = activity.getResources().getDisplayMetrics();
            // 获取屏幕长和高
            int widths = displayMetrics.widthPixels;
            int heights = displayMetrics.heightPixels;
    
    //        Display display = activity.getWindowManager().getDefaultDisplay();
    //        // 获取屏幕宽和高
    //        int widths = display.getWidth();
    //        int heights = display.getHeight();
            // 允许当前窗口保存缓存信息
            view.setDrawingCacheEnabled(true);
            // 去掉状态栏
            Bitmap bmp = Bitmap.createBitmap(view.getDrawingCache(), 0,
                    statusBarHeights, widths, heights - statusBarHeights);
            // 销毁缓存信息
            view.destroyDrawingCache();
            return bmp;
        }
    

    对于ListView、RecyclerView等控件、长截图自行搜索截图方法。

    SurfaceView截图

    关于SurfaceView截屏网上也没有搜到什么解决方案,原因SurfaceView采用双缓存机制,SurfaceView在更新视图时用到了两张 Canvas,一张 frontCanvas 和一张 backCanvas ,每次实际显示的是 frontCanvas ,backCanvas 存储的是上一次更改前的视图。当你在播放这一帧的时候,它已经提前帮你加载好后面一帧了,所以播放起视频很流畅。
    当使用lockCanvas() 获取画布时,得到的实际上是backCanvas 而不是正在显示的 frontCanvas ,之后你在获取到的 backCanvas 上绘制新视图,再 unlockCanvasAndPost(canvas)此视图,那么上传的这张 canvas 将替换原来的 frontCanvas 作为新的frontCanvas ,原来的 frontCanvas 将切换到后台作为 backCanvas 。例如,如果你已经先后两次绘制了视图A和B,那么你再调用 lockCanvas() 获取视图,获得的将是A而不是正在显示的B,之后你将重绘的 A 视图上传,那么 A 将取代 B 作为新的 frontCanvas 显示在SurfaceView 上,原来的B则转换为backCanvas。相当与多个线程,交替解析和渲染每一帧视频数据 --引用https://www.jianshu.com/p/a2a235bee59e

    普通View onDraw 内容是静态的,不调invalidate() 它是不会发生变化,你可以拿到里面的Bitmap;但是SurfaceView不同,无法拿到它back buffer里面的Bitmap。

    官方注释:

    * <p>Enables or disables the drawing cache. When the drawing cache is enabled, the next call
        * to {@link #getDrawingCache()} or {@link #buildDrawingCache()} will draw the view in a
        * bitmap. Calling {@link #draw(android.graphics.Canvas)} will not draw from the cache when
        * the cache is enabled. To benefit from the cache, you must request the drawing cache by
        * calling {@link #getDrawingCache()} and draw it on screen if the returned bitmap is not
        * null.</p>
    

    如果你用普通view的截图方法去获取截图,老铁你看到的图片是这样的

    图片是黑色
    

    这里我们可以采用另一种思路去实现,所谓条条道路通罗马吗,我们可以从视频源来获取截图,反正视频源的图片也是一帧一帧的渲染到SurfaceView上面,获取截图之后,获取SurfaceView控件的宽高参数设置到截图上,这里涉及到另外一种情况,就是SurfaceView控件上面还有View控件,这种可以将普通控件Bitmap图片与SurfaceView截图合成一张图片,这样就完美截图SurfaceView附近的截图。

    SurfaceView截图
    获取SurfaceView控件视频截图

    点击截图时,获取MediaPlayer 当前一张视频图片

      /**
         * @param uri         视频的本地路径
         * @param context     上下文
         * @param mediaPlayer 媒体播放Player
         * @return Bitmap 返回的视频图像
         */
        public static Bitmap getVideoFrame(Context context, Uri uri, MediaPlayer mediaPlayer) {
            Bitmap bmp = null;
            // android 2.3及其以上版本使用
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            try {
                retriever.setDataSource(context, uri);
                // 这一句是必须的
                String timeString =
                        retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                // 获取总长度,这一句也是必须的
                long titalTime = Long.parseLong(timeString) * 1000;
    
                int duration = mediaPlayer.getDuration();
                // 通过这个计算出想截取的画面所在的时间
                long videoPosition = titalTime * mediaPlayer.getCurrentPosition() / duration;
                if (videoPosition > 0) {
                    bmp = retriever.getFrameAtTime(videoPosition,
                            MediaMetadataRetriever.OPTION_CLOSEST);
                }
            } catch (IllegalArgumentException ex) {
                ex.printStackTrace();
            } catch (RuntimeException ex) {
                ex.printStackTrace();
            } finally {
                try {
                    retriever.release();
                } catch (RuntimeException e) {
                    e.fillInStackTrace();
                }
            }
            return bmp;
        }
    
    获取SurfaceView控件照片截图

    点击截图时,获取Camera当前一张照片,类似于拍照

     camera.takePicture(null, null, new Camera.PictureCallback() {
                @Override
                public void onPictureTaken(byte[] data, Camera camera) {
                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                    Matrix matrix = new Matrix();
                    //旋转照片
                    matrix.setRotate(360 - 90);
                    matrix.postScale(-1, 1);
                    bitmap = createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                    if (takePhotoCallBack != null) {
                        takePhotoCallBack.takePhotoCallBack(bitmap);
                    }
                    //拍照完成继续
                    startCamera(holder);
                }
            });
    
    获取SurfaceView控件自定义截图

    一般SurfaceView的视频源不仅限于Camera、Media,还有其他乱七八糟的视频源,比如opencv等,这个时候我们可以获取SurfaceView的Canvas,这样我们可以将Canvas内容转换成Bitmap。

    public abstract class AbstractMySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
        SurfaceHolder surfaceHolder;
    
        public AbstractMySurfaceView(Context context) {
            super(context);
            surfaceHolder = this.getHolder();
            surfaceHolder.addCallback(this);
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                   int height) {
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            new Thread(new MyRunnable()).start();
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
        }
    
        class MyRunnable implements Runnable {
            @Override
            public void run() {
                Canvas canvas = surfaceHolder.lockCanvas(null);//获取画布
                doDraw(canvas);
                surfaceHolder.unlockCanvasAndPost(canvas);//解锁画布
            }
        }
    
        //将绘制方法抽象出来供子类实现
        protected abstract void doDraw(Canvas canvas);
    
        //将oDraw绘制在自己的canvas上
        public Bitmap getBitmap() {
            Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            doDraw(canvas);
            return bitmap;
        }
    }
    
     mAbstractMySurfaceView = new AbstractMySurfaceView(this) {
                @Override
                protected void doDraw(Canvas canvas) {
                    Paint paint = new Paint();
                    paint.setColor(Color.YELLOW);
                    canvas.drawRect(new RectF(100, 100, 1000, 500), paint);
                }
     };
    

    将opencv的视频一帧一帧绘制到Canvas上,从而获取Canvas内容转化成Bitmap。

    org.opencv.core.Rect rectOC = new org.opencv.core.Rect(x, y, width, height);
    Rect rect = new Rect(rectOC.x, rectOC.y, rectOC.x + rectOC.width, rectOC.y + rectOC.height);
    canvas.drawRect(rect, mPaint);
    
    整个屏幕截屏(必杀技)

    Android 5.0以上的版本才支持整个屏幕截屏,这样就不管啥控件屏幕上你能看到都能截取出来,这里为了大家方便调用对其进行封装。

    ScreenCapture screenCapture = new ScreenCapture(this);
    Bitmap captureBitmap = screenCapture.getCaptureBitmap();
    

    这个参照了Rxpermission的思想进行了封装,使屏幕截图更加方便,以后添加屏幕录制功能后,将这个封装抽出来作为一个库。

    总结

    Android普通View控件截屏一般没什么问题,网上一搜一大把,对于SurfaceView截屏就有点困难了,一般方式的截图都是黑屏,我们可以采用三种思路:
    1、获取源头视频的截图作为SurfaceView的截图
    2、获取SurfaceView的画布canvas,将canvas保存成Bitmap
    3、直接截取整个屏幕,然后在截图SurfaceView位置的图

    相关文章

      网友评论

        本文标题:关于Android SurfaceView截屏总结

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