在记录【CustomView】Android 自定义绘制 这篇文章时,发现此篇代码存在需优化的地方:(以下代码已优化)
我们在自定义绘制View的时候,需要注意以下三点:
⚠️ 提前创建对象是一项重要的优化措施。视图会非常频繁地重新绘制,并且许多绘制对象的初始化都需要占用很多资源。
⚠️ 请在 onSizeChanged() 中计算位置、尺寸以及其他与视图大小相关的任何值,而不要在每次绘制(onDraw)时都重新计算。
⚠️ 在 onDraw() 方法内创建绘制对象会显著降低性能并使界面显得卡顿。
项目中有的扫码功能中需要覆盖一层扫码框,如下所示:
#### 自定义View,绘制4个角且需要圆角
* 这里绘制4个角是用画线条的方式实现,用strokeCap = Paint.Cap.ROUND来实现圆角。
private val paint by lazy {
Paint().apply {
strokeCap = Paint.Cap.ROUND
isAntiAlias = true
}
}
canvas.drawLine(
frame.left,
frame.top,
(frame.left + innerCornerLengthHorizontal),
(frame.top),
paint
)
1.CustomView >>> ViewfinderView
/**
* viewfinder window
*/
class ViewfinderView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : View(context, attrs, defStyleAttr) {
// viewfinder inner Width
private var innerWidth = 0F
// viewfinder inner Height
private var innerHeight = 0F
// view inner horizontal length
private var innerCornerLengthHorizontal = 0F
// view inner vertical length
private var innerCornerLengthVertical = 0F
// view inner corner width
private var innerCornerWidth = 0F
private var viewWidth: Float = 0F
private var viewHeight: Float = 0F
// view inner color
private var innerCornerColor = 0
private val viewfinderFrame: RectF = RectF(0f, 0f, 0f, 0f)
private val paint by lazy {
Paint().apply {
strokeCap = Paint.Cap.ROUND
isAntiAlias = true
color = resources.getColor(R.color.viewfinder_mask)
}
}
private val paintCorner by lazy {
Paint().apply {
strokeCap = Paint.Cap.ROUND
isAntiAlias = true
style = Paint.Style.FILL
strokeWidth = innerCornerWidth
}
}
init {
initInnerRect(context, attrs)
}
/**
* Initialize the size of the inner frame
*
* @param context
* @param attrs
*/
private fun initInnerRect(
context: Context,
attrs: AttributeSet?
) {
val styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView)
innerWidth = styledAttributes.getDimension(
R.styleable.ViewfinderView_inner_width,
resources.getDimension(R.dimen.default_inner_width)
)
innerHeight = styledAttributes.getDimension(
R.styleable.ViewfinderView_inner_height,
resources.getDimension(R.dimen.default_inner_height)
)
innerCornerColor = styledAttributes.getColor(
R.styleable.ViewfinderView_inner_corner_color,
resources.getColor(R.color.default_inner_color)
)
innerCornerLengthHorizontal = styledAttributes.getDimension(
R.styleable.ViewfinderView_inner_corner_length_horizontal,
resources.getDimension(R.dimen.default_inner_corner_length_horizontal)
)
innerCornerLengthVertical = styledAttributes.getDimension(
R.styleable.ViewfinderView_inner_corner_length_vertical,
resources.getDimension(R.dimen.default_inner_corner_length_vertical)
)
innerCornerWidth = styledAttributes.getDimension(
R.styleable.ViewfinderView_inner_corner_width,
resources.getDimension(R.dimen.default_inner_corner_width)
)
paintCorner.color = innerCornerColor
styledAttributes.recycle()
}
// 视图不需要对其大小进行特殊控制,您只需替换一个方法,即 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)
)
}
// 如果您需要更精细地控制视图的布局参数,请实现 onMeasure()。
// 此方法的参数是 View.MeasureSpec 值,用于告诉您视图的父视图希望您的视图有多大,以及该大小是硬性最大值还是只是建议值。
// 作为优化措施,这些值以打包整数形式存储,您可以使用 View.MeasureSpec 的静态方法解压缩每个整数中存储的信息。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
Log.d(
"ViewfinderView",
"onMeasure - measuredWidth: $measuredWidth, measuredHeight: $measuredHeight"
)
}
// 提前创建对象是一项重要的优化措施。视图会非常频繁地重新绘制,并且许多绘制对象的初始化都需要占用很多资源。
// 在 onDraw() 方法内创建绘制对象会显著降低性能并使界面显得卡顿。
public override fun onDraw(canvas: Canvas) {
Log.d(
"ViewfinderView",
"onDraw - viewWidth: $viewWidth, viewHeight: $viewHeight , viewfinderFrame top: ${viewfinderFrame.top}, left: ${viewfinderFrame.left}, right: ${viewfinderFrame.right}, bottom: ${viewfinderFrame.bottom},"
)
// Draw the exterior (i.e. outside the framing rect) darkened
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
)
drawFrameBounds(canvas, viewfinderFrame)
}
/**
* Draw frame border
*
* @param canvas
* @param frame
*/
private fun drawFrameBounds(
canvas: Canvas,
frame: RectF
) {
// Upper left corner
canvas.drawLine(
frame.left,
frame.top,
(frame.left),
(frame.top + innerCornerLengthVertical),
paintCorner
)
canvas.drawLine(
frame.left,
frame.top,
(frame.left + innerCornerLengthHorizontal),
(frame.top),
paintCorner
)
// Upper right corner
canvas.drawLine(
(frame.right),
frame.top,
frame.right,
(frame.top + innerCornerLengthVertical),
paintCorner
)
canvas.drawLine(
frame.right - innerCornerLengthHorizontal,
frame.top,
frame.right,
frame.top,
paintCorner
)
// Lower left corner
canvas.drawLine(
frame.left,
(frame.bottom - innerCornerLengthVertical),
(frame.left),
frame.bottom,
paintCorner
)
canvas.drawLine(
frame.left,
(frame.bottom),
(frame.left + innerCornerLengthHorizontal),
frame.bottom,
paintCorner
)
// Lower right corner
canvas.drawLine(
(frame.right),
(frame.bottom - innerCornerLengthVertical),
frame.right,
frame.bottom,
paintCorner
)
canvas.drawLine(
(frame.right - innerCornerLengthHorizontal),
(frame.bottom),
frame.right,
frame.bottom,
paintCorner
)
}
}
源码上有段代码比较奇怪:
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)
)
⚠️ 为什么在原本就是Float的情况下要先转Int,再转Float,我想在代码审核的审核,谁也不允许多做这一步!
具体分析详见:Android 自定义View出现的绘制背景区域断开的情况
2. styleable添加xml属性
<resources>
<color name="viewfinder_mask">#80ffffff</color>
<color name="default_inner_color">#4a008a</color>
<declare-styleable name="ViewfinderView">
<attr name="inner_width" format="dimension" />
<attr name="inner_height" format="dimension" />
<attr name="inner_corner_color" format="color" />
<attr name="inner_corner_length_horizontal" format="dimension" />
<attr name="inner_corner_length_vertical" format="dimension" />
<attr name="inner_corner_width" format="dimension" />
</declare-styleable>
<dimen name="default_inner_width">409dp</dimen>
<dimen name="default_inner_height">250dp</dimen>
<dimen name="default_inner_corner_length_horizontal">34dp</dimen>
<dimen name="default_inner_corner_length_vertical">23dp</dimen>
<dimen name="default_inner_corner_width">4dp</dimen>
</resources>
使用示例:
<com.android.demo.view.ViewfinderView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:inner_corner_color="@color/colorAccent"
app:inner_corner_length_horizontal="31dp"
app:inner_corner_length_vertical="21dp"
app:inner_corner_width="4dp"
app:inner_height="71dp"
app:inner_width="123dp" />
效果: