Partial Border

Sometimes you want to draw a border around a Composable function, but only on one or two sides. This snippet provides a Modifier that allows you to specify which sides of the border should be drawn.

import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.Dp
/**
* Border definition can be extended to provide border style or [androidx.compose.ui.graphics.Brush]
* One more way is make it sealed class and provide different implementations:
* SolidBorder, DashedBorder etc
*/
data class Border(val strokeWidth: Dp, val color: Color)
@Stable
fun Modifier.border(
start: Border? = null,
top: Border? = null,
end: Border? = null,
bottom: Border? = null,
) =
drawBehind {
start?.let {
drawStartBorder(it, shareTop = top != null, shareBottom = bottom != null)
}
top?.let {
drawTopBorder(it, shareStart = start != null, shareEnd = end != null)
}
end?.let {
drawEndBorder(it, shareTop = top != null, shareBottom = bottom != null)
}
bottom?.let {
drawBottomBorder(border = it, shareStart = start != null, shareEnd = end != null)
}
}
private fun DrawScope.drawTopBorder(
border: Border,
shareStart: Boolean = true,
shareEnd: Boolean = true
) {
val strokeWidthPx = border.strokeWidth.toPx()
if (strokeWidthPx == 0f) return
drawPath(
Path().apply {
moveTo(0f, 0f)
lineTo(if (shareStart) strokeWidthPx else 0f, strokeWidthPx)
val width = size.width
lineTo(if (shareEnd) width - strokeWidthPx else width, strokeWidthPx)
lineTo(width, 0f)
close()
},
color = border.color
)
}
private fun DrawScope.drawBottomBorder(
border: Border,
shareStart: Boolean,
shareEnd: Boolean
) {
val strokeWidthPx = border.strokeWidth.toPx()
if (strokeWidthPx == 0f) return
drawPath(
Path().apply {
val width = size.width
val height = size.height
moveTo(0f, height)
lineTo(if (shareStart) strokeWidthPx else 0f, height - strokeWidthPx)
lineTo(if (shareEnd) width - strokeWidthPx else width, height - strokeWidthPx)
lineTo(width, height)
close()
},
color = border.color
)
}
private fun DrawScope.drawStartBorder(
border: Border,
shareTop: Boolean = true,
shareBottom: Boolean = true
) {
val strokeWidthPx = border.strokeWidth.toPx()
if (strokeWidthPx == 0f) return
drawPath(
Path().apply {
moveTo(0f, 0f)
lineTo(strokeWidthPx, if (shareTop) strokeWidthPx else 0f)
val height = size.height
lineTo(strokeWidthPx, if (shareBottom) height - strokeWidthPx else height)
lineTo(0f, height)
close()
},
color = border.color
)
}
private fun DrawScope.drawEndBorder(
border: Border,
shareTop: Boolean = true,
shareBottom: Boolean = true
) {
val strokeWidthPx = border.strokeWidth.toPx()
if (strokeWidthPx == 0f) return
drawPath(
Path().apply {
val width = size.width
val height = size.height
moveTo(width, 0f)
lineTo(width - strokeWidthPx, if (shareTop) strokeWidthPx else 0f)
lineTo(width - strokeWidthPx, if (shareBottom) height - strokeWidthPx else height)
lineTo(width, height)
close()
},
color = border.color
)
}
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Text
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.unit.dp
import androidx.ui.tooling.preview.Preview
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BordersDemo()
}
}
}
data class Borders(
val start: Border? = null,
val end: Border? = null,
val top: Border? = null,
val bottom: Border? = null
)
@Preview(showBackground = true)
@Composable
fun BordersDemo() {
Column(
horizontalGravity = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
val strokeWidth = 8.dp
val start = Border(strokeWidth, Color.Cyan)
val end = Border(strokeWidth, Color.Green)
val bottom = Border(strokeWidth, Color.Blue)
val top = Border(strokeWidth, Color.Red)
val samples = listOf(
Borders(bottom = bottom, end = end),
Borders(bottom = bottom, top = top),
Borders(start = start, end = end),
Borders(top = top, start = start, end = end),
Borders(bottom = bottom, top = top, start = start, end = end),
)
samples.forEach { borders ->
Text(
text = "Hello Android!",
modifier = Modifier
.border(
bottom = borders.bottom,
end = borders.end,
start = borders.start,
top = borders.top
)
.padding(strokeWidth + 8.dp)
)
Spacer(modifier = Modifier.height(24.dp))
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

Have a project you'd like to submit? Fill this form, will ya!

If you like this snippet, you might also like:

Maker OS is an all-in-one productivity system for developers

I built Maker OS to track, manage & organize my life. Now you can do it too!