追記 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
を押せばいいらしい。- ここらへん参照:https://github.com/android/compose-samples/pull/387#issuecomment-777515590
追記:Icon
の増えてるのでコピペじゃ動かなくなりました。
contentDescription
という文字列を入れる引数が増えてます。ので、コピペしたらIcon
の引数を足してください。以下のように
Icon(
imageVector = Icons.Outlined.Home,
contentDescription = "アイコンの説明。なければnullでもいい"
)
この続きです。そのうち追記しに来る
https://takusan.negitoro.dev/posts/android_jc/
Snackbar表示
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取得
リソース取得等は用意されてるけど、それ以外でContextを使いたい場合はこうです!
@Composable
fun needContext() {
val context = LocalContext.current
}
コルーチンは?
val scope = rememberCoroutineScope()
scope.launch {
}
テーマとか文字の色にカラーコードを使いたい!
Color.parseColor()
がそのままでは使えないので、androidx.compose.ui.graphics
の方のColor
の引数に入れてあげます。
Text(
text = AnniversaryDate.makeAnniversaryMessage(anniversary),
color = Color(android.graphics.Color.parseColor("#252525"))
)
ダークモード
まずはThemeColor.kt
みたいな色だけを書いておくクラスを作ってはりつけ
なんかisDarkMode
に@Composable
を付ける理由はわかりません。サンプルコードがそうなってたので便乗
/**
* [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(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
}
そしたら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
を書いてる人のサンプルには勝てないというわけでソースコード
の探し方でも。
1.Android Studioで使いたいUI部品のソースコードを開く
Button
とかOutlinedButton
とかMaterialTheme
の部分でCtrl 押しながら クリック
することで飛べます。
こんなのが出ると思う
2.ブラウザで
を開きます。
3.@sample の部分を探します。
さっきの画像だと、
@sample androidx.compose.material.samples.OutlinedButtonSample
のところですね
4.検索欄に入れる
で、何を検索欄に入れればいいんだって話ですが、さっき見つけた@sample ~
の部分、
最後から.
までの部分を入力します。
@sample androidx.compose.material.samples.OutlinedButtonSample
だと、OutlinedButtonSample
がそうです。
早速検索欄に入れましょう
5.コードを読み解く
検索欄に入れたらおそらく一番最初のサジェストが使い方の例になってると思います。
あとは読んでいくしか無いです。
サンプルアプリ
今までやったこと(だいたい)を一つのアプリにしてみました。どうぞ。
https://github.com/takusan23/JetpackComposeSampleApp
ダウンロード
https://github.com/takusan23/JetpackComposeSampleApp/releases/tag/1.0