Android Compose - 具有类似 GradientDrawable 角度的自定义 linearGradient

Android Compose - custom linearGradient with angle like GradientDrawable

如何使用角度参数自定义 Brush.linearGradient(),例如 O、45、90、135...或任何其他角度?

谢谢。

为 LinearGradient 设置任何角度似乎是个好主意,但这需要做很多工作...

并且设置角度如0、45、90、135..都比较简单。用Brush.linearGradient(...)的方法,可以结合参数startend做成

第一次看到这个函数

        @Stable
        fun linearGradient(
            colors: List<Color>,
            start: Offset = Offset.Zero,
            end: Offset = Offset.Infinite,
            tileMode: TileMode = TileMode.Clamp
        )

从默认参数值(开始、结束),我们可以得出默认角度为 135。如何?

start = Offset.Zero == Offset(0f, Float.POSITIVE_INFINITY)
end = Offset.Infinite == Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)


在直角坐标中从头到尾,可以看到方向是正下方,所以角度是135°,所以,可以得出结论

angle 0
start = Offset(0f,0f)
end = Offset(Float.INFINITY,0f)

angle 45 
start = Offset(0f, Float.POSITIVE_INFINITY)
end = Offset(Float.POSITIVE_INFINITY, 0f)

angle 90 
start = Offset(0f, Float.POSITIVE_INFINITY)
end = Offset(0f,0f)
... 
``

实施间隔 45 度的配给 counter-clockwise

fun GradientOffset(angle: GradientAngle): GradientOffset {
    return when (angle) {
        GradientAngle.CW45 -> GradientOffset(
            start = Offset.Zero,
            end = Offset.Infinite
        )
        GradientAngle.CW90 -> GradientOffset(
            start = Offset.Zero,
            end = Offset(0f, Float.POSITIVE_INFINITY)
        )
        GradientAngle.CW135 -> GradientOffset(
            start = Offset(Float.POSITIVE_INFINITY, 0f),
            end = Offset(0f, Float.POSITIVE_INFINITY)
        )
        GradientAngle.CW180 -> GradientOffset(
            start = Offset(Float.POSITIVE_INFINITY, 0f),
            end = Offset.Zero,
        )
        GradientAngle.CW225 -> GradientOffset(
            start = Offset.Infinite,
            end = Offset.Zero
        )
        GradientAngle.CW270 -> GradientOffset(
            start = Offset(0f, Float.POSITIVE_INFINITY),
            end = Offset.Zero
        )
        GradientAngle.CW315 -> GradientOffset(
            start = Offset(0f, Float.POSITIVE_INFINITY),
            end = Offset(Float.POSITIVE_INFINITY, 0f)
        )
        else -> GradientOffset(
            start = Offset.Zero,
            end = Offset(Float.POSITIVE_INFINITY, 0f)
        )
    }
}

/**
 * Offset for [Brush.linearGradient] to rotate gradient depending on [start] and [end] offsets.
 */
data class GradientOffset(val start: Offset, val end: Offset)

enum class GradientAngle {
    CW0, CW45, CW90, CW135, CW180, CW225, CW270, CW315
}

使用枚举 class 定义 counter-clockwise 旋转,得到 GradientOffset which returns start and end Brush.linearGradient

的偏移量
@Composable
fun GradientRotationDemo() {

    val canvasSize = 300.dp
    val canvasModifier = Modifier
        .size(canvasSize)


    // Offsets for gradients based on selected angle
    var gradientOffset by remember {
        mutableStateOf(GradientOffset(GradientAngle.CW0))
    }

    var angleSelection by remember { mutableStateOf(0f) }
    var angleText by remember { mutableStateOf("0 Degrees") }


    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(backgroundColor)
            .padding(8.dp)
    ) {

        Text(
            text = angleText,
            color = Blue400,
            modifier = Modifier
                .padding(8.dp),
            fontSize = 18.sp,
            fontWeight = FontWeight.Bold
        )

        Slider(
            modifier = Modifier.height(50.dp),
            value = angleSelection,
            onValueChange = {
                angleSelection = it
   
                gradientOffset = when (angleSelection.roundToInt()) {
                    0 -> {
                        angleText = "0 Degrees"
                        GradientOffset(GradientAngle.CW0)
                    }
                    1 -> {
                        angleText = "45 Degrees"
                        GradientOffset(GradientAngle.CW45)
                    }
                    2 -> {
                        angleText = "90 Degrees"
                        GradientOffset(GradientAngle.CW90)
                    }
                    3 -> {
                        angleText = "135 Degrees"
                        GradientOffset(GradientAngle.CW135)
                    }
                    4 -> {
                        angleText = "180 Degrees"
                        GradientOffset(GradientAngle.CW180)
                    }

                    5 -> {
                        angleText = "225 Degrees"
                        GradientOffset(GradientAngle.CW225)
                    }
                    6 -> {
                        angleText = "270 Degrees"
                        GradientOffset(GradientAngle.CW270)
                    }
                    else -> {
                        angleText = "315 Degrees"
                        GradientOffset(GradientAngle.CW315)
                    }

                }
            },
            steps = 6,
            valueRange = 0f..7f
        )

        Column(
            Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState()),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {


            CanvasWithTitle(
                modifier = canvasModifier,
                text = "Gradient"
            ) {
                val redGreenGradient = Brush.linearGradient(
                    colors = listOf(Color.Red, Color.Green, Color.Blue),
                )
                drawRect(redGreenGradient)
            }

            CanvasWithTitle(
                modifier = canvasModifier,
                text = "Gradient Angle"
            ) {
                val redGreenGradient = Brush.linearGradient(
                    colors = listOf(Color.Red, Color.Green, Color.Blue),
                    start = gradientOffset.start,
                    end = gradientOffset.end
                )
                drawRect(redGreenGradient)
            }
        }
    }
}

CanvasWithTitle 没什么特别的,但为了轻松测试它,我也添加了它

private fun CanvasWithTitle(
    modifier: Modifier = Modifier,
    text: String,
    onDraw: DrawScope.() -> Unit
) {
    Column(
        modifier = Modifier
            .wrapContentWidth()
    ) {

        Text(
            text = text,
            color = Blue400,
            modifier = Modifier
                .padding(8.dp),
            fontSize = 18.sp,
            fontWeight = FontWeight.Bold
        )

        Canvas(modifier = modifier, onDraw = onDraw)
    }
}

顺便说一下Brush.linearGradient的初始旋转角度是315度counter-clockwise,也就是顺时针45度

结果,上面的是默认渐变,下面的是通过获取开始和结束偏移应用旋转功能的

要以任意角度旋转,您需要创建一个函数来获取 Composable 的宽度和高度,并使用

进行二维旋转
xNew = x*cos(angle) - y*sin(angle)
yNew = x*sin(angle) + y*cos(angle)

但不确定如何转换回轴以在旋转 45 度的函数中具有相同的值。

我做了一个支持任何角度的通用解决方案,并写了一篇关于它的medium article(感谢第一个解决方案的想法)。有需要的可以看看