メモ > 技術 > IDE: AndroidStudio > アプリの作成(Jetpack)
アプリの作成(Jetpack)
■Jetpackについて
Android Jetpackとは、アプリを作成するためのコンポーネントやツールなどをひとまとめにしたもの。
AndroidのOSバージョンアップからは切り離されているので、OSのアップデートを待たずに迅速に対応できるようになっている。
一例だが、以下のような機能が提供されている。
compose … 形状とデータの依存関係を記述するコンポーズ可能な関数を使用して、UIをプログラムで定義する。
camera … モバイルカメラアプリを構築する。
appsearch … ユーザー向けにカスタムのアプリ内検索機能を構築する。
Android Jetpack デベロッパー リソース - Android デベロッパー | Android Developers
https://developer.android.com/jetpack?hl=ja
Google Developers Japan: Android Jetpack を使用してアプリの開発を加速
https://developers-jp.googleblog.com/2018/05/use-android-jetpack-to-accelerate-your.html
Android Jetpackってなにもの? - Qiita
https://qiita.com/k_masa777/items/c01c1de6ac763ce5c075
■Jetpack Composeについて
Jetpackで提供されるコンポーネントの一つ。
Kotlinで宣言的にUIを記述できる。(SwiftUIのように簡単にレイアウトできるみたい。)
従来のようなXMLではなく、setContentブロック内で「コンポーズ可能な関数」を呼び出してレイアウトを作成する。
コンポーズ可能な関数は、関数名に「@Composable」アノテーションを追加するだけで作成できる。
まずは以下の内容を一読するといい。
UI アプリ開発ツールキット Jetpack Compose - Android デベロッパー | Android Developers
https://developer.android.com/jetpack/compose?hl=ja
Android Compose のチュートリアル | Android デベロッパー | Android Developers
https://developer.android.com/jetpack/compose/tutorial?hl=ja
以下も参考になりそう。
Jetpack Compose入門 はじめの一歩
https://zenn.dev/ko2ic/articles/0a141f9e5a0d39
Jetpack Composeを使ってみた - Qiita
https://qiita.com/kota_2402/items/7bbdd87be8024785e25b
Jetpack Compose入門 - 縁側プログラミング
https://engawapg.net/programming/jetpack-compose/
■ハローワールド(Koala Feature Drop時点)
src/main/java/net/refirio/helloworld/MainActivity.kt
package net.refirio.helloworld
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import net.refirio.helloworld.ui.theme.HelloWorldTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
HelloWorldTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
HelloWorldTheme {
Greeting("Android")
}
}
build.gradle.kts
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
}
app\build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.example.helloworld"
compileSdk = 34
defaultConfig {
applicationId = "com.example.helloworld"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
app\src\main\AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloWorld"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.HelloWorld">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
以前に比べて、MainActivity.kt 内に enableEdgeToEdge() というコードが追加されている。
edge-to-edgeについては、以下などのページを参照。
APIレベルを上げると強制的に適用されるようなので、原則有効であるものと考える方が良さそう。
アプリの対象 API レベル 35 で初めて edge-to-edge に対処する[Android View編] #Android - Qiita
https://qiita.com/seabat-dev/items/b1ee9c71674e80abccc7
Androidのedge-to-edge表示対応の作業メモ(Compose向け)
https://zenn.dev/tomoya0x00/articles/f854a6825a1182
■練習用に表示を調整した場合(Koala Feature Drop時点)
src/main/java/net/refirio/helloworld/MainActivity.kt
- - - - - - - - - - - - - - - - - - - -
package net.refirio.helloworld
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import net.refirio.helloworld.ui.theme.HelloWorldTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
HelloWorldTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
MainScreen(modifier = Modifier.padding(innerPadding))
}
}
}
}
}
@Composable
fun MainScreen(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text("Hello Android!!")
}
}
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
MainScreen()
}
- - - - - - - - - - - - - - - - - - - -
■練習用にテーマを外した場合(Koala Feature Drop時点)
※テーマについては、後述の「テーマの適用」も参照。
※テーマ名の「HelloWorld」は固定の名前ではなく、アプリを作成したときのパッケージ名をもとに付けられるみたい。
よってこのテーマは無理に外さずに、必要に応じてカスタマイズして使うと良さそう。
src/main/java/net/refirio/helloworld/MainActivity.kt
- - - - - - - - - - - - - - - - - - - -
package net.refirio.helloworld
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
MainScreen(modifier = Modifier.padding(innerPadding))
}
}
}
}
@Composable
fun MainScreen(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text("Hello Android!!")
}
}
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
MainScreen()
}
- - - - - - - - - - - - - - - - - - - -
また app\src\main\AndroidManifest.xml から以下のように、
「Theme.HelloWorld」に関する記述を削除すればテーマの適用は無くなるみたい。
(ただしテーマが適用されていると全画面で表示されるが、テーマを削除すると上部にタイトルバーが表示される。)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
■ハローワールド(Flamingo時点)
src/main/java/net/refirio/helloworld/MainActivity.kt
package net.refirio.helloworld
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import net.refirio.helloworld.ui.theme.HelloWorldTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HelloWorldTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
HelloWorldTheme {
Greeting("Android")
}
}
■練習用にテーマを外した場合(Flamingo時点)
※テーマについては、後述の「テーマの適用」も参照。
※テーマ名の「HelloWorld」は固定の名前ではなく、アプリを作成したときのパッケージ名をもとに付けられるみたい。
よってこのテーマは無理に外さずに、必要に応じてカスタマイズして使うと良さそう。
src/main/java/net/refirio/helloworld/MainActivity.kt
package com.example.helloworld
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen()
}
}
}
@Composable
fun MainScreen() {
Text("Hello Android!!")
}
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
MainScreen()
}
■プレビューの表示をリッチにする
showSystemUiを有効にすると、プレビューにUIが表示される。
@Preview(showBackground = true)
↓
@Preview(showSystemUi = true)
Compose のツール | Jetpack Compose | Android Developers
https://developer.android.com/jetpack/compose/tooling?hl=ja
■Jetpack Composeの基本
※上で作成した「練習用に表示を調整した場合」をもとに、MainScreen() の内容だけを以下に記述する。
Text("Hello")
文字列の表示。
Column {
// テキストを表示
Text("Hello")
// 色名を指定してテキストを表示
Text("Hello", color = Color.Red)
// 色コードを指定してテキストを表示
Text("Hello", color = Color(0xff66ccaa))
// フォントサイズを指定してテキストを表示
Text("Hello", fontSize = 10.sp)
Text("Hello", fontSize = 30.sp)
}
コンポーネントの装飾。
// テキストを装飾
Text(
"Hello",
modifier = Modifier
.size(120.dp, 80.dp)
.offset(20.dp, 20.dp)
.background(Color(0xff66cdaa), RoundedCornerShape(20.dp))
.border(2.dp, Color(0xff2f4f4f), RoundedCornerShape(20.dp))
.padding(20.dp)
)
画像の表示。
あらかじめ、res/drawable 内に画像を配置しておく(ここでは「photo01.jpg」としておく。)
// 画像を表示
Image(
painter = painterResource(R.drawable.photo01),
contentDescription = "画像の表示サンプル"
)
Imageを使えない場合、自動で適切ではないクラスが読み込まれている可能性がある。
この場合、以下のように読み込むクラスを調整する。
import androidx.compose.ui.semantics.Role.Companion.Image
↓
import androidx.compose.foundation.Image
【Jetpack Compose】Image()コンポーザブルが使用できない - Qiita
https://qiita.com/antk/items/3b10b5f8843bb8896470
縦に並べる。
Column {
Text("Hello!")
Text("Hello!!")
Text("Hello!!!")
}
横に並べる。
Row {
Image(
painter = painterResource(id = R.drawable.photo01),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(50.dp)
)
Image(
painter = painterResource(id = R.drawable.photo02),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(50.dp)
)
Image(
painter = painterResource(id = R.drawable.photo03),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(50.dp)
)
}
入れ子にする。
Column {
Row {
Text("[AAA]")
Text("[BBB]")
Text("[CCC]")
}
Row {
Text("[DDD]")
Text("[EEE]")
Text("[FFF]")
}
}
ボタンの表示。
Column {
Text("これはボタンのテストです。")
Button(
onClick = { Log.d("Button", "onClick") }
) {
Text("ボタン")
}
}
ボタンをクリックしてUIを更新。
Column {
// 「by remember」と「mutableStateOf」により、前回の値を記憶している
var count by remember { mutableStateOf(0) }
Text("ボタンのタップ回数: $count")
Button(
onClick = { count++ }
) {
Text("カウントアップ")
}
}
値の保持については
・「by remember」で宣言すると、Composable関数で特定の値を保持できる。(値がリセットされない。)
rememberの後の「{ 〜 }」ブロック内は、初回しか実行されない。つまり初期値をセットできる。
・「mutableStateOf」は、値の変更を監視することが可能なMutableStateを返す。
・「by remember」で宣言された変数(count)は見た目は普通のintだが、ComposeではStateとして扱われる。
ただし、この変数を別のStateでない変数に代入すると、代入先の変数は普通の変数となり、値の変更も監視されなくなる。
という仕組みで実現している。
「remember」や「mutableStateOf」については、以下なども参考になる。
Jetpack Compose入門(11) ボタンクリックでUIを更新する - 縁側プログラミング
https://engawapg.net/jetpack-compose/1038/update-ui-on-click-button/
もう雰囲気で使わない。rememberを理解するためのポイント - 縁側プログラミング
https://engawapg.net/jetpack-compose/2113/remember-tips/
「by remember」を使う際、
var count by remember { mutableStateOf(0) }
のコードで以下のようなエラーになることがあった。
この場合、IDEの機能でimportを何度か行うと解消された。
Type 'TypeVariable(T)' has no method 'getValue(Nothing?, KProperty<*>)' and thus it cannot serve as a delegate
Caused by: org.gradle.api.GradleException: Compilation error. See log for more details
UIを専用の関数にまとめる。
コンポーズ可能な(UI用の関数を呼び出す)関数には「@Composable」を付ける。
@Composable
fun MainScreen() {
Column {
SubContents()
SubContents()
SubContents()
}
}
@Composable
fun SubContents() {
Row {
Text("[AAA]")
Text("[BBB]")
Text("[CCC]")
}
}
■Jetpack Composeで画面遷移
Jetpack Compose入門(15) 画面遷移 - 縁側プログラミング
https://engawapg.net/jetpack-compose/1393/screen-transition/
画面遷移には androidx.navigation.compose パッケージが必要なので導入する。
以下のページでバージョンを確認する。
Navigation | Jetpack | Android Developers
https://developer.android.com/jetpack/androidx/releases/navigation?hl=ja
現時点での安定板は「2.8.0」となっていた。
build.gradle の dependencies 内に以下を追加する。
implementation("androidx.navigation:navigation-compose:2.8.0")
なお、以前は以下の書き方で紹介されることが多かった。
※これはGroovy DSLで書かれたGradleファイル用の書き方。以前から使われているもの。
先のカッコありのものは、GradleがKotlin DSLで書かれている場合の書き方。新しいプロジェクトではKotlin DSLが推奨されている。
implementation "androidx.navigation:navigation-compose:2.8.0"
追加したら「Sync Now」をクリックする。
Jetpack Composeで画面遷移させる | mokelab tech sheets
https://tech.mokelab.com/android/compose/app/navigation/navigate.html
以下のとおり実装する。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
HelloWorldTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
MainScreen(modifier = Modifier.padding(innerPadding))
}
}
}
}
}
@Composable
fun MainScreen(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "FirstScreen") {
composable("FirstScreen") {
FirstScreen(navController = navController)
}
composable("SecondScreen") {
SecondScreen(navController = navController)
}
}
}
}
@Composable
fun FirstScreen(navController: NavController) {
Column {
Text("スクリーンA")
Button(onClick = { navController.navigate("SecondScreen") }) {
Text("スクリーンBへ")
}
}
}
@Composable
fun SecondScreen(navController: NavController) {
Column {
Text("スクリーンB")
Button(onClick = { navController.navigateUp() }) {
Text("スクリーンAへ")
}
}
}
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
MainScreen()
//FirstScreen()
//SecondScreen()
}
上のように navController を渡すのは明快ではあるが、テストのことを考えると推奨されないらしい。
以下のようにすることが推奨されるらしい。
Compose を使用したナビゲーション | Jetpack Compose | Android Developers
https://developer.android.com/jetpack/compose/navigation?hl=ja
@Composable
fun MainScreen(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "FirstScreen") {
composable("FirstScreen") {
FirstScreen(
onNavigateToSecondScreen = { navController.navigate("SecondScreen") },
onNavigateToThirdScreen = { navController.navigate("ThirdScreen") }
)
}
composable("SecondScreen") {
SecondScreen(
onNavigateToThirdScreen = { navController.navigate("ThirdScreen") }
)
}
composable("ThirdScreen") {
ThirdScreen()
}
}
}
}
@Composable
fun FirstScreen(onNavigateToSecondScreen: () -> Unit, onNavigateToThirdScreen: () -> Unit) {
Column {
Text("スクリーン1")
Button(onClick = onNavigateToSecondScreen) {
Text("スクリーン2へ")
}
Button(onClick = onNavigateToThirdScreen) {
Text("スクリーン3へ")
}
}
}
@Composable
fun SecondScreen(onNavigateToThirdScreen: () -> Unit) {
Column {
Text("スクリーン2")
Button(onClick = onNavigateToThirdScreen) {
Text("スクリーン3へ")
}
}
}
@Composable
fun ThirdScreen() {
Column {
Text("スクリーン3")
}
}
Jetpack Compose入門(15) 画面遷移 - 縁側プログラミング
https://engawapg.net/jetpack-compose/1393/screen-transition/
以下のようにすると、画面遷移の際に引数を受け渡しできる。
@Composable
fun MainScreen(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "FirstScreen") {
composable("FirstScreen") {
FirstScreen(
onNavigateToSecondScreen = { navController.navigate("SecondScreen") },
onNavigateToThirdScreen = { navController.navigate("ThirdScreen") },
onNavigateToProfileScreen = { userId, message -> navController.navigate("ProfileScreen/$userId/$message") }
)
}
composable("SecondScreen") {
SecondScreen(
onNavigateToThirdScreen = { navController.navigate("ThirdScreen") }
)
}
composable("ThirdScreen") {
ThirdScreen()
}
composable(
"ProfileScreen/{userId}/{message}",
arguments = listOf(
navArgument("userId") { type = NavType.IntType },
navArgument("message") { type = NavType.StringType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
val message = backStackEntry.arguments?.getString("message") ?: ""
ProfileScreen(
userId,
message,
onNavigateToFirstScreen = { navController.navigate("FirstScreen") }
)
}
}
}
}
@Composable
fun FirstScreen(
onNavigateToSecondScreen: () -> Unit,
onNavigateToThirdScreen: () -> Unit,
onNavigateToProfileScreen: (Int, String) -> Unit
) {
Column {
Text("スクリーン1")
Button(onClick = onNavigateToSecondScreen) {
Text("スクリーン2へ")
}
Button(onClick = onNavigateToThirdScreen) {
Text("スクリーン3へ")
}
Text("プロフィール")
Button(onClick = { onNavigateToProfileScreen(1, "テスト1") }) {
Text("プロフィール1へ")
}
Button(onClick = { onNavigateToProfileScreen(2, "テスト2") }) {
Text("プロフィール2へ")
}
Button(onClick = { onNavigateToProfileScreen(3, "テスト3") }) {
Text("プロフィール3へ")
}
}
}
@Composable
fun SecondScreen(onNavigateToThirdScreen: () -> Unit) {
Column {
Text("スクリーン2")
Button(onClick = onNavigateToThirdScreen) {
Text("スクリーン3へ")
}
}
}
@Composable
fun ThirdScreen() {
Column {
Text("スクリーン3")
}
}
@Composable
fun ProfileScreen(
userId: Int = 0,
message: String = "text",
onNavigateToFirstScreen: () -> Unit
) {
Column {
Text("プロフィール $userId $message")
Button(onClick = onNavigateToFirstScreen) {
Text("スクリーン1へ")
}
}
}
画面遷移については、以下などを参考に引き続き確認したい。
Jetpack Composeで画面遷移させる | mokelab tech sheets
https://tech.mokelab.com/android/compose/app/navigation/navigate.html
JetpackComposeでQiitaのクライアントアプリを作ろう|Masato Ishikawa
https://note.com/masato1230/n/n743532de2d84
[Jetpack Compose] NavigationBar と Nested Navigation
https://zenn.dev/ykrods/articles/580bc1fda58081
JetpackComposeのNavigation Componentを触ったのでまとめる - Qiita
https://qiita.com/b4tchkn/items/55b1892ed725297eefe3
Jetpack Composeにおける画面遷移とは? - dely Tech Blog
https://tech.dely.jp/entry/2021/12/17/170000
【シンプルサンプル】AndroidStudio 画面遷移 - Qiita
https://qiita.com/kiyoZy/items/259699222ae1fec65a8f
Jetpack Compose入門(15) 画面遷移 - 縁側プログラミング
https://engawapg.net/jetpack-compose/1393/screen-transition/
Android Navigation Compose 導入 - Gunosy Tech Blog
https://tech.gunosy.io/entry/android_navigation_compose
■Jetpack Composeでリスト表示
Jetpack Compose入門(17) リスト - 縁側プログラミング
https://engawapg.net/jetpack-compose/1442/list/
簡単なリスト。
ただし以下の場合、要素数に応じたUIが最初に用意されるため、要素が多くなればなるほどパフォーマンスが低下する。
また、リストの要素数が多くなって画面からはみ出しても、リストをスクロールして表示することはできない。
val fruits = listOf("リンゴ", "オレンジ", "グレープ", "ピーチ", "ストロベリー")
Column(modifier = modifier) {
fruits.forEach { fruit ->
Text(text = "これは $fruit です。")
}
}
このような場合、LazyColumnを使って以下のように記述する。
LazyColumnを使えば表示に必要な分だけ展開されるため、要素数が多くなってもパフォーマンスは低下しない。
また、リストをスクロールして表示することもできる。
val fruits = listOf("リンゴ", "オレンジ", "グレープ", "ピーチ", "ストロベリー")
LazyColumn {
items(fruits) { fruit ->
Text(text = "これは $fruit です。")
}
}
複数セットのデータを扱うリスト。
data class Fruits(val english: String, val japanese: String)
val fruits = listOf(
Fruits("Apple", "リンゴ"),
Fruits("Orange", "オレンジ"),
Fruits("Grape", "グレープ"),
Fruits("Peach", "ピーチ"),
Fruits("Strawberry", "ストロベリー"),
)
LazyColumn {
items(fruits) { fruit ->
Text("${fruit.english}は日本語で${fruit.japanese}です。")
}
}
タップでトーストを表示。
val context = LocalContext.current
data class Fruits(val english: String, val japanese: String)
val fruits = listOf(
Fruits("Apple", "リンゴ"),
Fruits("Orange", "オレンジ"),
Fruits("Grape", "グレープ"),
Fruits("Peach", "ピーチ"),
Fruits("Strawberry", "ストロベリー"),
)
LazyColumn {
itemsIndexed(fruits) { index, fruit ->
Text(
text = "${index}. ${fruit.english}",
modifier = Modifier.clickable {
Toast.makeText(context, "日本語で${fruit.japanese}です。", Toast.LENGTH_SHORT).show()
}
)
}
}
■テーマの適用
Jetpack Compose入門(18) テーマカラーの適用 - 縁側プログラミング
https://engawapg.net/jetpack-compose/1457/theme/
AndroidStudioが生成するデフォルトコードでは、プロジェクト名に応じたテーマが適用済みになっている。
src/main/java/net/refirio/helloworld/MainActivity.kt
package net.refirio.helloworld
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import net.refirio.helloworld.ui.theme.HelloWorldTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
HelloWorldTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
HelloWorldTheme {
Greeting("Android")
}
}
自動的に作成された HelloWorldTheme テーマが適用されている。
Greeting定義部分の「fun MainScreen(modifier: Modifier = Modifier) {」でデフォルト設定として扱うModifierを参照させている。
app\src\main\AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloWorld"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.HelloWorld">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2箇所ある「android:theme="@style/Theme.HelloWorld"」部分でテーマを適用している。
app\src\main\java\net\refirio\helloworld\ui\theme\Theme.kt
package net.refirio.helloworld.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun HelloWorldTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
テーマファイルも、Kotlinで書かれたプログラムとなっている。
lightColorSchemeの内容を調整することで、配色を変更できる…が、そのままだと適用されない。
さらに以下の部分をコメントアウトもしくは削除すると、色の変更が反映される。
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
【Jetpack Compose】Material3でテーマカラーを変更する際に陥りがちなミス - Qiita
https://qiita.com/Nagumo-7960/items/8699f7670bff4cc7a137