たくさんの自由帳
Androidのお話
たくさんの自由帳
投稿日 : | 0 日前
文字数(だいたい) : 5981
追記:2021/07/15:1.0.0-rc02が出てます。
追記:2021/07/14:1.0.0-rc01が出てます。
正式リリースも近い。あとなんかAndroid Studioのプレビュー機能が4んでるみたいだから、ui-tooling
だけはBeta09
のままがいいらしい?
詳細:https://stackoverflow.com/questions/68224361/jetpack-compose-cant-preview-after-updating-to-1-0-0-rc01
composeOptions {
kotlinCompilerVersion '1.5.10'
kotlinCompilerExtensionVersion '1.0.0-rc01'
}
追記:2021/06/26:Beta09が出てます。
それと、現状?AndroidStudioのテンプレートからEmpty Compose Activityを選択してそのままの状態で実行すると起動しません。KotlinとJetpackComposeのバージョンを上げる必要があります。初見殺しだろこれ
java.lang.NoSuchMethodError: No static method copy-H99Ercs$default
build.gradle
(appフォルダではない方)
buildscript {
ext {
compose_version = '1.0.0-beta09' // beta09へ
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.0-beta04"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10" // 1.5.10へ
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
app/build.gradle
(appフォルダの方のbuild.gradle)
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.10' // 1.5.10へ
}
これで実行できると思います。
追記:2021/06/06:知らん間にJetpack Compose Beta08がリリースされました。Kotlinのバージョンを1.5.10にする必要があります。
composeOptions {
kotlinCompilerVersion '1.5.10'
kotlinCompilerExtensionVersion '1.0.0-beta08'
}
あと Compose版マテリアルデザインライブラリ
のリファレンスが親切になっていました。Google本気やん
https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#overview
【割と破壊的仕様変更】beta08
からSurface()
とCard()
のクリックイベントはModifier
経由ではなく、onClick
の引数を使うようになりました。
Surface(modifier = Modifier.clickable { }) {
}
Surface(onClick = { }) {
}
そしてSurface()
、Card()
でonClick
引数を使う際は、@ExperimentalMaterialApi
をComposeな関数に付ける必要があります。
@ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Surface(onClick = { }) {
}
}
}
追記:2021/05/22:Jetpack Composeがいつの間にかbeta07まで進んでました。
あとGoogle I/Oで言ってたけどホーム画面のウイジェットもJetpack Composeで書けるようになるとかなんとか
追記:2021/04/11:Beta04がリリースされました。多分Kotlinのバージョンを上げる必要があります。
composeOptions {
kotlinCompilerVersion '1.4.32'
kotlinCompilerExtensionVersion '1.0.0-beta04'
}
追記:2021/03/25:Beta03がリリースされました。AndroidView
の問題が修正されています。
ついでに、Android 7以前?で起きてたスクロール時にAndroidView
がずれる問題も直ってました。
追記:2021/03/13:Beta02がリリースされました。一緒にKotlinのバージョンを1.4.31
にする必要があります。
ComposeView
の問題が修正されました(多分)。
(なお今度はスクロール時にAndroidView()
がずれるようになった模様)
composeOptions {
kotlinCompilerVersion '1.4.31'
kotlinCompilerExtensionVersion '1.0.0-beta02'
}
また、AppCompat
、Fragment
のライブラリのバージョンをそれぞれ1.3
以上にする必要があります。
dependencies {
implementation 'androidx.fragment:fragment-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.3.0-beta01'
}
追記 2021/03/02: Jetpack Compose Beta がリリースされました!。ついに(待望の)ベータ版になります
追記 2021/02/15:Jetpack Composeのバージョンがalpha 12
になりました。
影響があったといえば、
vectorResource
が非推奨。painterResource
を使うように。
Icon
へDrawableを渡すときの引数はimageVector
ではなく、painter
になります。Icon(
painter = painterResource(R.drawable.ic_outline_open_in_browser_24px),
contentDescription = "ブラウザ起動"
)
Context取得時に使う、AmbientContext.current
がLocalContext.current
に変更になりました。
Android Studioが対応してないのか、Kotlinのバージョンを1.4.30にしても、バージョンが古いので利用できませんってIDEに言われます。
'padding(Dp): Modifier' is only available since Kotlin 1.4.30 and cannot be used in Kotlin 1.4
Languages & Frameworks
へ進み、Kotlin
を選び、Update Channel
をEarly Access Preview 1.4.x
にしてInstall
を押せばいいらしい。
追記:Icon
の増えてるのでコピペじゃ動かなくなりました。
contentDescription
という文字列を入れる引数が増えてます。ので、コピペしたらIcon
の引数を足してください。以下のように
Icon(
imageVector = Icons.Outlined.Home,
contentDescription = "アイコンの説明。なければnullでもいい"
)
この続きです。そのうち追記しに来る
https://takusan.negitoro.dev/posts/android_jc/
Scaffold { }
で囲ってあげる必要があります。
Snackbar表示以外でもアプリバーとかドロワーの表示でも使うので置いておいて損はないはず。
また、Compose内で利用できるコルーチン(rememberCoroutineScope()
)を利用する必要があります。
@ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
val state = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = state,
topBar = {
TopAppBar() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxHeight().padding(10.dp),
) {
Text(text = "ブログ一覧")
}
}
}
) {
OutlinedButton(onClick = {
scope.launch {
val result = state.snackbarHostState.showSnackbar(
message = "Snackbar表示",
actionLabel = "押せます",
duration = SnackbarDuration.Short,
)
// 押せたかどうか
if (result == SnackbarResult.ActionPerformed) {
Toast.makeText(this@MainActivity, "押せました!", Toast.LENGTH_SHORT).show()
}
}
}) {
Text(text = "Snackbar表示")
}
}
}
}
}
実行結果
参考にしました:https://gist.github.com/gildor/82ec960cc0c5873453f024870495eab3
リソース取得等は用意されてるけど、それ以外でContextを使いたい場合はこうです!
@Composable
fun needContext() {
val context = LocalContext.current
}
なんかコルーチン、2種類あるっぽい。
なんか警告出てたので追記(2021/04/19)
これは@Composable
がついた関数内でしか呼べません。
Button()
やText()
を置く感じで使うことになります。
@Composable
fun TimerText() {
val timerCount = remember { mutableStateOf(0) }
/**
* ここはコンポーザブルのスコープ内
*
* Button()やText()が設置可能
* */
LaunchedEffect(key1 = Unit, block = {
while (true) {
timerCount.value += 1
delay(1000)
}
})
Text(text = "${timerCount.value} 秒経過")
}
また、key1
の中身が変わると、今のコルーチンはキャンセルされ、新しいコルーチンが起動するようになっています。
@Composable
fun TimerText() {
val timerCount = remember { mutableStateOf(0) }
val isRunning = remember { mutableStateOf(false) }
/**
* Button()やText()が設置可能な場所
*
* Composable内で利用できるコルーチン
*
* key1が変更されると、既存のコルーチンはキャンセルされ、新しくコルーチンが起動する
* */
LaunchedEffect(key1 = isRunning.value, block = {
timerCount.value = 0
while (isRunning.value) {
timerCount.value += 1
delay(1000)
}
})
Button(onClick = {
isRunning.value = !isRunning.value
}) {
if (isRunning.value) {
Text(text = "${timerCount.value} 秒経過")
} else {
Text(text = "タイマー開始")
}
}
}
じゃあrememberCoroutineScope
はなんだよって話ですが、これはComposable
な関数ではないところで使うのが正解らしいです。
(例えば、ボタンを押したときに呼ばれる関数はComposableな関数
ではない)
@Composable
fun RememberCoroutine() {
val scope = rememberCoroutineScope()
val context = LocalContext.current
Button(onClick = {
// ここはComposableな関数ではない
scope.launch {
// Toast表示が終わるまで一時停止する
suspendToast(context)
println("終了")
}
}) {
Text(text = "rememberCoroutineScope")
}
}
/** Toast表示が終わるまで一時停止する関数 */
suspend fun suspendToast(context: Context) {
Toast.makeText(context, "rememberCoroutineScope", Toast.LENGTH_SHORT).show()
delay(2 * 1000) // 2秒ぐらい
}
あってるか分からないので、詳しくは公式で
https://developer.android.com/jetpack/compose/lifecycle
Color.parseColor()
がそのままでは使えないので、androidx.compose.ui.graphics
の方のColor
の引数に入れてあげます。
Text(
text = AnniversaryDate.makeAnniversaryMessage(anniversary),
color = Color(android.graphics.Color.parseColor("#252525"))
)
まずはThemeColor.kt
みたいな色だけを書いておくクラスを作ってはりつけ
なんかisDarkMode
に@Composable
を付ける理由はわかりません。サンプルコードがそうなってたので便乗
@Composable
をつけるとLocalContext
等へアクセスできる。
つけない場合だと(今回の場合は)引数にContext
が必要になる。なるほどなあ
// 引数にContextが必要
fun isDarkMode(context: Context) {}
// いらない
@Composable
fun isDarkMode() {
val context = LocalContext.current
}
ここから例です
/**
* [MaterialTheme]に渡すテーマ。コードでテーマの設定ができるってマジ?
* */
/** ダークモード。OLED特化 */
val DarkColors = darkColors(
primary = Color.White,
secondary = Color.Black,
)
/** ライトテーマ */
val LightColors = lightColors(
primary = Color(android.graphics.Color.parseColor("#757575")),
primaryVariant = Color(android.graphics.Color.parseColor("#494949")),
secondary = Color(android.graphics.Color.parseColor("#a4a4a4")),
)
/** ダークモードかどうか */
@Composable
fun isDarkMode(): Boolean {
// ComposableをつけるとLocalContext等、Composable内でしか呼べない関数を呼べる(それはそう)
// 今回はContextを引数に取らなくてもLocalContextを使うことが出来た
val context = LocalContext.current
val conf = context.resources.configuration
val nightMode = conf.uiMode and Configuration.UI_MODE_NIGHT_MASK
return nightMode == Configuration.UI_MODE_NIGHT_YES // ダークモードなら true
}
そしたらMaterialTheme { }
に渡してあげます。if文を一行で書く
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme(
// 色の設定
colors = if (isDarkMode(AmbientContext.current)) DarkColors else LightColors
) {
Scaffold {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
// この2つ、ダークモードなら白色、それ以外なら黒色になるはず
Text(text = "もじのいろ")
Icon(imageVector = Icons.Outlined.Home)
}
}
}
}
}
}
ちゃんと動けばダークモードのときは真っ暗になると思います。AOD
ちなみに黒基調にするとIcon()
等が勝手に検知してアイコンの色を白色に変更してくれるそうです。ダークモード対応の手間が減る
見つけたので報告しますね。そんなに難しくない。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme(
// 色の設定
colors = if (isDarkMode(AmbientContext.current)) DarkColors else LightColors
) {
Scaffold {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
// 選択中タブ
var selectTabIndex by remember { mutableStateOf(0) }
TabLayout(
selectTabIndex = selectTabIndex,
tabClick = { index -> selectTabIndex = index }
)
Text(text = "選択中:$selectTabIndex")
}
}
}
}
}
}
/**
* タブレイアウト
*
* @param selectTabIndex 選択するタブを入れてね
* @param tabClick タブを押した時
* */
@Composable
fun TabLayout(selectTabIndex: Int, tabClick: (Int) -> Unit) {
TabRow(
modifier = Modifier.padding(10.dp),
selectedTabIndex = selectTabIndex,
backgroundColor = Color.Transparent,
) {
Tab(selected = selectTabIndex == 0, onClick = {
tabClick(0)
}) {
Icon(imageVector = Icons.Outlined.Android)
Text(text = "Android 9")
}
Tab(selected = selectTabIndex == 1, onClick = {
tabClick(1)
}) {
Icon(imageVector = Icons.Outlined.Android)
Text(text = "Android 10")
}
Tab(selected = selectTabIndex == 2, onClick = {
tabClick(2)
}) {
Icon(imageVector = Icons.Outlined.Android)
Text(text = "Android 11")
}
}
}
動作結果
MaterialTheme
のcolors
の部分を変えることでテーマを切り替えられるようになりました。これ従来のレイアウトじゃできないからComposeの強みじゃない?
まずは色の情報を置いておくクラスを作成して、以下をコピペします。
ThemeColor.kt
/** ダークモード。OLED特化 */
val DarkColors = darkColors(
primary = Color.White,
secondary = Color.Black,
)
/** ライトテーマ */
val LightColors = lightColors(
primary = Color(android.graphics.Color.parseColor("#FF6200EE")),
primaryVariant = Color(android.graphics.Color.parseColor("#FF3700B3")),
secondary = Color(android.graphics.Color.parseColor("#FFFFFF")),
)
/** 青基調 */
val blueTheme = lightColors(
primary = Color(android.graphics.Color.parseColor("#0277bd")),
primaryVariant = Color(android.graphics.Color.parseColor("#58a5f0")),
secondary = Color(android.graphics.Color.parseColor("#004c8c")),
)
/** 赤基調 */
val redTheme = lightColors(
primary = Color(android.graphics.Color.parseColor("#c2185b")),
primaryVariant = Color(android.graphics.Color.parseColor("#8c0032")),
secondary = Color(android.graphics.Color.parseColor("#fa5788")),
)
/** 緑基調 */
val greenTheme = lightColors(
primary = Color(android.graphics.Color.parseColor("#1b5e20")),
primaryVariant = Color(android.graphics.Color.parseColor("#003300")),
secondary = Color(android.graphics.Color.parseColor("#4c8c4a")),
)
/** ダークモードかどうか */
@Composable
fun isDarkMode(context: Context): Boolean {
val conf = context.resources.configuration
val nightMode = conf.uiMode and Configuration.UI_MODE_NIGHT_MASK
return nightMode == Configuration.UI_MODE_NIGHT_YES // ダークモードなら true
}
その後に書いていきます。ダークモードを一緒に書いた人はこっから掛けばいいです。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// デフォルト
val defaultTheme = if (isDarkMode(AmbientContext.current)) DarkColors else LightColors
// 色を保持する
val themes = remember { mutableStateOf(defaultTheme) }
MaterialTheme(
// 色の設定
colors = themes.value
) {
Scaffold(
topBar = {
TopAppBar() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxHeight().padding(10.dp),
) {
Text(text = "動的テーマ")
}
}
}
) {
// テーマ切り替え
DynamicThemeButtons(themeClick = { themes.value = it})
}
}
}
}
}
/**
* 動的にテーマを切り替える。
*
* @param themeClick ボタンを押したときに呼ばれる。引数にはテーマ([Colors])が入ってる
* */
@Composable
fun DynamicThemeButtons(
themeClick: (Colors) -> Unit,
) {
// デフォルト
val defaultTheme = if (isDarkMode(context = AmbientContext.current)) DarkColors else LightColors
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Button(
modifier = Modifier.padding(5.dp),
onClick = { themeClick(blueTheme) }
) {
Text(text = "青")
}
Button(modifier = Modifier.padding(5.dp),
onClick = { themeClick(redTheme) }
) {
Text(text = "赤")
}
Button(modifier = Modifier.padding(5.dp),
onClick = { themeClick(greenTheme) }
) {
Text(text = "緑")
}
Button(modifier = Modifier.padding(5.dp),
onClick = { themeClick(defaultTheme) }
) {
Text(text = "デフォルト")
}
}
}
実行結果
ボタンを押すと色が切り替わると思います。
AnimatedVisibility
ってのがあります。
class MainActivity : AppCompatActivity() {
@ExperimentalAnimationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// デフォルト
val defaultTheme = if (isDarkMode(AmbientContext.current)) DarkColors else LightColors
// 色を保持する
val themes = remember { mutableStateOf(defaultTheme) }
MaterialTheme(
// 色の設定
colors = themes.value
) {
VisibilityAnimationSample()
}
}
}
}
@ExperimentalAnimationApi
@Composable
fun VisibilityAnimationSample() {
// 表示するか
val isShow = remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
AnimatedVisibility(visible = isShow.value) {
// この中に書いたやつがアニメーションされながら表示される
Column {
// 10個ぐらい
repeat(10) {
Icon(imageVector = Icons.Outlined.Android, modifier = Modifier.rotate(90f * it))
}
}
}
// 表示、非表示切り替え
Button(onClick = { isShow.value = !isShow.value }) {
Text(text = "アニメーションさせながら表示")
}
}
}
画像じゃわからんけど、ちゃんとアニメーションされてます。
android:gravity="right"
をJetpack Composeでもやりたいわけですね。重要な点はfillMaxWidth()
を使うところです
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GravityRight()
}
}
}
@Composable
fun GravityRight() {
Column {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
Text(text = "右に寄ってる Row")
IconButton(onClick = { /*TODO*/ }) {
Icon(imageVector = Icons.Outlined.Adb)
}
}
Divider() // 区切り
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.Center
) {
Text(text = "右に寄ってる Column")
IconButton(onClick = { /*TODO*/ }) {
Icon(imageVector = Icons.Outlined.Adb)
}
}
}
}
こうなるはず
LinearLayout
と同じようにweight
を設定すればできます。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WeightSample()
}
}
}
@Composable
fun WeightSample() {
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(modifier = Modifier.weight(1f).padding(5.dp), onClick = { /*TODO*/ }) {
Text(text = "ぼたんだよー")
}
Button(modifier = Modifier.weight(1f).padding(5.dp), onClick = { /*TODO*/ }) {
Text(text = "ぼたんだよー")
}
Button(modifier = Modifier.weight(1f).padding(5.dp), onClick = { /*TODO*/ }) {
Text(text = "ぼたんだよー")
}
}
}
こうなるはず
埋めたい部品に対してweight(1f)
を足してあげることで、他の部品の事を考えながら埋めたい部品で埋めてくれます。
こっちも親要素にfillMaxWidth()
を指定してあげる必要があります。(画面いっぱい使うなら)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MessageSendUI()
}
}
}
@Composable
fun MessageSendUI() {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val message = remember { mutableStateOf("") }
// この部品を最大まで広げたい
OutlinedTextField(
value = message.value,
onValueChange = { message.value = it },
modifier = Modifier
.padding(5.dp)
.weight(1f)
)
IconButton(
onClick = { /*TODO*/ },
modifier = Modifier.padding(5.dp)
) {
Icon(imageVector = Icons.Outlined.Send)
}
}
}
こうなるはず
Jetpack Compose
を書いてる人のサンプルには勝てないというわけでソースコード
の探し方でも。
Button
とかOutlinedButton
とかMaterialTheme
の部分でCtrl 押しながら クリック
することで飛べます。
こんなのが出ると思う
を開きます。
さっきの画像だと、
@sample androidx.compose.material.samples.OutlinedButtonSample
のところですね
で、何を検索欄に入れればいいんだって話ですが、さっき見つけた@sample ~
の部分、
最後から.
までの部分を入力します。
@sample androidx.compose.material.samples.OutlinedButtonSample
だと、OutlinedButtonSample
がそうです。
早速検索欄に入れましょう
検索欄に入れたらおそらく一番最初のサジェストが使い方の例になってると思います。
あとは読んでいくしか無いです。
今までやったこと(だいたい)を一つのアプリにしてみました。どうぞ。
https://github.com/takusan23/JetpackComposeSampleApp
https://github.com/takusan23/JetpackComposeSampleApp/releases/tag/1.0