美文网首页
如何利用Canvas打造一个海报视图?

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

作者: Yuven | 来源:发表于2022-06-02 10:08 被阅读0次

    前言

    现在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打造一个海报视图?

          本文链接:https://www.haomeiwen.com/subject/sffqmrtx.html