前言
现在APP几乎都会有海报的功能,而海报页面跟要生成海报的数据页面,有一部分是一模一样的,当然可以手动复制一份去实现这个功能,这篇文章讲一下如何利用Canvas去实现这个功能;
先来Android官网对Canvas的描述:
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
Canvas
类保存“draw”调用。要绘制一些东西,需要4个基本组件:
1.一个bitmap用来确定像素大小
2.一个用来承载绘制调用(写入位图)的画布
3.一个绘图原语(例如Rect
、Path
、text
、Bitmap
)
4.Paint
(用来描述绘图的颜色和样式)
首先要对view的getTop()
getLeft()
getRight()
getBottom()
有清晰的概念。
-
getTop()
:获取到的是View自身的顶边到其父布局顶边的距离 -
getLeft()
:获取到的是View自身的左边到其父布局左边的距离 -
getRight()
:获取到的是View自身的右边到其父布局左边的距离 -
getBottom()
:获取到的是View自身的底边到其父布局顶边的距离
所以其实view的宽高:
width = getRight() - getLeft()
height = getBottom() - getTop()
好,有这个概念以后,那接下来的就简单多了。
情况一
假设我们要完整的截取这个view,我们如何获取这个view的bitmap呢?
第一种方法:我们可以使用view自带的方法
view.setDrawingCacheEnabled(true)
val bitmap = view.getDrawingCache()
但是,view.getDrawingCache()
已经被官方标记为@Deprecated
,官方现在推荐的是view.draw(Canvas canvas)
,所以往下看
第二种方法:我们也可以这样做
1.先创建一个空白的bitmap
2.把空白的bitmap设置给canvas
3.然后调用view的draw(Canvas canvas)方法
val bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(bgColor); //防止view没有设置背景的时候出现黑底
view.draw(canvas);
通过这两种方法,我们就可以拿到view的bitmap,就不用再做海报视图页面的时候,又重新写布局,绑定布局了。我们只需把bitmap传递过去,在目标页面用ImageView展示即可。
bitmap传参需要注意避免TransactionTooLargeException的问题超链接 ,详情见我另外一篇文章。
一般情况下,这样就可以满足大部分的业务需求了,那肯定有不一般的情况。
情况二
我不要整个view的截图,如何指定只截取到某个子view?如图所示,假设childC后面还有childD,childE,childF,我只需要截取到childC
分析一下,思路其实是一样的,既然不要整个view的截图,那是不是我只要截取到每个子view,然后进行拼接起来就可以了呢?
1.那首先我们得确定好画布的大小,根据上面那张图,宽度默认是父parent的宽度,高度便是我们要截取到的childC的getBottom
int bitmapWidth = vParent.getWidth();
int bitmapHeight = vChildUntil.getTop();
//创建空白的bitmap
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
2.拿到bitmap确定好大小以后,就可以给画布设置了
Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);
3.然后就可以for循环,依次把子view的bitmap draw到画布上,这里需要注意的是,需要按照子view原来在父view的位置,在画布对应的地方去draw。
bitmapChild = view2Bitmap(child);
canvas.drawBitmap(bitmapChild, child.getLeft(), child.getTop(), paint);
完整代码如下:
public static void createBitmapFromParentUntilChildView(ViewGroup vParent, View vChildUntil, @ColorInt int bgColor, OnCreateBitmapResultListener listener) {
ThreadPlus.submitRunnable(() -> {
//获取bitmap的宽高
int bitmapWidth = vParent.getWidth();
int bitmapHeight = vChildUntil.getTop();
//创建空白的bitmap
Bitmap bitmap = createBitmapSafely(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888, 1); //这里不能RGB_565,背景会变黑色
//Rect rect = new Rect(0, 0, bitmapWidth, bitmapHeight);
if (bitmap != null) {
Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);
canvas.drawColor(bgColor); // 防止 View 上面有些区域空白导致最终 Bitmap 上有些区域变黑
//开始依次画子view的bitmap
int childCount = vParent.getChildCount();
View child;
Bitmap bitmapChild;
Paint paint = new Paint();
for (int i = 0; i < childCount; i++) {
child = vParent.getChildAt(i);
if (child == vChildUntil) {
break;
}
bitmapChild = view2Bitmap(child, bgColor);
canvas.drawBitmap(bitmapChild, child.getLeft(), child.getTop(), paint);
}
}
if (listener != null) {
listener.onResult(bitmap);
}
});
//return bitmap;
}
public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {
try {
return Bitmap.createBitmap(width, height, config);
} catch (OutOfMemoryError e) {
e.printStackTrace();
if (retryCount > 0) {
System.gc();
return createBitmapSafely(width, height, config, retryCount - 1);
}
return null;
}
}
/**
* View to bitmap
*/
public static Bitmap view2Bitmap2(final View view, int bgColor) {
if (view == null) return null;
Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(bgColor); //避免黑底
view.draw(canvas);
return bitmap;
}
public interface OnCreateBitmapResultListener {
void onResult(Bitmap bitmap);
}
最后
到这里就已经可以根据现实需求情况去做选择了,建议把这些工作都放在子线程
去做,避免UI线程阻塞卡顿
。
地图View同理,获取到地图层的bitmap后,在原来container bitmap的基础上,叠加上去地图bitmap。
以上。