如何利用Canvas打造一个海报视图?

前言

现在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).


76BA2742.png

Canvas类保存“draw”调用。要绘制一些东西,需要4个基本组件:
1.一个bitmap用来确定像素大小
2.一个用来承载绘制调用(写入位图)的画布
3.一个绘图原语(例如RectPathtextBitmap
4.Paint(用来描述绘图的颜色和样式)

首先要对view的getTop() getLeft() getRight() getBottom()有清晰的概念。

20181113111908225.png
  • 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

微信截图_20220601162617.png

分析一下,思路其实是一样的,既然不要整个view的截图,那是不是我只要截取到每个子view,然后进行拼接起来就可以了呢?
76BC11EB.jpg

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。

以上。

你可能感兴趣的:(如何利用Canvas打造一个海报视图?)