Files
StallDualScreen/app/src/main/java/com/sw/dualscreen/view/HollowOvalInRectView.kt
T
2026-02-11 17:00:08 +08:00

122 lines
4.2 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.sw.dualscreen.view
import androidx.core.graphics.toColorInt
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
/**
* 带镂空椭圆的矩形自定义View
* 矩形尺寸:400dp × 510dp,背景色 #FFF4FAFF
* 镂空椭圆尺寸:400dp × 436dp,居中显示
*/
class HollowOvalInRectView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// 基础尺寸配置(dp
private val rectWidthDp = 400f // 矩形宽度
private val rectHeightDp = 510f // 矩形高度
private val ovalWidthDp = 400f // 椭圆宽度
private val ovalHeightDp = 436f // 椭圆高度
// 转换后的像素尺寸
private val rectWidth: Float
private val rectHeight: Float
private var ovalWidth: Float
private var ovalHeight: Float
// 矩形背景画笔(颜色 #FFF4FAFF)
private val rectPaint = Paint().apply {
isAntiAlias = true
color = "#FFF4FAFF".toColorInt() // 矩形背景色
// color = "#FFFFFF".toColorInt() // 矩形背景色
style = Paint.Style.FILL
}
// 镂空椭圆画笔(核心:DST_OUT模式实现镂空)
private val hollowOvalPaint = Paint().apply {
isAntiAlias = true
color = Color.BLACK // 颜色不影响镂空效果,仅需非透明即可
style = Paint.Style.FILL
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
}
// 椭圆路径(复用避免重复创建)
private val ovalPath = Path()
init {
// dp转px,保证多设备尺寸一致
val density = resources.displayMetrics.density
rectWidth = rectWidthDp * density
rectHeight = rectHeightDp * density
ovalWidth = ovalWidthDp * density
ovalHeight = ovalHeightDp * density
// 关键配置:关闭硬件加速保证Xfermode生效
setLayerType(LAYER_TYPE_HARDWARE, null)
// 视图背景透明,避免干扰
setBackgroundColor(Color.TRANSPARENT)
}
/**
* 强制设置View尺寸为指定的矩形尺寸
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val targetWidth = MeasureSpec.makeMeasureSpec(rectWidth.toInt(), MeasureSpec.EXACTLY)
val targetHeight = MeasureSpec.makeMeasureSpec(rectHeight.toInt(), MeasureSpec.EXACTLY)
super.onMeasure(targetWidth, targetHeight)
}
/**
* 核心绘制逻辑:先画矩形,再画镂空椭圆
*/
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 保存画布图层,确保Xfermode生效
val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null)
// 1. 绘制底层矩形(背景色 #FFF4FAFF)
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), rectPaint)
// 2. 计算椭圆居中坐标(椭圆宽度和矩形宽度一致,仅垂直居中)
val ovalTop = (rectHeight - ovalHeight) / 2 // 椭圆顶部偏移
val ovalBottom = ovalTop + ovalHeight // 椭圆底部坐标
// 3. 绘制镂空椭圆(居中)
ovalPath.reset()
ovalPath.addOval(
0f, // 左:和矩形左对齐(宽度一致)
ovalTop, // 上:垂直居中
rectWidth, // 右:和矩形右对齐
ovalBottom, // 下
Path.Direction.CW
)
canvas.drawPath(ovalPath, hollowOvalPaint)
// 恢复画布状态
canvas.restoreToCount(layerId)
}
// 扩展方法:支持动态修改矩形背景色
fun updateRectBackgroundColor(colorHex: String) {
try {
rectPaint.color = Color.parseColor(colorHex)
invalidate() // 重绘视图
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
}
// 扩展方法:支持动态修改椭圆尺寸(可选)
fun updateOvalSize(widthDp: Float, heightDp: Float) {
val density = resources.displayMetrics.density
ovalWidth = widthDp * density
ovalHeight = heightDp * density
invalidate()
}
}