Welcome! Have you ever been intrigued by the idea of crafting a Seven-Segment View in Jetpack Compose? In this exploration, we’ll delve into this fascinating feature. While its practical utility might seem limited, the scope for creativity is boundless. Let’s dive in together and uncover the possibilities!
Section 1: Define support utilities.
SegmentsState
The SegmentsState
class manages the state of each segment for a given digit. True
indicates it’s illuminated for a segment, while false
denotes it’s not.
data class SegmentsState(
val a: Boolean = false,
val b: Boolean = false,
val c: Boolean = false,
val d: Boolean = false,
val e: Boolean = false,
val f: Boolean = false,
val g: Boolean = false
)
Additionally, to cover the state of all digits, we create a map linking each digit (0 to 9) to its respective SegmentsState.
Note: All the states in
SegmentsState
arefalse
by default, so we don’t need to include them here.
val SevenSegmentNumbers = mapOf(
0 to SegmentsState(a = true, b = true, c = true, d = true, e = true, f = true),
1 to SegmentsState(b = true, c = true),
2 to SegmentsState(a = true, b = true, d = true, e = true, g = true),
3 to SegmentsState(a = true, b = true, c = true, d = true, g = true),
4 to SegmentsState(b = true, c = true, f = true, g = true),
5 to SegmentsState(a = true, c = true, d = true, f = true, g = true),
6 to SegmentsState(a = true, c = true, d = true, e = true, f = true, g = true),
7 to SegmentsState(a = true, b = true, c = true),
8 to SegmentsState(a = true, b = true, c = true, d = true, e = true, f = true, g = true),
9 to SegmentsState(a = true, b = true, c = true, d = true, f = true, g = true),
)
Also, I’ve crafted an extension function to enhance code readability. When applied to an Int
object, this function retrieves the associated SegmentsState.
fun Int.getSegmentsState(): SegmentsState {
return SevenSegmentNumbers.getOrElse(this) {
throw IllegalArgumentException("The digit must be in the range from 0 to 9")
}
}
SegmentData
The SegmentData
class stores specific information about each segment, including its activation status, orientation (vertical or horizontal), and coordinates to draw—start and end offsets.
data class SegmentData(
val isActive: Boolean,
val isVertical: Boolean,
val startX: Float,
val endX: Float,
val startY: Float,
val endY: Float
)
Additionally, to manage segment spacing, I’ve created an extension function addSpacing()
for the SegmentData
class. This function adjusts the segment's start and end coordinates by a specified space, ensuring appropriate spacing between segments.
fun SegmentData.addSpacing(space: Float): SegmentData {
return when
isVertical -> this.copy(
startY = (this.startY + space),
endY = (this.endY - space)
)
else -> this.copy(
startX = (this.startX + space),
endX = (this.endX - space)
)
}
}
Other utility functions
The splitToDigits()
function efficiently divides a number into individual digits, returning them as a list of integers. This function proves invaluable when handling numeric input for segment display.
fun Int.splitToDigits(): List<Int> {
return this.toString().map(Char::digitToInt)
}
The padToStart()
function, an extension of the List class, ensures a list reaches a specified size by padding it with a designated value. Particularly useful when ensuring a consistent length for digit representation in the Seven-Segment View.
fun <T> List<T>.padToStart(size: Int, value: T): List<T> {
require(size >= 0) { "Size should not be negative" }
return if (this.size < size) {
List(size - this.size) { value } + this
} else {
this
}
}
Section 2: A single Seven-Segment View.
Firstly, let’s define an extension function drawSegment
for DrawScope
that will draw a single segment.
private fun DrawScope.drawSegment(
color: Color,
data: SegmentData,
halfWidth: Float // Half of the segment width
) {
// Create a Path object to draw the segment
val segmentPath = Path().apply {
// Using with() for improved readability by accessing SegmentData properties directly
with(data) {
// Move the cursor to the start of the segment
moveTo(startX, startY)
if (isVertical) {
// Drawing vertical segment path
lineTo(startX + halfWidth, startY + halfWidth) // 1
lineTo(startX + halfWidth, endY - halfWidth) // 2
lineTo(endX, endY)
lineTo(startX - halfWidth, endY - halfWidth) // 3
lineTo(startX - halfWidth, startY + halfWidth) // 4
} else {
// Drawing horizontal segment path
lineTo(startX + halfWidth, startY - halfWidth) // 1
lineTo(endX - halfWidth, startY - halfWidth) // 2
lineTo(endX, endY)
lineTo(endX - halfWidth, startY + halfWidth) // 3
lineTo(startX + halfWidth, startY + halfWidth) // 4
}
// Close the path
close()
}
}
// Draw the segment using the path and specified color
drawPath(segmentPath, color)
}
Excellent! We’re now ready to utilize this function for drawing the view.
@Composable
fun SingleSevenSegment(
state: SegmentsState,
modifier: Modifier,
activeColor: Color,
inactiveColor: Color,
segmentWidth: Dp,
segmentsSpace: Dp
) {
Canvas(
// This modifier ensures that the view is displayed in a correct ratio of 2:1
modifier = modifier.aspectRatio(0.5f, matchHeightConstraintsFirst = true)
) {
val halfViewHeight = (size.height / 2)
val halfWidth = (segmentWidth.toPx() / 2)
val rightEdge = (size.width - halfWidth)
val bottomEdge = (size.height - halfWidth)
// Define data for each segment based on the SegmentsState and Canvas configurations
val segmentData = listOf(
SegmentData(state.a, isVertical = false, halfWidth, rightEdge, halfWidth, halfWidth),
SegmentData(state.b, isVertical = true, rightEdge, rightEdge, halfWidth, halfViewHeight),
SegmentData(state.c, isVertical = true, rightEdge, rightEdge, halfViewHeight, bottomEdge),
SegmentData(state.d, isVertical = false, halfWidth, rightEdge, bottomEdge, bottomEdge),
SegmentData(state.e, isVertical = true, halfWidth, halfWidth, halfViewHeight, bottomEdge),
SegmentData(state.f, isVertical = true, halfWidth, halfWidth, halfWidth, halfViewHeight),
SegmentData(state.g, isVertical = false, halfWidth, rightEdge, halfViewHeight, halfViewHeight)
)
// Draw the segments
segmentData.forEach { data ->
drawSegment(
color = if (data.isActive) activeColor else inactiveColor,
data = data.addSpacing(segmentsSpace.toPx()),
halfWidth = halfWidth,
)
}
}
}
While we’ve successfully drawn a single seven-segment view, its current limitation in accepting values only up to 9 restricts its practicality. To address this, we’ll implement the final view in the next section. This enhanced view will be able to display a wider range of desired numbers.
Section 3: The final Seven-Segment View.
Well, as was mentioned above, here’s the final function:
@Composable
fun SevenSegmentView(
number: Int,
modifier: Modifier,
activeColor: Color,
inactiveColor: Color = activeColor.copy(0.16f),
digitsNumber: Int = 1,
digitsSpace: Dp = 4.dp,
segmentWidth: Dp = 4.dp,
segmentsSpace: Dp = 0.dp
) {
// Validate input parameters
require(digitsNumber > 0) { "Digits number should be greater than 0" }
require(number >= 0) { "The number has to be positive" }
// Split the number into individual digits
var digits = remember(number) { number.splitToDigits() }
// Ensures the correct number of digits to display
// Alternative behaviors could be implemented, such as throwing an exception for mismatches
if (digits.size > digitsNumber)
digits = digits.takeLast(digitsNumber)
}
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(digitsSpace),
verticalAlignment = Alignment.CenterVertically
) {
// Pad the digits to match the desired number of digits and display each digit using SingleSevenSegment
// Alternatively, replacing null with 0 ensures that unfilled segments display as 0 instead of remaining unilluminated
digits.padToStart(digitsNumber, null).forEach { digit ->
val state = digit?.getSegmentsState() ?: SegmentsState()
SingleSevenSegment(
state = state,
modifier = Modifier.fillMaxHeight(),
activeColor = activeColor,
inactiveColor = inactiveColor,
segmentWidth = segmentWidth,
segmentsSpace = segmentsSpace
)
}
}
}
Congratulations🥳! We’ve successfully built it👏. For the complete code implementation, you can access it on GitHub Gist🧑💻.
Advertisement
Are you learning a foreign language and struggling with new vocabulary? Then, I strongly recommend you check out this words-learning app, which will make your journey easy and convenient!
Section 4: Usage example.
var number by remember { mutableStateOf(0) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(50.dp)
) {
SevenSegmentView(
number = number,
digitsNumber = 4,
segmentsSpace = 1.dp,
segmentWidth = 8.dp,
digitsSpace = 16.dp,
activeColor = Color.Green,
modifier = Modifier.height(100.dp)
)
Button(
onClick = { number++ }
) {
Text("Increase counter")
}
}
Awesome! Take a look at the output:
You might also like 👇
Thank you for reading this article!❤️ I hope you’ve found it enjoyable and valuable. Feel free to show your appreciation by hitting the clap👏 if you liked it or follow Kappdev for more exciting articles😊.
Happy coding!