关于Android SurfaceView截屏总结

简介: 关于Android SurfaceView截屏总结

普通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开发
Android播放器之SurfaceView与GLSurfaceView
Surface的官方介绍:Handle onto a raw buffer that is being managed by the screen compositor,Surface是一个raw buffer的句柄,通过它在raw buffer上进行绘制,可以通过Surface获得一个Canvas。
273 0
|
2月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
152 8
|
2月前
|
Android开发
Android中SurfaceView的双缓冲机制和普通View叠加问题解决办法
本文介绍了 Android 平台上的 SurfaceView,这是一种高效的图形渲染控件,尤其适用于视频播放、游戏和图形动画等场景。文章详细解释了其双缓冲机制,该机制通过前后缓冲区交换来减少图像闪烁,提升视觉体验。然而,SurfaceView 与普通 View 叠加时可能存在 Z-Order 不一致、同步问题及混合渲染难题。文中提供了使用 TextureView、调整 Z-Order 和创建自定义组合控件等多种解决方案。
125 9
|
4月前
|
Android开发 开发者
Android经典面试题之SurfaceView和TextureView有什么区别?
分享了`SurfaceView`和`TextureView`在Android中的角色。`SurfaceView`适于视频/游戏,独立窗口低延迟,但变换受限;`TextureView`支持复杂变换,视图层级中渲染,适合动画/视频特效,但性能略低。两者在性能、变换、使用和层级上有差异,开发者需按需选择。
73 1
|
6月前
|
Android开发
Android 截屏 录屏 与获取log
Android 截屏 录屏 与获取log
54 1
|
编解码 Android开发
Android | 老生常谈!屏幕适配原理 & 方案总结笔记
Android | 老生常谈!屏幕适配原理 & 方案总结笔记
569 0
Android | 老生常谈!屏幕适配原理 & 方案总结笔记
|
Android开发
Android 使用MediaPlayer和SurfaceView播放视频
Android 使用MediaPlayer和SurfaceView播放视频
168 0
|
Android开发
Android 裁剪摄像头预览窗口-SurfaceView
Android 裁剪摄像头预览窗口-SurfaceView
748 0
Android 裁剪摄像头预览窗口-SurfaceView
|
开发工具 Android开发
Android推送集成方案总结
刚做完推送集成方案,记录下坑。 这里记录的特性和使用时针对写blog时采用的sdk的,具体使用流程和限制还请参考官方给出的sdk. #### 1、推送规则 小米手机用小米推送; 华为手机用华为推送; 其他手机用友盟推送。