This is an English translation for the article: Jetpack Compose Crossword Quizの問題紹介&解答解説
Hello, I’m Tsutomu Hayakawa from the Engineering Department!
DeNA participated as a Gold Sponsor at DroidKaigi 2024, held offline from Wednesday, September 11th to Friday, September 13th, 2024.
This year we set up a Sponsor Booth where two activities were held: “Introduction to SWET Team’s Initiatives” and “Jetpack Compose Crossword Quiz”!
A large number of attendees came to visit our booth, and just like last year, the activities were a huge success: a total of 307 answer sheets were collected just for the “Jetpack Compose Crossword Quiz”!
In this blog, we will introduce the questions and answers that were presented this year for this quiz.
※ The import statements are omitted in the problem descriptions.
※ The answers are provided below the questions and previews. If you would like to challenge yourself, please stop scrolling and try to solve them first!
Question 1
Below are the problem, preview, and answer for Question 1.
This problem was created by
@shirataki707
.
We collected 53 answers during the event, with a correct answer rate of 83%.
@Composable
fun Question1() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
BusinessCard()
}
}
@Composable
fun BusinessCard() {
Row(
modifier = Modifier
.size(width = 320.dp, height = 180.dp)
.□□□□□□(width = 1.dp, color = Color.Black) // A-2
) {
Box(
modifier = Modifier
.background(color = colorResource(id = R.color.orange))
.size(width = 160.dp, height = 180.dp),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.dena_logo),
contentDescription = "DeNA Logo",
modifier = Modifier.size(256.dp)
)
}
Box(
modifier = Modifier
.background(Color.White)
.size(width = 160.dp, height = 180.dp)
) {
Column(
modifier = Modifier
.fillMaxSize(),
□□□□□□□□□□Alignment = Alignment.Start // A-1
) {
Text(
text = "ドロイド太郎",
modifier = Modifier.padding(start = 16.dp, □□□ = 16.dp) // A-4
)
Text(text = "Taro Droid", modifier = Modifier.padding(start = 20.dp))
□□□□□□(modifier = Modifier.weight(1f)) // A-3
Text(
text = "Android事業本部\nKotlin部",
style = □□□□□□□□□(fontSize = 12.sp), // A-5
modifier = Modifier.padding(start = 16.dp, bottom = 16.dp),
)
}
}
}
}
Crossword and Answers
Explanation
Since the difficulty level is Easy, basic parts of Jetpack Compose were used to create this problem, adding a DeNA feel to it by reprecating an actual DeNA business card.
Key Points
The problem is about the positioning of Composables, for example margins with Spacer
and padding
’s directions (start
, top
, end
, bottom
).
Reflections
The difficulty level seemed just right. Since it’s a BusinessCard, maybe the Card Composable should have been used.
Question 2
Below are the problem, preview, and answer for Question 2.
This problem was created by
@shirataki707
.
We collected 38 answers during the event, with a correct answer rate of 13%.
@Composable
fun Question2Page() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
val imageBrush =
□□□□□□□□□□□(ImageShader(ImageBitmap.imageResource(id = R.drawable.golden_gate_bridge))) // A-1
Box(
modifier = Modifier
.clip(□□□□□□□□□□□□□Shape(0.dp)) // A-6
.size(300.dp)
.□□□□□□□□□□□□□ { // A-2
clip = true
shape = CircleShape
□□□□□□□□□□□□ = 30.dp.toPx() // A-3
}
.background(Color(0xFFF06292))
) {
Text(
text = "Hello\nDroidKaigi 2024",
style = TextStyle(
□□□□□ = imageBrush, // A-4
fontWeight = FontWeight.ExtraBold,
fontSize = 36.sp,
),
modifier = Modifier
.align(Alignment.Center)
.padding(16.dp)
)
}
Box {
BalloonCord(offset = Offset(-65f, 0f), degrees = -20f)
BalloonCord(offset = Offset(-20f, 0f), degrees = -10f)
BalloonCord(offset = Offset(65f, 0f), degrees = 20f)
BalloonCord(offset = Offset(20f, 0f), degrees = 10f)
}
Box(
modifier = Modifier
.width(100.dp)
.height(70.dp)
.background(Color(0xFF6A4E42))
)
}
}
@Composable
fun BalloonCord(offset: Offset, degrees: Float) {
Box(
modifier = Modifier
.offset(x = offset.x.dp)
.size(width = 6.dp, height = 120.dp)
.□□□□□□(degrees = degrees) // A-5
.background(color = Color(0xFFD2B48C))
)
}
Crossword and Answers
Key Points
There are two key points:
-
Shape Clipping
- To clip shapes in Compose, you can use
graphicsLayer
or its wrapperModifier.clip
. In this problem, both are used. - For A-6, although it clips to a rectangle,
RoundedCornerShape
was intentionally used instead ofRectangleShape
(This might have made the problem somewhat more complex).
- To clip shapes in Compose, you can use
-
Balloon Shape of the Balloon Section
- The official Android graphics guide was used as a reference to create it.
- By leveraging
graphicsLayer
’stranslationY
, the circle can be moved downwards without changing the boundaries of theBox Composable
. This way, by first clipping with a rectangle, the portion that extends out of the rectangle is removed.
Reflections
Cramming various aspects of the image like Brush
and clip
into the problem might have made the difficulty higher than expected. Additionally, the layout of the balloon section required close inspection of the image and the code, which we realize in hindsight was inappropriate for a normal-level problem.
Question 3
Below are the problem, preview, and answer for Question 3.
This problem was created by
@emusute1212
.
We collected 31 answers during the event, with a correct answer rate of 35%.
@Composable
fun Question3Page() {
val progressWidth = 300.dp
val progressHeight = 20.dp
val thumbsWidth = 4.dp
val percentage = 0.6f
Box(
modifier = Modifier
.fillMaxSize()
.background(color = Color.White),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.width(progressWidth)
.height(progressHeight)
.background(color = Color.LightGray)
) {
Box(
modifier = Modifier
.□□□□□□□□□□□□(□□□□□□□□ = percentage) // A-6, A-3
.fillMaxHeight()
.background(color = Color.Cyan)
)
Thumbs(
modifier = Modifier
.offset(x = (□□□□□□□□□□□□□ * □□□□□□□□□□) - (□□□□□□□□□□□ / 2)) // A-2, A-4, A-5
.□□□□□□□□□□□□□□□(□□□□□□□□□ = true), // A-7, A-1
width = thumbsWidth,
height = progressHeight + 4.dp,
)
}
}
}
@Composable
fun Thumbs(
modifier: Modifier = Modifier,
width: Dp,
height: Dp,
) {
Box(
modifier = modifier
.width(width)
.height(height)
.blur(2.dp, edgeTreatment = BlurredEdgeTreatment.Unbounded)
.background(color = Color.Blue)
)
}
Crossword and Answers
Key Points
Recently there was an opportunity for the problem creator to implement a rich progress bar, allowing him to try some Modifier
s that he had never used before. This good learning experience was turned into this problem.
Specifying a percentage to the fraction
argument of fillMaxWidth
will make the progress bar length stretches accordingly.
Additionally, the blue bar at the end of the progress bar extends beyond the size of the parent element. To express this, the unbounded
argument of wrapContentSize
is set to true
, allowing the blue bar to be drawn beyond the parent element.
Common Mistakes
It seemed that many people considered fillMaxWidth
as weight
.
Also, it seemed that many people were thinking of using requiredHeight
instead of wrapContentSize
to achieve off-screen drawing.
Reflections
The gradient effect on the blue bar at the end made it a little hard to understand that it extends beyond the screen. There might have been room for improvement with this problem, such as making the overflow more prominent.
Question 4
Below are the problem, preview, and answer for Question 4.
This problem was created by
@emusute1212
.
We collected 37 answers during the event, with a correct answer rate of 57%.
@Composable
fun Question4() {
val numbers = listOf(
listOf(7, 8, 9),
listOf(4, 5, 6),
listOf(1, 2, 3),
listOf(0),
)
Box(
modifier = Modifier
.fillMaxSize()
.background(color = Color.White),
contentAlignment = Alignment.Center,
) {
Column(
modifier = Modifier
.width(200.dp),
) {
□□□□□□□.forEach { // A-5
Row(
□□□□□□□□□□Arrangement = Arrangement.□□□□□□□□(20.dp), // A-4, A-2
) {
it.forEach { number ->
Text(
text = number.toString(),
style = □□□□□□□□□( // A-3
fontSize = 80.sp,
□□□□□□□□□ = TextAlign.Center, // A-1
),
modifier = Modifier.weight(1f),
)
}
}
}
}
}
}
Crossword and Answers
Key Points
This is a sample that arranges numbers in a grid pattern. The key to this problem is how to use two-dimensional arrays and Arrangement
. It demonstrates how to arrange items in a grid pattern, by iterating over numbers
and its elements with forEach
. The spaces between elements are achieved by using Arrangement
’s spacedBy
.
Common Mistakes
We noticed that most mistakes were made because people were not very familiar with using Arrangement
’s spacedBy
.
Reflections
A point for reflection is that the problem was made a bit too easy. It might have been better to include at least one more twist or challenging element. Additionally, to make the spacing between characters more visible, it would have been better to add a border
to the Text
elements.
Question 5
Below are the problem, preview, and answer for Question 5.
This problem was created by
@shirataki707
.
We collected 35 answers during the event, with a correct answer rate of 88%.
@Composable
fun Question5() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.□□□□□□ // A-4
) {
DroidKunFace()
DroidKunRightEye()
DroidKunLeftEye()
DroidKunRightEar()
// DroidKunLeftEar
Box(
modifier = □□□□□□□□ // A-5
.□□□□□□(x = (-75).dp, y = (-150).dp) // A-1
.size(width = 12.dp, □□□□□□ = 60.dp) // A-3
.□□□□□□(degrees = -30f) // A-6
.□□□□□□□□□□(color = Color(0xFFA4CA39)) // A-2
)
}
}
Crossword and Answers
Explanation
Since the difficulty level is Easy, only the basic parts of Compose were used, and an Android feel was brought by drawing Droid-kun. To reduce the amount of code shown in the problem and to prevent the crossword answers from appearing in multiple places, Composable
functions such as DroidKunFace()
were defined separately.
Key Points
Instead of using Canvas
, which would increase the difficulty level, the character was drawn by using offset
and rotate
.
Reflections
The part DroidKunLeftEar
is named to indicate the ear on the left side of the screen. However, considering it from Droid-kun’s perspective, this would be its right ear. This might have caused some confusion.
Question 6
Below are the problem, preview, and answer for Question 6.
This problem was created by
@chnotchy
.
We collected 46 answers during the event, with a correct answer rate of 56%.
@Composable
fun Question6() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp)
) {
Assets() // display `back.png` and `grad.png`
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.□□□□□□□□□□□(1f) // A-3
.fillMaxSize()
.clip(□□□□□□□□□□□) // A-1
) {
□□□□□( // A-4
painter = painterResource(id = R.drawable.□□□□), // A-5
contentDescription = "icon",
modifier = Modifier.fillMaxSize()
)
□□□□□( // A-4
painter = painterResource(id = R.drawable.□□□□), // A-2
contentDescription = "icon",
modifier = Modifier.fillMaxSize()
)
}
}
}
Crossword and Answers
Key Points
The key points are as follows:
Box
display order- Circular
clip
- Fixed aspect ratio
First, the basics: in a Box, the Composable defined last will be displayed in front of the one defined before. In this case, back.png
needs to be in the background, while grad.png
needs to be displayed in the front. Therefore, the first image (A-5) is back
, and the next image (A-2) is grad
.
A-1 refers to the CircleShape
used to clip an image into a circular shape.
Finally, the aspect ratio of the image is fixed with the rather rarely used aspectRatio
(A-3). The hint here is the argument 1f
. If this Modifier is not specified, the image will stretch to fill the entire space, so when displayed on a vertically long screen, the image will become vertically long. For further details, please refer to the following document.
https://developer.android.com/develop/ui/compose/graphics/images/customize?hl=en#custom-aspect-ratio
Question 7
Below are the problem, preview, and answer for Question 7.
This problem was created by
@akkiee76
.
We collected 36 answers during the event, with a correct answer rate of 41%.
@Composable
fun Question09() {
var isPresented by remember { mutableStateOf(true) }
val bottomSheetState = rememberModalBottomSheetState()
Column(
modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.SpaceBetween
) {
Box(
modifier = Modifier
.aspectRatio(1f)
.fillMaxWidth()
) {
MapView()
}
Spacer(modifier = Modifier.weight(1f))
}
□□□□□□□□□□□□□□□□( // A-2
on□□□□□□□Request = { isPresented = false }, // A-6
modifier = Modifier.fillMaxSize(),
sheetState = bottomSheetState,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
□□□□□Color = Color.Transparent // A-4
) {
Column(modifier = Modifier.□□□□□□□(horizontal = 8.dp)) { // A-5
Text(
text = "Golden Gate Bridge", fontSize = 24.sp
)
Text(
text = "San Francisco, California",
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
)
Divider(modifier = Modifier.padding(vertical = 8.dp))
Text(
text = "A suspension bridge spanning the Golden Gate Strait connecting San Francisco Bay on the west coast of the United States with the Pacific Ocean."
)
Spacer(modifier = Modifier.height(8.dp))
□□□( // A-8
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center
) {
□□□□□( // A-10
painter = painterResource(id = R.□□□□□□□□.golden_gate_bridge), // A-7
contentDescription = "Golden Gate Bridge",
modifier = Modifier.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Fit
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "photo by @mvdheuvel on Unsplash", fontSize = 16.sp
)
}
}
}
@Composable
fun MapView() {
val goldenGateBridge = LatLng(37.8199, -122.4783)
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(goldenGateBridge, 12.5f)
}
□□□□□□□□□( // A-3
modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState
) {
□□□□□□( // A-1
state = MarkerState(□□□□□□□□ = goldenGateBridge), // A-9
title = "GoldenGateBridge",
snippet = "Marker in GoldenGateBridge"
)
}
}
Crossword and Answers
Explanation
This problem showcases a typical map application using Google Maps. The answers focus on basic Modifier
s. The Google Maps keywords are designed to be easy to figure out even if you have never used Google Maps before, so feel free to try to guess them from the surrounding code!
Question 8
Below are the problem, preview, and answer for Question 8.
This problem was created by
@akkiee76
.
We collected 31 answers during the event, with a correct answer rate of 58%.
@Composable
fun Question8() {
Box(
modifier = Modifier
.fillMaxSize()
.□□□□□□□□□□(Color(0xFF3DDC84)), // A-3
□□□□□□□□□□□□□□□□ = Alignment.Center // A-4
) {
Canvas(modifier = Modifier.size(1080f.dp)) {
drawPath(
path = shadowPath, brush = Brush.linear□□□□□□□□( // A-1
colors = listOf(Color(0x44000000), Color(0x00000000)),
start = Offset(429.492f, 495.9793f),
end = Offset(858.4757f, 924.963f)
)
)
drawPath(
path = droidPath, color = Color.White, style = □□□□ // A-5
)
}
Text(
text = "DroidKaigi 2024",
modifier = Modifier.□□□□□□(y = 48.dp), // A-2
color = Color.White,
style = □□□□□□□□□( // A-6
fontSize = 36.sp, fontWeight = FontWeight.Bold
)
)
}
}
Crossword and Answers
Explanation
This problem is based on a design using Canvas
. It features questions on basic Modifier
s. A-1 and A-5 are questions about the Modifier
for drawPath
, so please try them out while looking closely at the preview!
Backup Question
Below are the problem, preview, and answer for a question that we had prepared as a backup.
This problem was created by @rokuro.ichihara.
Although there was no opportunity for everyone to attempt this question on-site, feel free to give it a shot now!
@Composable
fun QuestionSpare() {
Box(
modifier = Modifier
.fillMaxSize()
.□□□□□□□□□□(Color(0xFF3DDC84)), // A-3
□□□□□□□□□□□□□□□□ = Alignment.Center // A-4
) {
Canvas(modifier = Modifier.size(1080f.dp)) {
drawPath(
path = shadowPath, brush = Brush.linear□□□□□□□□( // A-1
colors = listOf(Color(0x44000000), Color(0x00000000)),
start = Offset(429.492f, 495.9793f),
end = Offset(858.4757f, 924.963f)
)
)
drawPath(
path = droidPath, color = Color.White, style = □□□□ // A-5
)
}
Text(
text = "DroidKaigi 2024",
modifier = Modifier.□□□□□□(y = 48.dp), // A-2
color = Color.White,
style = □□□□□□□□□( // A-6
fontSize = 36.sp, fontWeight = FontWeight.Bold
)
)
}
}
Crossword and Answers
Explanation
Since the difficulty level is Easy, this question can be solved with basic knowledge of Compose, by focusing on Compose’s fundamental layouts and Modifier
class functions.
The UI is composed of a banner using DroidKaigi’s theme color and text quoted from Google’s official site.
This concludes the explanation of the “Jetpack Compose Crossword Quiz”. We are very happy that so many people tried the problems at our booth this DroidKaigi 2024! Some participants came back to try the problems as they changed, resulting in over 300 responses in total. We hope to continue holding booth activities that many people can try and experience in the future. Comments and feedback are welcome and appreciated. Thank you very much for reading to the end!
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。