如何使自定义View的进度边框从左上角而非左下角开始绘制?
问题:修改自定义圆角矩形进度边框的起始绘制位置
当前拖动滑块时,自定义圆角矩形边框从左下角开始绘制,需要修改为从左上角(或任意指定位置)开始。
原自定义View代码
internal class V(context: Context?) : View(context) { var path: Path = Path() var paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) var length: Float = 0f var intervals: FloatArray = floatArrayOf(0f, 0f) init { paint.color = Color.GREEN paint.style = Paint.Style.STROKE paint.strokeWidth = 20f } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { path.reset() val rect = RectF(0f, 0f, w.toFloat(), h.toFloat()) val inset = paint.strokeWidth rect.inset(inset, inset) path.addRoundRect(rect, 100f, 100f, Path.Direction.CW) length = PathMeasure(path, false).length intervals[1] = length intervals[0] = intervals[1] val effect: PathEffect = DashPathEffect(intervals, length) paint.setPathEffect(effect) } fun setProgress(progress: Int) { val effect: PathEffect = DashPathEffect(intervals, length - length * progress / 100) paint.setPathEffect(effect) invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawPath(path, paint) } }
原Activity使用代码
val ll = LinearLayout(this) ll.orientation = LinearLayout.VERTICAL val sb = SeekBar(this) ll.addView(sb) val v = V(this) ll.addView(v) setContentView(ll) val sbcl: SeekBar.OnSeekBarChangeListener = object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { v.setProgress(progress) } override fun onStartTrackingTouch(seekBar: SeekBar) {} override fun onStopTrackingTouch(seekBar: SeekBar) {} } sb.setOnSeekBarChangeListener(sbcl)
解决方案
方法1:手动构建从左上角开始的路径
默认Path.addRoundRect()的CW方向路径起点是左下角,我们可以手动构建路径,让起点从左上角开始,这样进度绘制的起始位置就会对应左上角。
修改自定义View的onSizeChanged方法:
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { path.reset() val rect = RectF(0f, 0f, w.toFloat(), h.toFloat()) val inset = paint.strokeWidth rect.inset(inset, inset) val rx = 100f val ry = 100f // 手动构建从左上角开始的顺时针路径 path.moveTo(rect.left + rx, rect.top) // 上边直边:从左上圆角结束点到右上圆角开始点 path.rLineTo(rect.width() - 2 * rx, 0f) // 右上圆角:90度圆弧 path.arcTo(rect.right - 2 * rx, rect.top, rect.right, rect.top + 2 * ry, 0f, 90f, false) // 右边直边:从右上圆角结束点到右下圆角开始点 path.rLineTo(0f, rect.height() - 2 * ry) // 右下圆角:90度圆弧 path.arcTo(rect.right, rect.bottom - 2 * ry, rect.right - 2 * rx, rect.bottom, 90f, 90f, false) // 下边直边:从右下圆角结束点到左下圆角开始点 path.rLineTo(-(rect.width() - 2 * rx), 0f) // 左下圆角:90度圆弧 path.arcTo(rect.left, rect.bottom - 2 * ry, rect.left + 2 * rx, rect.bottom, 180f, 90f, false) // 左边直边:从左下圆角结束点到左上圆角开始点 path.rLineTo(0f, -(rect.height() - 2 * ry)) // 左上圆角:90度圆弧 path.arcTo(rect.left, rect.top, rect.left + 2 * rx, rect.top + 2 * ry, 270f, 90f, false) path.close() length = PathMeasure(path, false).length intervals[1] = length intervals[0] = intervals[1] val effect: PathEffect = DashPathEffect(intervals, length) paint.setPathEffect(effect) }
修改后,路径起点为左上角的直边起始位置,拖动滑块时进度会从左上角开始绘制。
方法2:调整DashPathEffect的偏移量(不修改路径)
如果不想手动构建路径,可以计算从默认起点(左下角)到左上角的路径长度,调整DashPathEffect的偏移值,让绘制起始点定位到左上角。
- 在自定义View中添加变量存储左上角对应的偏移量:
private var startOffsetToTopLeft: Float = 0f
- 修改
onSizeChanged方法,计算该偏移量:
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { path.reset() val rect = RectF(0f, 0f, w.toFloat(), h.toFloat()) val inset = paint.strokeWidth rect.inset(inset, inset) val rx = 100f path.addRoundRect(rect, rx, rx, Path.Direction.CW) val pathMeasure = PathMeasure(path, false) length = pathMeasure.length // 计算从左下角到左上角的路径长度:右下直边 + 右下圆角 + 右上直边 + 右上圆角 val rightEdgeLength = rect.width() - rx val cornerArcLength = Math.PI * rx / 2 val topEdgeLength = rect.height() - rx startOffsetToTopLeft = (rightEdgeLength + cornerArcLength + topEdgeLength + cornerArcLength).toFloat() intervals[1] = length intervals[0] = intervals[1] // 初始偏移设置为左上角位置,保证初始状态隐藏 val effect: PathEffect = DashPathEffect(intervals, startOffsetToTopLeft) paint.setPathEffect(effect) }
- 修改
setProgress方法,调整偏移量:
fun setProgress(progress: Int) { // 计算目标偏移量:从左上角开始,根据进度反向偏移 val targetOffset = startOffsetToTopLeft - length * progress / 100f // 处理偏移量为负数的情况,取模保证在路径长度范围内 val finalOffset = if (targetOffset < 0) targetOffset + length else targetOffset val effect: PathEffect = DashPathEffect(intervals, finalOffset) paint.setPathEffect(effect) invalidate() }
此方法无需修改原路径结构,仅通过偏移调整就能让进度从左上角开始绘制。
内容的提问来源于stack exchange,提问作者Robin
相关产品推荐
相关产品推荐

