How to custom Brush.linearGradient() with angle param, such as O, 45, 90, 135... or any angle other?
Thanks.
How to custom Brush.linearGradient() with angle param, such as O, 45, 90, 135... or any angle other?
Thanks.
Setting any angle for LinearGradient seems is a good idea, but that needs too much work to do...
And setting the angle such as 0, 45, 90, 135.. is relatively simple. With the method of Brush.linearGradient(...), you can combine the parameter start and end to make it.
First see this function
@Stable
fun linearGradient(
colors: List<Color>,
start: Offset = Offset.Zero,
end: Offset = Offset.Infinite,
tileMode: TileMode = TileMode.Clamp
)
from the default parameter values (start, end), we can conclude default angle is 135. and how?
start = Offset.Zero == Offset(0f, Float.POSITIVE_INFINITY)
end = Offset.Infinite == Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)
from start to end in the Cartesian coordinates, we can see the direction is right down, so the angle is 135. So, we can conclude
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)
...
``
I've made a universal solution that supports any angle, and wrote a medium article about it (thanks to the first solution for the idea). Check it out if necessary
Implementation of ration with 45 degree intervals 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
}
Usage
// 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
)
Full Demo
Using an enum class to define clockwise rotation, get GradientOffset which returns start and end offset for 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)
}
}
}
}
Nothing special about CanvasWithTitle but to test it with ease i add it too
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)
}
}
By the way initial rotation angle of Brush.linearGradient is 315 degrees counter-clockwise, or 45 degrees clockwise
Result, the one on top is default gradient, the one at the bottom is one that applied rotation function by getting start and end offsets
For rotating with any angle you need to create a function that gets width and height of your Composable and do 2d rotation with
xNew = x*cos(angle) - y*sin(angle)
yNew = x*sin(angle) + y*cos(angle)
but not sure about how to translate back to axis to have same values in the function that rotates by 45 degress.