Compose 自定义 - 绘制 Draw

博客围绕 Android 绘制操作展开,介绍了绘制概念,强调尺寸转换。阐述了 Modifier 修饰符绘制、Canvas() 可组合项绘制,还讲解了 Brush 的使用,包括颜色分布、图案重复、大小更改及用图片作 Brush。此外,介绍了 BlendMode 混合模式和 DrawScope 下的多种绘制方法及转换操作。

一、概念

所有的绘制操作都是通过调整像素大小来执行的。若要确保项目在不同的设备密度和屏幕尺寸上都能采用一致的尺寸,请务必使用 .toPx() 对 dp 进行转换或者采用小数尺寸。 

fun Dp.toPx(): Float = value * density
val width = 50.dp.toPx()

二、Modifier 修饰符绘制

官方页面

在修饰的可组合项之上或之下绘制。

.

.drawBehind()

fun Modifier.drawBehind(
    onDraw: DrawScope.() -> Unit
)

在被修饰项的后面绘制内容(底层是先绘制 Lambda 再绘制被修饰项,后绘制的会显示在上面),适合绘制背景边框。

.drawWithContent()

fun Modifier.drawWithContent(
    onDraw: ContentDrawScope.() -> Unit
)

可以掌控绘制顺序,手动调用 drawContent() 绘制被修饰项,后绘制的会显示在上面。

.drawWithCache()

fun Modifier.drawWithCache(
    onBuildDrawCache: CacheDrawScope.() -> DrawResult
)

当绘制复杂效果时,不希望因为重组而重新创建 Lambda 中用于绘制的实例如 Bush、Path 等,这可能会产生内存抖动,还可以避免重复的昂贵计算。在 Lambada 中调用 onDrawWithContent()、onDrawBehind() 就类似于上面两个修饰符的功能。

 

@Composable
fun Demo() {
    Row(
        modifier = Modifier.size(150.dp),
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Image(
            painterResource(id = R.drawable.logo_wechat_square),
            contentDescription = null,
            modifier = Modifier
                .size(50.dp)
                .drawWithContent {
                    drawContent()
                    drawRedDot()    //后绘制的会显示在上面
                }
        )
        Image(
            painterResource(id = R.drawable.logo_wechat_square),
            contentDescription = null,
            modifier = Modifier.padding(start = 10.dp).size(50.dp).drawBehind {
                drawRedDot()
            }
        )
    }
}

fun DrawScope.drawRedDot() {
    drawCircle(
        color = Color.Red,
        radius = 18F,
        center = Offset(drawContext.size.width, 0f)
    )
}

三、Canvas() 可组合项绘制

是一个可组合项,在 DrawScope 作用域中调用各种函数绘制。Compose 作为跨平台 UI 框架,所使用的 Canvas() 函数只是一个封装,最终还是调用具体平台即 Android 原生的 Canvas。

fun Canvas(

        modifier: Modifier,

        onDraw: DrawScope.() -> Unit

) = Spacer(modifier.drawBehind(onDraw))

发现该方法只是一个封装,真正绘制的是调用 drawBehind()。绘制内容是显示在 Spacer 下面的,由于 Spacer 是透明的,因此我们所绘制内容得以全部显示。

@Composable
fun Demo() {
    Canvas(
        modifier = Modifier.size(400.dp).background(Color.White)
    ) {
        //通过 size 可以拿到可以拿画布的宽高
        val width = size.width
        val height = size.height
        //通过 center 可以拿到画布中心点坐标
        val center = center
        //调用各种方法绘制
        drawRect(
            color = Color.Blue,
            size = Size(width,height)
        )
    }
}

四、笔刷 Brush

官方页面

用于绘制渐变色。

linearGradient

线性渐变

fun linearGradient(
        colors: List<Color>,        //渐变颜色
        start: Offset = Offset.Zero,        //开始的位置
        end: Offset = Offset.Infinite,        //结束的位置
        tileMode: TileMode = TileMode.Clamp        //重复模式
): Brush

水平渐变和垂直渐变底层就是调用的线性渐变。

horizontalGradient

水平方向渐变

fun horizontalGradient(
        colors: List<Color>,
        startX: Float = 0.0f,
        endX: Float = Float.POSITIVE_INFINITY,
        tileMode: TileMode = TileMode.Clamp
): Brush

verticalGradient

垂直方向渐变

fun verticalGradient(
        colors: List<Color>,
        startY: Float = 0.0f,
        endY: Float = Float.POSITIVE_INFINITY,
        tileMode: TileMode = TileMode.Clamp
): Brush

radialGradient

放射渐变

fun radialGradient(
        colors: List<Color>,
        center: Offset = Offset.Unspecified,        //中心位置
        radius: Float = Float.POSITIVE_INFINITY,        //半径
        tileMode: TileMode = TileMode.Clamp
): Brush

sweepGradient

扫描渐变

fun sweepGradient(
        colors: List<Color>,
        center: Offset = Offset.Unspecified
): Brush

4.2.1 使用 colorStop 更改颜色分布

自定义颜色在渐变中的显示方式,可以调整每种颜色的 colorStop 值,0 ~ 1 之间的小数。

val colorStops = arrayOf(
    0.0f to Color.Yellow,
    0.2f to Color.Red,
    1f to Color.Blue
)
Box(modifier = Modifier
        .requiredSize(200.dp)
        .background(Brush.horizontalGradient(colorStops = colorStops))
)

 4.2.2 使用 TileMode 让图案重复显示

当未指定 Brush 的开始位置 start 和结束位置 end 时,默认会填满整个区域,只有在区域 > Brush 时 TileMode 才会在渐变中平铺。以下举例 HorizontalGradient 的效果。

TileMode.Repeated将区域剩余空间绘制为重复的顺序颜色。
TileMode.Mirror将区域剩余空间绘制为重复的反转颜色。
TileMode.Clamp将区域剩余空间绘制为结束颜色。
TileMode.Decal将区域剩余空间绘制为透明色。(仅适用于 API 31 及更高版本。可使用 TileMode.isSupported() 确定设备是否支持 TileMode。如果使用了不受支持的 TileMode,系统会应用默认的 TileMode.Clamp。)

 4.2.3 更改 Brush 大小

当知道绘制区域大小时(如在 DrawScope 中通过 size 获取)可以按照 TileMode 方式平铺,在不知道的情况下(如将 Brush 分配给文字)可以扩展 Shader 重写 createShader() 函数利用绘制区域大小 size 形参。对于 radialGradient 如果未指定中心位置 center 和半径radius,渐变将占据整个 DrawScope 但是是以宽高较小的那边为直径,此时自定义大小会获得更好的效果(发散到屏幕外边去)。

val listColors = listOf(Color.Yellow, Color.Red, Color.Blue)
val customBrush = remember {
    object : ShaderBrush() {
        override fun createShader(size: Size): Shader {
            return LinearGradientShader(
                colors = listColors,
                from = Offset.Zero,
                to = Offset(size.width / 4f, 0f),
                tileMode = TileMode.Mirror
            )
        }
    }
}
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(customBrush)
)

4.2.4 使用图片作为 Brush

如需使用 ImageBitmap 作为 Brush,请以 ImageBitmap 的形式加载相应图片,然后创建 ImageShader Brush。可以应用于一下几种类型的绘制:背景、文字、画布。

val imageBrush =
    ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.dog)))

//用于 background
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(imageBrush)
)

//用于 TextStyle
Text(
    text = "Hello Android!",
    style = TextStyle(
        brush = imageBrush,
        fontWeight = FontWeight.ExtraBold,
        fontSize = 36.sp
    )
)

//用于 DrawScope#drawCircle()
Canvas(onDraw = {
    drawCircle(imageBrush)
}, modifier = Modifier.size(200.dp))

五、BlendMode 混合模式

混合是有个原图像src,一个dst目标图像,这两个图的相交区域的各种组合情况。

Clear删除源图像和目标图像,不留下任何内容
Src删除目标图像,只绘制源图像
Dst删除源图像,只绘制目标图像
SrcOver源图和目标图合成,源图在上
DstOver目标图和源图合成,目标图在上
SrcIn显示源图和目标图相交的部分,并且只显示源图像
DstIn显示目标图和源图相交的部分,并且只显示目标图
SrcOut显示源图像和目标图不相交的部分,并且只显示源图
DstOut显示目标图和源图像不相交的部分,并且只显示目标图
SrcAtop显示目标图,并且在相交的地方显示源图
DstAtop显示源图,并在相交的地方显示目标图
Xor显示源图和目标图,但相交的位置不显示(代码注释:对源图像和目标图像应用按位异或运算符。这就使得它们重叠的地方保持透明。)
Plus对源映像和目标映像的组件求和。其中一个图像的像素中的透明度降低了该图像对相应输出像素的贡献,就好像该图像中该像素的颜色较暗一样
Modulate将源图像和目标图像的颜色分量相乘。这只能产生相同或较深的颜色(乘以白色,1.0,结果不变;乘以黑色(0.0,结果为黑色)。合成两个不透明图像时,这与在投影仪上重叠两个透明胶片的效果类似。对于同样乘以alpha通道的变量,请考虑乘以。
Screen

将源图像和目标图像的分量的逆相乘,然后求逆结果。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。这基本上与调制混合模式相同,但是在乘法之前颜色值反转,结果在渲染之前反转回来。这只能产生相同或较浅的颜色(乘以黑色,1.0,结果不变;乘以白色(0.0,结果为白色)。类似地,在alpha通道中,它只能产生更不透明的颜色。这与两台投影仪同时在同一屏幕上显示图像的效果相似。

Overlay

将源图像和目标图像的分量相乘,然后调整它们以支持目标。具体来说,如果目标值较小,则将其与源值相乘,而如果源值较小,则将源值的倒数与目标值的倒数相乘,然后反转结果。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。

Darken通过从每个颜色通道中选择最低值来合成源图像和目标图像。输出图像的不透明度的计算方法与SrcOver相同。
Lighten通过从每个颜色通道中选择最高值来合成源图像和目标图像。输出图像的不透明度的计算方法与SrcOver相同。
ColorDodge将目标除以源的倒数。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。注意这个BlendMode只能在androidapi级别29及以上使用。
ColorBurn将目标的倒数除以源的倒数,然后求结果的倒数。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。注意这个BlendMode只能在androidapi级别29及以上使用。
Hardlight

将源图像和目标图像的分量相乘,然后调整它们以有利于源图像。具体来说,如果源值较小,则将其与目标值相乘,而如果目标值较小,则将目标值的倒数与源值的倒数相乘,然后反转结果。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。注意这个BlendMode只能在androidapi级别29及以上使用。

Softlight对于低于0.5的源值,使用ColorDodge;对于高于0.5的源值,使用ColorBurn。这会产生类似的效果,但比叠加效果更柔和。注意这个BlendMode只能在androidapi级别29及以上使用。
Difference从每个通道的较大值中减去较小的值。合成黑没有效果;合成白色将反转其他图像的颜色。输出图像的不透明度的计算方法与SrcOver相同。注意这个BlendMode只能在androidapi级别29及以上使用这种影响类似于排斥,但更为严厉。
Exclusion从两个图像的总和中减去两个图像乘积的两倍。合成黑没有效果;合成白色将反转其他图像的颜色。输出图像的不透明度的计算方法与SrcOver相同。注意这个BlendMode只能在androidapi级别29及以上使用效果类似于差异,但更柔和。
Multiply

将源图像和目标图像的分量相乘,包括alpha通道。这只能产生相同或较深的颜色(乘以白色,1.0,结果不变;乘以黑色(0.0,结果为黑色)。由于alpha通道也会相乘,因此一个图像中的完全透明像素(不透明度0.0)会导致输出中的完全透明像素。这与DstIn类似,但颜色组合在一起。

Hue获取源图像的色调,以及目标图像的饱和度和亮度。其效果是用源图像着色目标图像。输出图像的不透明度的计算方法与SrcOver相同。在源图像中完全透明的区域从目标图像获取其色调。注意这个BlendMode只能在androidapi级别29及以上使用。
Saturation获取源图像的饱和度,以及目标图像的色调和亮度。输出图像的不透明度的计算方法与SrcOver相同。在源图像中完全透明的区域从目标图像获取其饱和度。注意这个BlendMode只能在androidapi级别29及以上使用。
Color获取源图像的色调和饱和度,以及目标图像的亮度。其效果是用源图像着色目标图像。输出图像的不透明度的计算方法与SrcOver相同。源图像中完全透明的区域从目标处获取其色调和饱和度。注意这个BlendMode只能在androidapi级别29及以上使用。
Luminosity获取源图像的亮度,以及目标图像的色调和饱和度。输出图像的不透明度的计算方法与SrcOver相同。在源图像中完全透明的区域从目标图像获取其亮度。注意这个BlendMode只能在androidapi级别29及以上使用。

六、画布 DrawScope

官方页面

绘制都是在 DrawScope 中发生的,可以理解为画布,提供了绘制所需的上下文信息(如size、layoutDirection等属性),封装了绘制API。

属性说明
size可以拿到可以拿画布的宽高(size.width、size.height)
center可以拿到画布中心点坐标
drawContext存储了以下信息:绘制尺寸size、封装的canvas、用来旋转缩放移动的transform,而通过 canvas.nativeCanvas 就能获取具体平台的实现,即可以调用 Android 原生的 Canvas 来实现更多需求。
行为inset 将DrawScope坐标空间平移
translate 平移左上角,整体比例不变

rotate(旋转坐标)讲的是旋转了多少角度

rotateRad(旋转坐标)讲的是旋转了多少弧度

scale 缩放坐标
clipRect 裁剪矩形区域,绘制在裁剪好的矩形区域内。ClipOp.Difference从当前剪辑中减去提供的矩形。
clipPath 裁剪路径
drawIntoCanvas 直接提供底层画布
withTransform 组合转换

6.1 绘制基本形状

6.1.1 画线 drawLine()

fun drawLine(
        color: Color,
        start: Offset,        //起点偏移
        end: Offset,        //终点偏移
        strokeWidth: Float = Stroke.HairlineWidth,        //描边宽度(线宽)
        cap: StrokeCap = Stroke.DefaultCap,        //线两头形状
        pathEffect: PathEffect? = null,        //效果
        alpha: Float = 1.0f,        //范围0~1
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

cap

线条两头的形状

StrokeCap.Butt 平的(默认)
StrokeCap.Square 也是平的但是长一截
StrokeCap.Round 圆的

pathEffect

线条效果

PathEffect.cornerPathEffect(radius: Float)

将线段之间的锐角替换为指定半径的圆角 radius是半径

PathEffect.dashPathEffect(intervals: FloatArray, phase: Float = 0f)

将形状绘制为具有给定间隔的一系列破折号。比如虚线 例如interval={20,5},第一个参数表示虚线的长度是20,5是虚线之间的间隔是5. phase 偏移

PathEffect.chainPathEffect(outer: PathEffect, inner: PathEffect)

创建一个PathEffect,将内部效果应用于路径,然后应用外部效果

PathEffect.stampedPathEffect(shape: Path, advance: Float, phase: Float,style: StampedPathEffectStyle)

用path表示的指定形状冲压绘制的路径.  shape要踩踏的路径,advance 每个冲压形状之间的前进间距, phase 在压印第一个形状之前要偏移的相位量, style如何在每个位置转换形状,因为它是冲压. style有三种取值 StampedPathEffectStyle.Translate 平移 ,StampedPathEffectStyle.Rotate 旋转,StampedPathEffectStyle.Morph 变形

6.1.2 画矩形 drawRect()

fun drawRect(
        color: Color,
        topLeft: Offset = Offset.Zero,        //左上角偏移
        size: Size = this.size.offsetSize(topLeft),
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.3 画圆角矩形 drawRoundRect()

fun drawRoundRect(
        color: Color,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        cornerRadius: CornerRadius = CornerRadius.Zero,        //圆角半径
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.4 画圆形 drawCircle()

fun drawCircle(
        color: Color,
        radius: Float = size.minDimension / 2.0f,        //半径
        center: Offset = this.center,        //圆心偏移
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.5 画椭圆形 drawOval()

fun drawOval(
        color: Color,
        topLeft: Offset = Offset.Zero,        //左上角偏移
        size: Size = this.size.offsetSize(topLeft),        //传入宽高 Size(200f, 100f)
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.6 画弧度跟扇形 drawArc()

fun drawArc(
        color: Color,
        startAngle: Float,        //起始点角度
        sweepAngle: Float,        //扫过的角度
        useCenter: Boolean,        //是否连接中心(true扇形、false弧形)
        topLeft: Offset = Offset.Zero,        //左上角偏移
        size: Size = this.size.offsetSize(topLeft),        //传入宽高 Size(200f, 100f)
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边(弧形填充就是半圆)
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.7 画点 drawPoints()

fun drawPoints(
        points: List<Offset>,        //点坐标的集合
        pointMode: PointMode,        //如何绘制点
        color: Color,
        strokeWidth: Float = Stroke.HairlineWidth,        //描边宽度(线宽)
        cap: StrokeCap = StrokeCap.Butt,        //线两头形状
        pathEffect: PathEffect? = null,
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

PointMode

如何绘制点

PointMode.Points 分别画点
PointMode.Lines 画线(点集合两两组合划线,奇数的话最后一个不管)
PointMode.Polygon 画多边形(最后一个点偏移要跟第一个一样才闭口,否则只是连接各点)

6.2 绘制路径

6.2.1 drawPath()

fun drawPath(
        path: Path,
        color: Color,
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边(弧形填充就是半圆)
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.3 绘制图片

6.3.1 drawImage()

fun drawImage(
        image: ImageBitmap,        //源图像
        topLeft: Offset = Offset.Zero,        //左上角偏移
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
fun drawImage(
        image: ImageBitmap,
        srcOffset: IntOffset = IntOffset.Zero,        //源图像的偏移
        srcSize: IntSize = IntSize(image.width, image.height),        //源图像大小
        dstOffset: IntOffset = IntOffset.Zero,        //绘制图像的偏移
        dstSize: IntSize = srcSize,        //绘制图像的大小
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.4 绘制文本

6.5 用原生Canvas绘制 drawIntoCanvas()

可以将已有的 View 项目中的绘制逻辑搬过来用。

inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit)
drawIntoCanvas { canvas ->
    val paint = Paint()
    paint.color = Color.Red
    paint.strokeWidth = 10f
    canvas.drawLine(p1= Offset(50f,50f),p2= Offset(200f,200f),paint = paint)
}

6.6 转换

6.6.1 平移 translate()、inset()

inline fun DrawScope.translate(
    left: Float = 0.0f,
    top: Float = 0.0f,
    block: DrawScope.() -> Unit
) {
    drawContext.transform.translate(left, top)
    block()
    drawContext.transform.translate(-left, -top)
}

@Composable
fun Demo2() {
    Canvas(
        modifier = Modifier.size(200.dp).background(Color.Blue)
    ) {
        val halfSize = size / 2F
        //正常画
        drawRect(
            color = Color.Green,
            size = halfSize
        )
        //平移后画
        translate(left = 20F, top = 20F) {
            drawRect(
                color = Color.Red,
                size = halfSize
            )
        }
    }
}

6.6.2 缩放 scale()

inline fun DrawScope.scale(
    scale: Float,        //统一设置横向和纵向缩放大小
    pivot: Offset = center,
    block: DrawScope.() -> Unit
)

inline fun DrawScope.scale(
    scaleX: Float,        //横向缩放大小
    scaleY: Float,        //纵向缩放大小
    pivot: Offset = center,        //中心点偏移
    block: DrawScope.() -> Unit
)

6.6.3 旋转 rotate()、rotateRad()

inline fun DrawScope.rotate(
    degrees: Float,        //顺时针旋转的度数
    pivot: Offset = center,        //中心点偏移
    block: DrawScope.() -> Unit
)
inline fun DrawScope.rotateRad(
    radians: Float,        //顺时针旋转的弧度
    pivot: Offset = center,                //中心点偏移
    block: DrawScope.() -> Unit
)

6.6.4 裁剪 clipRect()、clipPath()

inline fun DrawScope.clipRect(
    left: Float = 0.0f,
    top: Float = 0.0f,
    right: Float = size.width,
    bottom: Float = size.height,
    clipOp: ClipOp = ClipOp.Intersect,
    block: DrawScope.() -> Unit
)
inline fun DrawScope.clipPath(
    path: Path,
    clipOp: ClipOp = ClipOp.Intersect,
    block: DrawScope.() -> Unit
)

6.7 组合多种转换

6.7.1 withTransform()

inline fun DrawScope.withTransform(
    transformBlock: DrawTransform.() -> Unit,
    drawBlock: DrawScope.() -> Unit
)

DrawTransform

作用域中依然可以调用 size 和 center

平移

fun translate(left: Float = 0.0f, top: Float = 0.0f)

fun inset(left: Float, top: Float, right: Float, bottom: Float)

缩放fun scale(scaleX: Float, scaleY: Float, pivot: Offset = center)
旋转fun rotate(degrees: Float, pivot: Offset = center)
裁剪

fun clipRect(
        left: Float = 0.0f,
        top: Float = 0.0f,
        right: Float = size.width,
        bottom: Float = size.height,
        clipOp: ClipOp = ClipOp.Intersect
)

fun clipPath(path: Path, clipOp: ClipOp = ClipOp.Intersect)

### 如何在Jetpack Compose中为RadioButton实现自定义样式 在Jetpack Compose中,`RadioButton`组件默认遵循Material Design的设计原则。然而,开发者可以通过覆盖颜色属性或其他视觉特性来创建自定义样式的单选按钮。以下是通过修改颜色和其他参数来自定义`RadioButton`外观的具体方法。 #### 自定义RadioButton的颜色 可以使用`colors`参数来自定义`RadioButton`的不同状态下的颜色。此参数接受一个`RadioButtonColors`对象,该对象可通过`radioButtonColors()`函数构建[^1]: ```kotlin import androidx.compose.material.RadioButton import androidx.compose.material.RadioButtonDefaults import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.foundation.layout.Column import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier var selectedOption by remember { mutableStateOf(false) } Column { RadioButton( selected = selectedOption, onClick = { selectedOption = !selectedOption }, colors = RadioButtonDefaults.colors( // 定义不同状态下的颜色 unselectedColor = Color.Gray, // 未选中的颜色 selectedColor = Color.Blue, // 已选中的颜色 disabledColor = Color.LightGray // 禁用时的颜色 ), modifier = Modifier ) } ``` 以上代码展示了如何设置不同的颜色以匹配特定的应用主题或设计需求[^1]。 #### 使用自定义图标绘制RadioButton 如果需要完全替换默认的圆形单选按钮形状,则可以通过组合其他Composable函数(如`Canvas`)手动绘制图形并模拟其行为。下面是一个简单的例子,展示如何利用`Box`和条件渲染逻辑替代标准控件: ```kotlin import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.size import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.unit.dp @Composable fun CustomRadioButton(selected: Boolean, onSelect: () -> Unit){ val interactionSource = remember { MutableInteractionSource() } Box(contentAlignment = Alignment.Center, modifier = Modifier .size(24.dp) .clip(MaterialTheme.shapes.small) .background(if (selected) MaterialTheme.colors.primary else Color.Transparent) .clickable(interactionSource = interactionSource, indication = null){onSelect()} ){ Canvas(modifier=Modifier.size(18.dp)){ drawCircle(color = if (!selected) Color.Black.copy(alpha =0.5f) else Color.White ,radius = this.size.minDimension /3f) } } } // Usage Example: CustomRadioButton(selected = true, onSelect={}) ``` 这段代码提供了一个高度可配置的方式来重新定义单选按钮的表现形式,允许更精细控制绘图细节以及交互反馈效果。 #### 总结 无论是简单调整现有组件的主题配色方案还是深入到像素级定制,Jetpack Compose都提供了灵活而强大的工具集支持开发人员打造独一无二用户体验的同时保持一致性与高效性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值