fun Modifier.angledGradientBackground(colors: List<Color>, degrees: Float) = this.then(
drawBehind {
/*
Have to compute length of gradient vector so that it lies within
the visible rectangle.
--------------------------------------------
| length of gradient ^ / |
| ---> / / |
| / / <- rotation angle |
| / o --------------------| y
| / / |
| / / |
| v / |
--------------------------------------------
x
diagonal angle = atan2(y, x)
(it's hard to draw the diagonal)
Simply rotating the diagonal around the centre of the rectangle
will lead to points outside the rectangle area. Further, just
truncating the coordinate to be at the nearest edge of the
rectangle to the rotated point will distort the angle.
Let α be the desired gradient angle (in radians) and γ be the
angle of the diagonal of the rectangle.
The correct for the length of the gradient is given by:
x/|cos(α)| if -γ <= α <= γ, or π - γ <= α <= π + γ
y/|sin(α)| if γ <= α <= π - γ, or π + γ <= α <= 2π - γ
where γ ∈ (0, π/2) is the angle that the diagonal makes with
the base of the rectangle.
*/
val (x, y) = size
val gamma = atan2(y, x)
if (gamma == 0f || gamma == (PI / 2).toFloat()) {
// degenerate rectangle
return@drawBehind
}
val degreesNormalised = (degrees % 360).let { if (it < 0) it + 360 else it }
val alpha = (degreesNormalised * PI / 180).toFloat()
val gradientLength = when (alpha) {
// ray from centre cuts the right edge of the rectangle
in 0f..gamma, in (2*PI - gamma)..2*PI -> { x / cos(alpha) }
// ray from centre cuts the top edge of the rectangle
in gamma..(PI - gamma).toFloat() -> { y / sin(alpha) }
// ray from centre cuts the left edge of the rectangle
in (PI - gamma)..(PI + gamma) -> { x / -cos(alpha) }
// ray from centre cuts the bottom edge of the rectangle
in (PI + gamma)..(2*PI - gamma) -> { y / -sin(alpha) }
// default case (which shouldn't really happen)
else -> hypot(x, y)
}
val centerOffsetX = cos(alpha) * gradientLength / 2
val centerOffsetY = sin(alpha) * gradientLength / 2
drawRect(
brush = Brush.linearGradient(
colors = colors,
// negative here so that 0 degrees is left -> right
and 90 degrees is top -> bottom
start = Offset(center.x - centerOffsetX,center.y - centerOffsetY),
end = Offset(center.x + centerOffsetX, center.y + centerOffsetY)
),
size = size
)
}
)
/**
* 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
}
旋转函数
/**
*
* Get a [GradientOffset] that rotate a gradient clockwise with specified angle in degrees.
* Default value for [GradientOffset] is [GradientAngle.CW0] which is 0 degrees
* that returns a horizontal gradient.
*
* Get start and end offsets that are limited between [0f, Float.POSITIVE_INFINITY] in x and
* y axes wrapped in [GradientOffset].
* Infinity is converted to Composable width on x axis, height on y axis in shader.
*
* Default angle for [Brush.linearGradient] when no offset is 0 degrees in Compose ,
* [Brush.verticalGradient] is [Brush.linearGradient] with 90 degrees.
*
* ```
* 0 degrees
* start = Offset(0f,0f),
* end = Offset(Float.POSITIVE_INFINITY,0f)
*
* 45 degrees
* start = Offset(0f, Float.POSITIVE_INFINITY),
* end = Offset(Float.POSITIVE_INFINITY, 0f)
*
* 90 degrees
* start = Offset(0f, Float.POSITIVE_INFINITY),
* end = Offset.Zero
*
* 135 degrees
* start = Offset.Infinity,
* end = Offset.Zero
*
* 180 degrees
* start = Offset(Float.POSITIVE_INFINITY, 0f),
* end = Offset.Zero,
*
* ```
*/
fun GradientOffset(angle: GradientAngle = GradientAngle.CW0): 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)
)
}
}
使用方法非常简单,通过设置GradientAngle.CW顺时针旋转任何梯度
// Offsets for gradients based on selected angle
var gradientOffset by remember {
mutableStateOf(GradientOffset(GradientAngle.CW45))
}
Brush.linearGradient(
listOf(Color.Red, Color.Green, Color.Blue),
start = gradientOffset.start,
end = gradientOffset.end
)
7条答案
按热度按时间sg3maiej1#
您可以使用
Modifier.drawBehind()
并计算点的坐标来绘制渐变颜色。例如:
4xy9mtcn2#
可以使用参数
start
和end
来实现45度角。类似于:
njthzxwz3#
编辑2022-04-06
我意识到原始代码中有一个错误,这扭曲了梯度角。需要更多的三角函数,以便将渐变的开始和结束限制在画布区域内(如果需要的话),同时还保留渐变Angular 。这里是更新的解决方案,与奖金ASCII艺术。
旧答案
这是我基于@Ehan msz的代码的最终解决方案。我调整了他的解决方案,使0度对应于从左到右的梯度方向,90度对应于从上到下的方向。
cgh8pdjw4#
我创建了一个GradientOffset类,它可以让你将渐变旋转45度。
存储旋转Angular 的枚举和存储
Offset
s的数据类。旋转函数
使用方法非常简单,通过设置
GradientAngle.CW
顺时针旋转任何梯度结果
Repo link如果你想试试
注意事项
要使渐变可以旋转到任何Angular ,您需要实现自己的
LinearGradient
类,它扩展了ShaderBrush
,然后使用简单的三角函数计算旋转到位置。des4xlb05#
first解决方案有一个错误,因为偏移量也可以是负值(当您使用60度角检查并与CSS gradient进行比较时,您会注意到这一点)。
我已经做了一个通用的解决方案,支持任何Angular ,并写了一个medium article(感谢第一个解决方案的想法)。有必要的话可以查一下
guz6ccqo6#
需要使用偏移定义方向向量。对于任何Angular ,都需要明确指定最大绘图区域内的%,以确定矢量应指向的方向。
tcomlyy67#
修改了machfour变体,使渐变超出背景(这样就没有单色区域,如下例所示)。
> image