天天看點

Android 自定義View出現的繪制背景區域斷開的情況

先把自定義View 運作出來的效果貼一下:(貼上兩張圖,注意箭頭标記的地方)

Android 自定義View出現的繪制背景區域斷開的情況
Android 自定義View出現的繪制背景區域斷開的情況

然後發現有缺陷,會出現線條,大概還和innerWidth/innerHeight的奇偶數有關系,雖然上面代碼中有除2: “innerWidth / 2” ,會存在小數,但是繪制方法(canvas.drawRect)裡的參數都是Float,怎麼還會有問題呢?

貼下相關代碼:(自定義View的代碼見:【CustomView】掃碼中的掃描框(ViewfinderView)-簡單實作)

// 視圖不需要對其大小進行特殊控制,您隻需替換一個方法,即 onSizeChanged()
    override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
        super.onSizeChanged(width, height, oldWidth, oldHeight)
        viewWidth = width.toFloat()
        viewHeight = height.toFloat()

        val xMid = (viewWidth / 2)
        val yMid = (viewHeight / 2)
        val leftOffset = (xMid - innerWidth / 2)
        val topOffset = (yMid - innerHeight / 2)
        viewfinderFrame.set(
            leftOffset,
            topOffset,
            leftOffset + innerWidth,
            topOffset + innerHeight
        )
    }

    // 在 onDraw() 方法内建立繪制對象會顯著降低性能并使界面顯得卡頓。
    public override fun onDraw(canvas: Canvas) {
        canvas.drawRect(0f, 
            0f, 
            viewWidth, 
            viewfinderFrame.top, 
            paint
        )
        canvas.drawRect(
            0f,
            viewfinderFrame.top,
            viewfinderFrame.left,
            viewfinderFrame.bottom,
            paint
        )
        canvas.drawRect(
            viewfinderFrame.right,
            viewfinderFrame.top,
            viewWidth,
            viewfinderFrame.bottom,
            paint
        )
        canvas.drawRect(
            0f,
            viewfinderFrame.bottom,
            viewWidth,
            viewHeight,
            paint
        )
        
        ... //省略了一些無關的代碼
    }
           

然後去找了canvas.drawRect() 源碼:(從Canvas >> BaseCanvas >> BaseCanvas_Delegate)

public class BaseCanvas_Delegate {

    @LayoutlibDelegate
    /*package*/ static void nDrawRect(long nativeCanvas,
            final float left, final float top, final float right, final float bottom, long paint) {

        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
                (graphics, paintDelegate) -> {
                    int style = paintDelegate.getStyle();

                    // draw
                    if (style == Paint.Style.FILL.nativeInt ||
                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
                        graphics.fillRect((int)left, (int)top,
                                (int)(right-left), (int)(bottom-top));
                    }

                    if (style == Paint.Style.STROKE.nativeInt ||
                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
                        graphics.drawRect((int)left, (int)top,
                                (int)(right-left), (int)(bottom-top));
                    }
                });
    }
}
           

然後在BaseCanvas_Delegate.java 檔案中發現了存在某種問題或陰謀:竟然直接強轉Int,是夠直接的。

順帶列印下log:

com.android.demo D/ViewfinderView: onSizeChanged - viewfinderFrame w: 683, h: 420, oldw: 0, oldh: 0, measuredWidth: 683 , measuredHeight: 420

com.android.demo D/ViewfinderView: onDraw - viewWidth: 683.0, viewHeight: 420.0 , viewfinderFrame top: 116.8125, left: 180.0625, right: 502.9375, bottom: 303.1875
           

根據log裡的數值,我們替換到上面到代碼再根據源碼計算:

// onDraw - viewWidth: 683.0, viewHeight: 420.0 , viewfinderFrame top: 116.8125, left: 180.0625, right: 502.9375, bottom: 303.1875
canvas.drawRect(0f, 0f, 683.0, 116.8125, paint)  // graphics.fillRect(0, 0,  683, 116)

canvas.drawRect(0f, 116.8125, 180.0625, 303.1875, paint) // graphics.fillRect(0, 116, 180, 186)

canvas.drawRect(502.9375, 116.8125, 683.0, 303.1875, paint) // graphics.fillRect(502, 116, 180, 186)

canvas.drawRect(0f, 303.1875, 683.0, 420.0, paint) // graphics.fillRect(0, 303, 683, 98)
           

然後在根據四塊 Rect 來拼接核實一下:(用ABCD來表示,繪制的View) 

Android 自定義View出現的繪制背景區域斷開的情況

可以看到:A>B是無縫連接配接,但是B>D不是,因為116+186 = 302 < 303 (這裡丢失了1px,直接強轉Int帶來的“收益”啊!)

是以:這裡需要提前去處理下類型強轉問題,這裡隻需要在onSizeChanged()做處理即可,貼上優化代碼:

// 視圖不需要對其大小進行特殊控制,您隻需替換一個方法,即 onSizeChanged()
    override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
        super.onSizeChanged(width, height, oldWidth, oldHeight)
        Log.d(
            "ViewfinderView",
            "onSizeChanged - viewfinderFrame w: ${width}, h: ${height}, oldw: ${oldWidth}, oldh: ${oldHeight}, measuredWidth: $measuredWidth , measuredHeight: $measuredHeight"
        )
        viewWidth = width.toFloat()
        viewHeight = height.toFloat()

        val xMid = (viewWidth / 2).toInt()
        val yMid = (viewHeight / 2).toInt()
        val leftOffset = xMid - (innerWidth / 2).toInt()
        val topOffset = yMid - (innerHeight / 2).toInt()
        viewfinderFrame.set(
            leftOffset.toFloat(),
            topOffset.toFloat(),
            (leftOffset + innerWidth),
            (topOffset + innerHeight)
        )
    }
           

⚠️ 上面的處理,使原本奇數寬高的布局,雖然不至于會在中間丢失了1px,但是也會讓居中的樣式會整理偏移1px。

在繪制View背景塊拼接時,需要注意下這點!