关于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位置的图

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容