普通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控件视频截图
点击截图时,获取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位置的图