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(...)的方法,可以结合参数start
和end
做成
第一次看到这个函数
@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(感谢第一个解决方案的想法)。有需要的可以看看
如何使用角度参数自定义 Brush.linearGradient(),例如 O、45、90、135...或任何其他角度?
谢谢。
为 LinearGradient 设置任何角度似乎是个好主意,但这需要做很多工作...
并且设置角度如0、45、90、135..都比较简单。用Brush.linearGradient(...)的方法,可以结合参数start
和end
做成
第一次看到这个函数
@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(感谢第一个解决方案的想法)。有需要的可以看看