メモ > 技術 > IDE: AndroidStudio > アプリの作成(Jetpack Compose)
アプリの作成(Jetpack Compose)
■Jetpackについて
Android Jetpackとは、アプリを作成するためのコンポーネントやツールなどをひとまとめにしたもの
AndroidのOSバージョンアップからは切り離されているので、OSのアップデートを待たずに迅速に対応できるようになっている
一例だが、以下のような機能が提供されている
appsearch … ユーザー向けにカスタムのアプリ内検索機能を構築する
camera … モバイルカメラアプリを構築する
compose … 形状とデータの依存関係を記述するコンポーズ可能な関数を使用して、UIをプログラムで定義する
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")
}
}
以前に比べて 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時点)
- - - - - - - - - - - - - - - - - - - -
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()
}
- - - - - - - - - - - - - - - - - - - -
■ハローワールド(Flamingo時点)
src/main/java/net/refirio/helloworld/MainActivity.kt (Androidビューでは app/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")
}
}
build.gradle.kts
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
}
app\build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.helloworld"
compileSdk = 33
defaultConfig {
applicationId = "com.example.helloworld"
minSdk = 24
targetSdk = 33
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.4.3"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.0")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui: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>
■練習用にテーマを外した場合(Flamingo時点)
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()
}
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"
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>
■プレビューの表示をリッチにする
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を専用の関数にまとめる
@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.5.3」となっていた
build.gradle の dependencies 内に以下を追加する
implementation "androidx.navigation:navigation-compose:2.5.3"
追加したら「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)
setContent {
MainScreen()
}
}
}
@Composable
fun MainScreen() {
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(showSystemUi = true)
@Composable
fun MainScreenPreview() {
MainScreen()
//FirstScreen()
//SecondScreen()
}
上のように navController を渡すのは明快ではあるが、テストのことを考えると推奨されないらしい
以下のようにすることが推奨されるらしい
Compose を使用したナビゲーション | Jetpack Compose | Android Developers
https://developer.android.com/jetpack/compose/navigation?hl=ja
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen()
}
}
}
@Composable
fun MainScreen() {
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/
以下のようにすると、画面遷移の際に引数を受け渡しできる
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen()
}
}
}
@Composable
fun MainScreen() {
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/
■Jetpack Composeでリスト表示
Jetpack Compose入門(17) リスト - 縁側プログラミング
https://engawapg.net/jetpack-compose/1442/list/
簡単なリスト
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.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 {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreen()
}
}
}
}
}
@Composable
fun MainScreen(modifier: Modifier = Modifier) {
Text(
text = "Hello!",
modifier = modifier
)
}
@Preview(showSystemUi = true)
@Composable
fun MainScreenPreview() {
HelloWorldTheme {
MainScreen()
}
}
自動的に作成された HelloWorldTheme テーマが適用されている
MainScreen定義部分の「fun MainScreen(modifier: Modifier = Modifier) {」でデフォルトのModifierを参照させているが、これは特に修飾が適用されていないもの
例えばMainScreen呼び出し部分で「MainScreen(modifier = Modifier.padding(16.dp))」とすると、Textには16.pdのパディングが設定される
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.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
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
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
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
■画面領域の調整(未解決)
「Jetpack Composeの基本」の内容を試していると、エミュレータ、実機ともに表示が途中で途切れたようになる
…が、色々な要素を配置していくと全体が表示された…?
何か専用の指定があるのか、引き続き確認したい