[Jetpack Compose] Improving RoundCornerShape appearance in narrow widths | by ZhangKe
Recently, while working with rounded rectangles, I encountered an issue. When the width of the widget is too small, smaller than the diameter of the rounded corner, Compose applies some special handling, scaling down the corner radius proportionally, so it still appears as a rounded rectangle, as shown in the image below.
In some cases, such as the scenario mentioned above, this approach may not be ideal. Instead, we would prefer to keep the corner radius constant, as shown here.
The content of this article is about how to achieve the above effect.
Initially, I intended to draw directly using the Canvas
, but I realized that this approach isn’t versatile enough. In Compose, shapes are typically described using the Shape
class, and the scenario above also falls into the category of rounded rectangle shapes. If I can define a Shape
that can address the aforementioned situation, it can be applied in many places.
Compose provides a Shape
interface, which includes a single method. When we create a custom Shape
, our primary task is to implement this method.
@Immutable
interface Shape {
/**
* Creates [Outline] of this shape for the given [size].
*
* @param size the size of the shape boundary.
* @param layoutDirection the current layout direction.
* @param density the current density of the screen.
*
* @return [Outline] of this shape for the given [size].
*/
fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline
}
The input parameters represent the current widget’s information, where size
is the widget’s dimensions, and the return value is an Outline
.
When creating an Outline
, we can refer to the existing RoundedCornerShape
.
override fun createOutline(
size: Size,
topStart: Float,
topEnd: Float,
bottomEnd: Float,
bottomStart: Float,
layoutDirection: LayoutDirection
) = if (topStart + topEnd + bottomEnd + bottomStart == 0.0f) {
Outline.Rectangle(size.toRect())
} else {
Outline.Rounded(
RoundRect(
rect = size.toRect(),
topLeft = CornerRadius(if (layoutDirection == Ltr) topStart else topEnd),
topRight = CornerRadius(if (layoutDirection == Ltr) topEnd else topStart),
bottomRight = CornerRadius(if (layoutDirection == Ltr) bottomEnd else bottomStart),
bottomLeft = CornerRadius(if (layoutDirection == Ltr) bottomStart else bottomEnd)
)
)
}
We need to handle the special case of RoundedCornerShape
, that is, when the width is smaller than the diameter of the corner radius. Based on this, we need to add a conditional branch to handle this scenario.
if (topStart + topEnd + bottomEnd + bottomStart == 0.0f) {
Outline.Rectangle(size.toRect())
} else if (topStart == bottomStart && size.width < (topStart * 2F)) {
...
} else {
Outline.Rounded(
RoundRect(
rect = size.toRect(),
topLeft = CornerRadius(if (layoutDirection == Ltr) topStart else topEnd),
topRight = CornerRadius(if (layoutDirection == Ltr) topEnd else topStart),
bottomRight = CornerRadius(if (layoutDirection == Ltr) bottomEnd else bottomStart),
bottomLeft = CornerRadius(if (layoutDirection == Ltr) bottomStart else bottomEnd)
)
)
}
For the sake of simplicity, we will only handle the case where topState
is equal to bottomStart
.
The newly added if
branch represents the scenario we want to address.
This scenario includes two sub-cases:
- The widget’s height is less than or equal to the corner diameter. In this situation, there is only one semi-circle on the left side.
2. The widget’s height is greater than the corner diameter. In this case, the left side consists of two corner arcs and a connecting straight line.
To implement these two sub-cases, we can build different Paths.
val radius = topStart
val path = Path()
if (height > radius * 2) {
buildSlickRoundCornerPath(path, size, radius)
} else {
buildSingleArcPath(path, size, radius)
}
Outline.Generic(path)
The first case is relatively easy to handle.
private fun buildSingleArcPath(path: Path, size: Size, radius: Float) {
path.moveTo(size.width, 0F)
path.arcTo(
rect = Rect(0F, 0F, radius * 2F, radius * 2F),
startAngleDegrees = 90F,
sweepAngleDegrees = 180F,
forceMoveTo = true,
)
path.close()
}
The logic is quite simple. First, move to the top-right corner of the widget, then draw a circular arc with the radius equal to the corner radius, and finally close the Path
.
Now let’s take a look at the second case.
The major challenge here is adjusting the height of the widget’s drawing area dynamically based on its width.
In the illustration, the yellow line segment represents the width of the widget. In this case, we need to calculate the length of the red line, and then draw the circular arc for that portion.
val arcHeight = sqrt(radius * radius - (radius - width) * (radius - width))
The distance from the circular arc to the top and bottom of the widget can be calculated as:
val yOffset = radius - arcHeight
Thus, the upper half of the circular arc can be drawn as follows:
path.arcTo(
rect = Rect(
left = 0F,
top = yOffset,
right = width * 2F,
bottom = yOffset + arcHeight * 2F,
),
startAngleDegrees = 180F,
sweepAngleDegrees = 90F,
forceMoveTo = true,
)
Next, we move the Path
to the bottom of the lower circular arc:
val bottomArcBottom = height - yOffset
path.lineTo(x = width, y = bottomArcBottom)
path.arcTo(
rect = Rect(
left = 0F,
top = bottomArcBottom - arcHeight * 2,
right = width * 2F,
bottom = bottomArcBottom,
),
startAngleDegrees = 90F,
sweepAngleDegrees = 90F,
forceMoveTo = true,
)
Since the upper and lower arcs have the same size and similar parameters, there is no need for a detailed explanation.
Finally, close the Path
.
path.lineTo(0F, yOffset + arcHeight)
path.close()
Alright, that’s all for this article. The implementation is relatively straightforward overall, but there are a few small details to pay attention to. Therefore, I decided to share it directly so that anyone encountering this issue can use it as a reference.
Since Compose was developed later, the ecosystem isn’t fully mature yet. When dealing with complex requirements, many small issues like this may arise. Things that could be quickly implemented using Views might require more time in Compose. Thus, it’s up to us developers to gradually improve and contribute to the Compose community.
Click here to view the complete code.
Related Posts
Leave a Reply Cancel reply
Categories
- ! Без рубрики (1)
- ++PU (1)
- 1 (1)
- 1w (1)
- 1win Brazil (1)
- 1win India (1)
- 1WIN Official In Russia (1)
- 1win Turkiye (1)
- 1xbet egypt (1)
- 2ankarafayansustasi.net_may (1)
- ankarafayansustasi.netsiteai apr (1)
- Artificial intelligence (1)
- Arts & Entertainment, Photography (1)
- belugasitesi_mAY (1)
- BH_TOPsitesi apr (1)
- BHsitesy_may (2)
- Blog (3)
- Bookkeeping (14)
- Bootcamp de programação (2)
- Bootcamp de programación (2)
- BT_TOPsitesi apr (1)
- casino (5)
- casinom-hub (1)
- casinom-hub.comsitesi apr (3)
- colombian mail order brides (1)
- Cryptocurrency exchange (2)
- Dinamobet_next (1)
- Disease & Illness, Colon Cancer (1)
- Dumanbet (1)
- Dumanbet_next (1)
- Finance, Insurance (1)
- FinTech (5)
- Forex Trading (11)
- Galabet (1)
- Health & Fitness, Fitness Equipment (1)
- Hitbet (1)
- Home & Family, Crafts (1)
- Home & Family, Gardening (1)
- Internet Business, Audio-Video Streaming (1)
- Internet Business, Ecommerce (1)
- Internet Business, Email Marketing (1)
- Internet Business, Internet Marketing (1)
- IT Вакансії (1)
- IT Образование (5)
- IT Освіта (1)
- latin women dating (1)
- mail order bride (1)
- Mars bahis (2)
- Matadorbet (1)
- minimiri.comsitesi apr (3)
- Mobile App Development (771)
- Mostbet Russia (1)
- New Post (1)
- News (1)
- PB_TOPsitesi apr (1)
- PBsitesi_may (1)
- Pusulabet (1)
- redmirepool.bizsitesi apr (2)
- redmirepoolsitesi_may (1)
- Reference & Education, College (1)
- Reference & Education, Sociology (1)
- Rokusitesi apr (1)
- Sober living (6)
- Society, Divorce (1)
- Software development (7)
- Superbetin (1)
- Tempobet_next (1)
- thelongeststride.comsitesi apr (1)
- tipobet-turkiyesitesi apr (1)
- Ultrabet (1)
- Uncategorized (1)
- Игра (2)
- казино (1)
- Криптовалюты (1)
- Новости Криптовалют (1)
- Финтех (7)
- Форекс Брокеры (9)
- Форекс обучение (2)