Blog Infos
Author
Published
Topics
Published
Behind the mask

This is the playing field, in this state the matrix has all the cells valued at 0.

Paint it black
Canvas(
    modifier = Modifier
) {
    // this: DrawScope
}
fun drawLine(
color: Color,
start: Offset,
end: Offset,
strokeWidth: Float = Stroke.HairlineWidth,
cap: StrokeCap = Stroke.DefaultCap,
pathEffect: PathEffect? = null,
/*FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
fun drawRect(
color: Color,
topLeft: Offset = Offset.Zero,
size: Size = this.size.offsetSize(topLeft),
/*@FloatRange(from = 0.0, to = 1.0)*/
alpha: Float = 1.0f,
style: DrawStyle = Fill,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode
)
view raw DrawScope.kt hosted with ❤ by GitHub
@Preview
@Composable
fun DrawSquare() {
Canvas(modifier = Modifier.size(200.dp)) {
drawRect(
color = Color.Red,
size = size
)
}
}
view raw GameActivity.kt hosted with ❤ by GitHub

We note that from the DrawScope we can also access the dimensions of the Canvas and we can use it to size the square so that it occupies the entire space of the Canvas.

 

Let’s now try to draw two squares side by side.

@Preview
@Composable
fun DrawSquare() {
Canvas(
modifier = Modifier
.height(100.dp)
.width(200.dp)
) {
drawRect(
color = Color.Red,
size = Size(width = size.height, height = size.height),
topLeft = Offset(x = 0f, y = 0f)
)
drawRect(
color = Color.Red,
size = Size(width = size.height, height = size.height),
topLeft = Offset(x = size.height, y = 0f)
)
}
}
view raw GameActivity.kt hosted with ❤ by GitHub

As you can see, we doubled the width of the canvas and called the drawRectmethod twice, to position the two squares we use the topLeft parameter. In the case of the first square we start the drawing from the point x = 0 and y = 0, for the second square instead we start from y = 0 but we have to move on the x axis by the length of the side of the first square drawn.

 

 

The edges of the two squares are not distinguishable because they have the same color, so let’s draw the edges.

@Preview
@Composable
fun DrawSquare() {
Canvas(
modifier = Modifier
.height(100.dp)
.width(200.dp)
) {
drawRect(
color = Color.Red,
size = Size(width = size.height, height = size.height),
topLeft = Offset(x = 0f, y = 0f)
)
drawRect(
color = Color.Black,
size = Size(width = size.height, height = size.height),
topLeft = Offset(x = 0f, y = 0f),
style = Stroke(width = 5f)
)
drawRect(
color = Color.Red,
size = Size(width = size.height, height = size.height),
topLeft = Offset(x = size.height, y = 0f)
)
drawRect(
color = Color.Black,
size = Size(width = size.height, height = size.height),
topLeft = Offset(x = size.height, y = 0f),
style = Stroke(width = 5f)
)
}
}
view raw GameActivity.kt hosted with ❤ by GitHub

 

Let’s play
fun DrawScope.drawGameElement(
squareColor : Color,
borderColor : Color,
offset : Offset,
size : Size
) {
drawRect(
squareColor,
topLeft = offset,
size = size
)
drawRect(
borderColor,
topLeft = offset,
size = size,
style = Stroke(width = 1f)
)
}
view raw GameActivity.kt hosted with ❤ by GitHub

The last step is to create a canvas that given an array of integers you call the drawGameElement extension with an offset determined by the indices of the individual cells of the array.

val state : State<Array<IntArray>> = gameViewModel.state.collectAsState()
Canvas(
modifier = Modifier
.fillMaxHeight(0.8f)
.aspectRatio(0.5f)
.border(1.dp, Color.Black)
) {
val squareDim = size.height / state.value.size
state.value.forEachIndexed { y, it ->
it.forEachIndexed { x, value ->
if (value > 0) {
drawGameElement(
squareColor = when (value) {
1 -> Color.Red
2 -> Color.Blue
3 -> Color.Yellow
4 -> Color.Green
else -> Color.Red
},
borderColor = Color.White,
offset = Offset(
x = (x) * squareDim,
y = (y) * squareDim
),
size = Size(
width = squareDim,
height = squareDim
)
)
}
}
}
}
view raw GameActivity.kt hosted with ❤ by GitHub

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

With the advent of Android 15, edge-to-edge design has become the default configuration. Consequently, applications must be capable of accommodating window insets, including the system status bar and navigation bar, as well as supporting drawing…
Watch Video

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility GmbH

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android develop ...
Seven Principles Mob ...

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility ...

Jobs

val state : State<Array<IntArray>> = gameViewModel.state.collectAsState()

Now we need to calculate the dimensions of each single square, to do this we divide the height of the canvas by the number of rows in the matrix. Since we want to draw squares their height will be equal to their width so let’s avoid dividing the canvas width by the number of columns but keep in mind that we must make sure that the ratio between number of rows and number of columns must be equal to that between the height and width of the canvas.

val squareDim = size.height / state.value.size
state.value.forEachIndexed { y, it ->
    it.forEachIndexed { x, value ->
        if (value > 0) {
            drawGameElement(
                    ...
            )
        }
    }
}
squareColor = when (value) {
    1 -> Color.Red
    2 -> Color.Blue
    3 -> Color.Yellow
    4 -> Color.Green
    else -> Color.Red
}

For the size we use the previously calculated size.

size = Size(
    width = squareDim,
    height = squareDim
)

Finally we have to calculate the offset of each square, to do this just multiply the index on the x axis and the index on the y axis of each cell by the length of the side of the square.

offset = Offset(
    x = (x) * squareDim,
    y = (y) * squareDim
)
@HiltViewModel
class GameViewModel @Inject constructor() : ViewModel() {
private val field = Field()
private val stateFlow = MutableStateFlow(field.getActualField())
val state : StateFlow<Array<IntArray>> = stateFlow
init {
viewModelScope.launch {
while (!field.isGameEnded) {
delay(500)
// The tick method contains all the
// operating logic of the game.
field.tick()
stateFlow.emit(field.getActualField())
}
}
}
fun moveRight() {
viewModelScope.launch {
field.moveRight()
stateFlow.emit(field.getActualField())
}
}
fun moveLeft() {
viewModelScope.launch {
field.moveLeft()
stateFlow.emit(field.getActualField())
}
}
fun turn() {
viewModelScope.launch {
field.turnPiece()
stateFlow.emit(field.getActualField())
}
}
}
That’s all folks

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
Menu