今回は①の続きでメニュー画面の作成を記録する。
プロジェクトは作成済み。
①NASA APIを使って簡単なアプリ作ってみた①
②NASA APIを使って簡単なアプリ作ってみた② ←こちらの記事
③NASA APIを使って簡単なアプリ作ってみた③
④NASA APIを使って簡単なアプリ作ってみた④ (9月中に公開予定)
Activity作成
今回 Jetpack Composeを使用するため、Empty Activityを選択する。

コンポーネントの作成
メニューは文字とボタンをコンポーネント化する。
テキストは以下を指定できるテキストのコンポーネントを作成する。
引数として以下を受け取る。
- text:表示する文字列
- style:文字のスタイル
- color:文字の色 (デフォルト黒)
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
@Composable
private fun CustomText(
text: String,
style: TextStyle,
color: Color = Color.Black,
) {
Text(
text = text,
color = color,
style = style,
textAlign = TextAlign.Center //要素の水平中央に配置
)
}
ボタンは画面イメージのように左に画像を配置するボタンを作成する。
今回はMaterial3のElevatedButton(影があるボタン)を使用する。その要素として画像と文字を配置する。
Spacerは画像と文字列の間隔を指定している。
引数として以下を受け取る。
- onClick:ボタン押下後の処理
- icon:アイコン画像のid
- iconDesc:アイコンの説明文字列
- label:ボタンに表示する文字列
import androidx.compose.material3.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Modifier
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
@Composable
private fun CustomButton(
onClick: () -> Unit,
icon: Int,
iconDesc: Int,
label: String
) {
ElevatedButton(
onClick = onClick,
modifier = Modifier
.fillMaxWidth(0.9f) //ボタンの横幅(画面の横幅の90%)
.height(56.dp) // ボタンの縦幅
) {
Image(
painter = painterResource(id = icon), // ボタンに配置する画像
contentDescription = stringResource(id = iconDesc), //画像の説明
modifier = Modifier.size(24.dp) //画像の大きさ
)
Spacer(modifier = Modifier.width(12.dp)) //間隔の指定
Text(
text = label, // ボタンに表示する文字
color = Color.Black, //文字の色(デフォルト黒)
textAlign = TextAlign.Center, //要素の水平中央に配置
style = MaterialTheme.typography.bodyLarge // 文字の大きさ()
)
}
}
-
Modifier:Composeの修飾子でcomposableを拡張できる
- composableのサイズ、レイアウト、動作、外見の変更
- ラベルなどの追加
- 入力の処理
- 要素の操作の追加
- painterResource:PNG などのラスター化されたアセット形式を読み込む
- stringResource:XML リソースで静的に定義されている文字列を取得する
- MaterialTheme:GoogleのオープンソースデザインシステムであるMaterial DesignでUIデザインをカスタムできる。
- typography:テキストのスタイル定義
previewを表示する
@Preview
@Composable
private fun CustomButtonPreview() {
UniverseApplicationTheme {
FeatureButton(
onClick = { },
icon = R.drawable.mars,
iconDesc = R.string.mars_img,
label = stringResource(id = R.string.mars_btn)
)
}
}
画面遷移の定義
// 今日の天文台の画面に遷移
private fun navigateToAstronomyToday(context: Context) {
context.startActivity(Intent(context, AstronomyTodayActivity::class.java))
}
// 火星の気象情報画面に遷移
private fun navigateToMarsWeather(context: Context) {
context.startActivity(Intent(context, WeatherOnMarsActivity::class.java))
}
- startActivity():起動したいActivityを定義する
- intent:実行するアクションを終わらすオブジェクト
- 明示的インテント:起動するActivityを正確に把握する(←今回はこっち)
- 暗黙的インテント:リンクを開いたり、電話を掛けるといったアクションをシステムに伝える
- context:アプリケーション環境に関するグローバル情報へのインターフェース(参考)
これで画面に必要な部品や処理の定義が完了。
画面を作成する
@Composable
private fun MainMenuScreen(modifier: Modifier = Modifier) {
val context = LocalContext.current
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center, // 画面の中央に配置
horizontalAlignment = Alignment.CenterHorizontally, // センター水平に配置
) {
// タイトル
CustomText(
text = stringResource(id = R.string.title),
style = MaterialTheme.typography.headlineSmall.copy(
fontWeight = FontWeight.ExtraBold,
fontSize = 30.sp
)
)
Spacer(modifier = Modifier.height(32.dp))
// 操作説明文
CustomText(
text = stringResource(id = R.string.ope_text),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(40.dp)) // スペース
// 今日の天文台の画面に遷移ボタン
CustomButton(
onClick = { navigateToAstronomyToday(context) }, // ボタンクリック時の処理
icon = R.drawable.universe,
iconDesc = R.string.universe_img,
label = stringResource(id = R.string.universes_btn)
)
Spacer(modifier = Modifier.height(24.dp)) // スペース
// 火星の気象情報画面に遷移ボタン
CustomButton(
onClick = { navigateToMarsWeather(context) }, // ボタンクリック時の処理
icon = R.drawable.mars,
iconDesc = R.string.mars_img,
label = stringResource(id = R.string.mars_btn)
)
Spacer(modifier = Modifier.height(32.dp)) // スペース
// 提供文
CustomText(
text = stringResource(id = R.string.courtesy_text),
style = MaterialTheme.typography.labelMedium,
color = Color.Gray
)
}
}
-
LocalContext.current:Jetpack ComposeでAndroidのContextを取得できる
-
Arrangement:レイアウトの子をレイアウトの主軸方向 (水平方向と垂直方向) のように配置するために使用する
-
Alignment:要素の位置を計算するためのインターフェース。親レイアウト内のレイアウトの配置を定義するために使用する(参考)
-
.copy():headlineSmall のプロパティの一部をカスタマイズできる関数
画面表示
class MenuActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
UniverseApplicationTheme {
Scaffold { innerPadding ->
MenuScreen(Modifier.padding(innerPadding))
}
}
}
}
}
- setContent:Composable関数が呼び出されるアクティビティのレイアウトを定義する
- Scaffold:画面コンポジターによって管理されている生バッファへのハンドル(参考)
- innerPadding:Scaffold が自動で計算した中身用のパディング。
コード全体
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
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.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.universeapplication.ui.theme.UniverseApplicationTheme
class MenuActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
UniverseApplicationTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
MainMenuScreen(Modifier.padding(innerPadding))
}
}
}
}
}
private fun navigateToAstronomyToday(context: Context) {
context.startActivity(Intent(context, AstronomyTodayActivity::class.java))
}
private fun navigateToMarsWeather(context: Context) {
context.startActivity(Intent(context, WeatherOnMarsActivity::class.java))
}
@Composable
private fun MainMenuScreen(modifier: Modifier = Modifier) {
val context = LocalContext.current
Surface(
modifier = modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(24.dp)
) {
CustomText(
text = stringResource(id = R.string.title),
style = MaterialTheme.typography.headlineSmall.copy(
fontWeight = FontWeight.ExtraBold,
fontSize = 30.sp
)
)
Spacer(modifier = Modifier.height(32.dp))
CustomText(
text = stringResource(id = R.string.ope_text),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(40.dp))
CustomButton(
onClick = { navigateToAstronomyToday(context) },
icon = R.drawable.universe,
iconDesc = R.string.universe_img,
label = stringResource(id = R.string.universes_btn)
)
Spacer(modifier = Modifier.height(24.dp))
CustomButton(
onClick = { navigateToMarsWeather(context) },
icon = R.drawable.mars,
iconDesc = R.string.mars_img,
label = stringResource(id = R.string.mars_btn)
)
Spacer(modifier = Modifier.height(32.dp))
CustomText(
text = stringResource(id = R.string.courtesy_text),
style = MaterialTheme.typography.labelMedium,
color = Color.Gray
)
}
}
}
@Composable
private fun CustomText(
text: String,
style: TextStyle,
color: Color = Color.Black,
modifier: Modifier = Modifier,
textAlign: TextAlign? = null
) {
Text(
text = text,
color = color,
style = style,
textAlign = textAlign,
modifier = modifier
)
}
@Composable
private fun CustomButton(
onClick: () -> Unit,
icon: Int,
iconDesc: Int,
label: String
) {
ElevatedButton(
onClick = onClick,
modifier = Modifier
.fillMaxWidth(0.9f)
.height(56.dp)
) {
Image(
painter = painterResource(id = icon),
contentDescription = stringResource(id = iconDesc),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = label,
color = Color.Black,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge
)
}
}
振り返り
Jetpack Composeを実装で使ってみた。今回まとめることで各関数の使い方を深く知ることが出来た。
各コンポーネントの定義範囲や配置などはまだまだ勉強が必要だと感じた。
次はAPIを使用するため、Jetpack Composeのステータスなどの使い方をまとめていく。
参考にさせていただいたサイト
https://qiita.com/ZER0NE/items/7d857480157a8a3b59e6
https://qiita.com/tkmd35/items/e6abeed6ac68cbac09ed
https://qiita.com/ryotab22/items/ddfe2b425352f557f791
https://qiita.com/Otofu_droid/items/a4c2fdc4279778c65556
