Android Compose - 如何像GradientDrawable一样创建带有角度的自定义线性渐变

6

如何使用角度参数自定义Brush.linearGradient(),例如0、45、90、135等任意角度?

谢谢。

3个回答

6

任意角度的线性渐变看起来很不错,但需要做太多工作...

而设置角度为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
        )

从默认参数值(start,end)我们可以得出缺省角度为 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)
... 
``

2
你的回答没有涵盖问题中的“或任何角度”部分。 - Emad Razavi
@EmadRazavi 我已经制作了一个通用的解决方案,支持任何角度,并撰写了一篇中等难度的文章介绍它(感谢第一个解决方案提供的思路)。如果需要,请查看 https://dev59.com/W8Dqa4cB1Zd3GeqPkL3J#71577996 - Mukhtar Bimurat

4

实现顺时针45度间隔的配额

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
}

使用方法

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

  val redGreenGradient = Brush.linearGradient(
                colors = listOf(Color.Red, Color.Green, Color.Blue),
                start = gradientOffset.start,
                end = gradientOffset.end
            )

完整演示

使用枚举类定义顺时针旋转,获取 Brush.linearGradientGradientOffset,返回起始结束的偏移量

@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)
    }
}

顺便提一下,刷子线性渐变的初始旋转角度为逆时针315度,或顺时针45度。

结果,上面的是默认的渐变,下面的是通过获取开始和结束偏移量应用旋转函数后的渐变。

输入图像描述

要旋转任意角度,需要创建一个函数来获取您的Composable的宽度和高度,并进行二维旋转。

xNew = x*cos(angle) - y*sin(angle)
yNew = x*sin(angle) + y*cos(angle)

但不确定如何将其翻译回轴,以使旋转45度的函数具有相同的值。


2
我已经制作了一个通用解决方案,支持任何角度,并写了一篇关于此的中文Medium文章(受第一个解决方案启发)。如有需要,请查看。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接