先把自定义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背景块拼接时,需要注意下这点!