先把自定義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)
可以看到: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背景塊拼接時,需要注意下這點!