こんにちは、エンジニアリング室の tsutomu hayakawa です!
2024年9月11日(水)から13日(金)までオフラインで開催されたDroidKaigi 2024にて、DeNA はゴールドスポンサーとして協賛しました。
DeNA はDroidKaigi 2024 の「スポンサーブース」に出展し、ブース企画として「SWETチームの取り組み紹介」「Jetpack Compose Crossword Quiz」を実施しました!
当日はDeNAのブースにたくさんの方々にご参加いただき、昨年に引き続き大盛況となりました。「Jetpack Compose Crossword Quiz」で集めた解答用紙は、なんと307枚にものぼりました!
このブログでは、「Jetpack Compose Crossword Quiz」の問題と解説を行います。
※ 問題文においてimport部分の記載は省略する形で記載しております。
※ 問題文、プレビューの下に回答があります。回答を考えたい方は、スクロールを止めてチャレンジしてみてください。
Question1の問題と解説
Question1の問題、Preview、回答は以下のとおりです。
問題作成者は
@shirataki707
です。
イベント期間中の回答数は53、正解率は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),
)
}
}
}
}
クロスワードと回答
解説
難易度EasyなのでJetpackComposeの基礎的な部分を用いつつ、DeNAらしさがでるように実際の名刺をイメージして作成しました。
キーポイント
Spacer
を使った余白やpadding
の方向 (start
, top
, end
, bottom
) など、Composableの配置についての問題にしています。
反省点
難易度的にはちょうどよかったと思います。BusinessCardなので、Card Composable
を用いればよかったです。
Question2の問題と解説
Question2の問題、Preview、回答は以下のとおりです。
問題作成者は
@shirataki707
です。
イベント期間中の回答数は38、正解率は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))
)
}
クロスワードと回答
キーポイント キーポイントは2つあります。
-
図形のクリップ
- Composeでクリップするには、
graphicsLayer
を用いる方法と、そのラッパーであるModifier.clip
があります。今回はその両方を使っています。 - A-6では四角形にクリップしていますが、あえて
RectangleShape
ではなく、RoundedCornerShape
を使っています(これのせいで問題が複雑になってしまった気がします)。
- Composeでクリップするには、
-
バルーンの風船部分の形状
- Android公式のグラフィックスに関する ガイド を参考に作成しています。
graphicsLayer
のtranslationY
を用いることで、Box Composable
の境界を変えずに下方向へ円を移動できます。そのため、最初に四角形でクリップすることにより、四角からはみ出た部分が除去されるようになっています。
反省点
画像のBrush
やclip
など様々な観点を問題に詰め込んだことで予想以上に問題が難しくなりました。また、バルーンの風船部分のレイアウトについては画像とコードを良く見て考える必要があり、normalの問題としては不適切だったと反省しています。
Question3の問題と解説
Question3の問題、Preview、回答は以下のとおりです。
問題作成者は
@emusute1212
です。
イベント期間中の回答数は31、正解率は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)
)
}
クロスワードと回答
キーポイント
リッチなプログレスバーを自前で実装した際に今までなかなか使わないようなModifier
を使うことが多く、勉強になったので今回問題にしてみました。
fillMaxWidth
のfraction
に割合を指定することで、その割合分のサイズに引き伸ばすことを利用して、プログレスを表現しています。
また、今回のプログレスでは端の青色のバーが親要素のサイズを超えており、それを表現するために、wrapContentSize
のunbounded
をtrue
にして、親要素を超えて描画できるようにしました。
よくあった間違い
fillMaxWidth
をweight
と考えるケースが多かった気がしています。
また、wrapContentSize
ではなく、requiredHeight
で画面外描画を実現するのを考えている方も多かったように見受けられました。
反省点
端の青色のバーがグラデーションしている影響で画面外にはみ出ていることが若干わかりづらいのかなと思いました。このあたりはもう少し大きめにオーバーさせるなど改善の余地はあると考えております。
Question4の問題と解説
Question4の問題、Preview、回答は以下のとおりです。
問題作成者は
@emusute1212
です。
イベント期間中の回答数は37、正解率は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),
)
}
}
}
}
}
}
クロスワードと回答
キーポイント
数字を格子状に並べるサンプル。二次元配列の使い方やArrangement
の使い方がキーとなる問題。numbers
とその要素をforEach
で回すことで、格子状に配置することを表現していて、要素間のスペースはArrangement
のspacedBy
を用いることで実現している。
よくあった間違い
Arrangement
のspacedBy
をあまり利用したことがないので間違えてしまった、というケースが良く見受けられました。
反省点
少し簡単に作ってしまったのが反省点として挙げられます。問題として、一つぐらいはもう少しひねりのある問題にしても良かったと思いました。
あとは、文字間のスペースがわかりやすく見えるように、Text
の要素にborder
を追加した方が良かったのかなと考えています。
Question5の問題と解説
Question5の問題、Preview、回答は以下のとおりです。
問題作成者は
@shirataki707
です。
イベント期間中の回答数は35、正解率は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
)
}
}
クロスワードと回答
解説
難易度EasyなのでComposeの基礎的な部分を用いつつ、Androidらしさがでるようにドロイドくんを描きました。問題として見せるコード量を減らすためと、クロスワードの答えが複数箇所に現れるのを防ぐために、DrodiKunFace()
のように別で定義したComposable
を呼び出しています。
キーポイント
Canvas
を使って描画すると難易度が高くなるため、offset
やrotate
を使って描いています。
反省点
DroidKunLeftEar
としている部分は、画面内の左側にある耳という意味で命名しています。しかし、ドロイドくんにとっては右耳になるので、少し混乱させてしまったかもしれません。
Question6の問題と解説
Question6の問題、Preview、回答は以下のとおりです。
問題作成者は
@chnotchy
です。
イベント期間中の回答数は46、正解率は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()
)
}
}
}
クロスワードと回答
キーポイント
- ポイントは以下の3点です。
Box
の表示順- 円形の
clip
- 縦横比固定
まずは基本的なことですが、Box内のComposableは、下に記述された要素は画面の手前に重なって表示されます。今回は、 back.png
は背景に、 grad.png
は手前に表示する必要があります。したがって、最初の画像(A-5)が back
、次の画像(A-2)が grad
となります。
A-1は、円形に画像を切り取るCircleShape
です。
最後に、あまり使用する機会がないのが、画像の縦横比を固定するためのaspectRatio
(A-3)です。引数に 1f
が指定されているのがヒントです。これを指定しないと、画像が余白いっぱいに広がり、縦長の画面で表示した場合、画像が縦長になってしまいます。詳細は以下のドキュメントを参照してください。
https://developer.android.com/develop/ui/compose/graphics/images/customize?hl=ja#custom-aspect-ratio
Question7の問題と解説
Question7の問題、Preview、回答は以下のとおりです。
問題作成者は
@akkiee76
です。
イベント期間中の回答数は36、正解率は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"
)
}
}
クロスワードと回答
解説
GoogleMapを利用したよくある地図アプリ風の問題です。回答部分は基本的なModifierを出題しています。GoogleMapのキーワードはGoogleMapを使ったことがなくても回答しやすい問題となっているので、前後のコードから推測してみてください!
Question8の問題と解説
Question8の問題、Preview、回答は以下のとおりです。
問題作成者は
@akkiee76
です。
イベント期間中の回答数は31、正解率は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
)
)
}
}
クロスワードと回答
解説
Canvas
を利用したデザインをベースにした問題です。基本的なModifier
から出題しています。A-1、 A-5は、drawPath
のModifier
から出題していますが、プレビューを参考にぜひ挑戦してみてください!
予備Questionの問題と解説
こちらは予備として用意していた問題、Preview、回答になります。
問題作成者は@rokuro.ichiharaです。
皆様に解いていただく機会はなかったですが、考えてみていただければありがたいです。
@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
)
)
}
}
クロスワードと回答
解説
この問題は難易度がeasyであるため、基本的なComposeの知識があれば解けるようにComposeの基礎的なレイアウトや、Modifier
のクラス関数を使う問題を出題しました。UIとしてはDroidKaigiのテーマカラーを使ったバナーと、
Googleの公式サイト
から引用したテキストを置くような形で構成しています。
「Jetpack Compose Crossword Quiz」の問題解説は以上になります。 DroidKaigi 2024 のブースでたくさんの方々に問題の挑戦していただけ非常に嬉しく思います! 問題が変わるタイミングで挑戦しにきてくれる方もおり、合計300回を超える回答をいただきました。 多くの方々の参加と体験を提供できたブースイベントは、今後も続けていきたいと思っています。 コメントやフィードバックもお待ちしております。最後まで読んでいただき、本当にありがとうございました!!
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。