LoginSignup
2
2

More than 1 year has passed since last update.

Compose By Exampleを読んでのまとめ

Last updated at Posted at 2021-06-03

概要

今回は、Google社で提供されているComposeのpathwayの一環として、Compose By Exampleを視聴した内容をまとめていきたいと思います。

参考リンク:https://www.youtube.com/watch?v=DDd6IOlH3io

Jetpack Composeとは?

キャプチャ.JPG
まず初めに、Jetpack Composeには以下のような定義があります。

  • モダンな宣言的UIツールキット
  • Kotlinに基づいている
  • Unbundled(個別に独立している)である

Feature.JPG

また、Compoeの一つの大きなポイントとして"Make the easy things easy and the hard things possible" というのがあり、動画内でも何度も語られていました。

Theming, Layout, Animation

サンプル

Github: https://github.com/android/compose-samples/tree/34a75fb3672622a3fb0e6a78adc88bbc2886c28f
Composeのサンプルとして、何種類かデモアプリが上記のリポジトリで提供されています。

※ビルドが失敗する場合は、Dependencies.ktの以下のラインを
const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-beta01"
このように変更してみてください↓
const val androidGradlePlugin = "com.android.tools.build:gradle:7.1.0-alpha01"

JetChat&Jetsurvey

jetchat.JPG
JetchatとJetsurveyプロジェクトは、TextInputやStateManagementなどの基本となるような要素が含まれており、Compose学習の入門用に推薦されているプロジェクトです。

Jetcaster

jetcaster.JPG

AdancedThemingやAnimationなどが組み込まれた、より複雑なUIのサンプルプロジェクトです。

Owl

owl.JPG
MaterialDesignやAnimationなどに、フォーカスしたサンプルプロジェクトです。

Theming(テーマ)

Jetpack ComposeではMaterialDesignに特化した実装が組み込まれています。、JetPackColorからダークテーマ対応までは "Make the easy things easy" と定義されています。

Themeに関して、color、typography、shapeの3つの引数を定義することによって、任意にカスタマイズすることも可能です。

MaterialTheme(
  colors = ...
  typography = ...
  shapes = ...
) {
}

サンプルプロジェクトのOwlではこのビルダー関数を使用して、各画面ごとにテーマを作成しています。カスタムThemeを使用することによって、DarkThemeにも柔軟に対応することが可能になっています。
multitheme.JPG

例)https://github.com/android/compose-samples/blob/34a75fb3672622a3fb0e6a78adc88bbc2886c28f/Owl/app/src/main/java/com/example/owl/ui/theme/Theme.kt#L48

Color

Composeでは、下記のMaterial color stytemをモデリングしたクラスが用意されています。
colorsystem.JPG
コードで表すとこちら↓

val colors = Colors(
  primary = ...
  primaryVariant = ...

Material Themeでは、Colorsクラスを継承した、ベースラインのカラーパレットから作るデフォルトのビルダー関数もいくつか提供されており、色だけを変更したい場合などに用いることができます。

val lightColors = lightColors(
  primary = ...
  secondary = ...

Typography

同様に、Material ThemeでデフォルトのTypographyクラスが定義されており、ビルダー関数を用いてカスタマイズするための上書きもできます。

デフォルトで使用する場合↓
kotlin
val typography = Typography()

TextStyleを用いたカスタマイズの場合↓

val customTypography = Typography(
  h1 = TextStyle(
    fontFamily = Rubik,
    fontSize = 96.sp,
    fontWeight = FontWeight.Bold, 
    lineHeight = 120.sp,
  )
 h2 = ...
 subtitle = ...
 body = ...

図式化したものがこちら↓
font.JPG

Shape

Composeでは、small, medium, largeの3種類のサイズのコンポーネントを定義することによって、デフォルトまたはカスタムのShapeを作成することができます。それぞれのサイズの例として、ボタン、カード及びシートなどが挙げられます。

val shapes = Shapes(
  small = ...
  medium = ...
  large = ...

角丸にしたい場合↓(サイズとパーセントの両方でしていできます)

    small = RoundedCornerShape(size = 4.dp),

または 

    small = RoundedCornerShape(percent = 50),

左上の角をカットしたい場合。

small = CutCornerShape(topLeft = 16.dp)

推奨されるアプリでのTheme適応方法

以下のサンプルコードのように、Compasable関数の中に内包する事により、コード上で横断して使えるようにすることができます。

@Compose
fun YelloTheme(
  content: @Composable () -> Unit
) {
  MaterialTheme(
    colors = YellowLightColors,
    typography = OwlTypography, 
    ...
  )
}

なにもThemeが設定されていないコンテンツに対し
before.JPG

YellowThemeを適用した結果↓
after.JPG

以下のように記述することで、画面の一部分だけ違うテーマを適応することもできます。

fun CourseDetails(...) {
  PinkTheme {
    ...
    BlueTheme {
      RelatedCourses(...)
    }
  }
}

関連ページの部分だけ、Themeを変更した例↓
partial.JPG

テーマの要素について

MaterialThemeの要素には、それぞれ型安全にアクセスして利用できます。

Text(
  text = ...
  style = MaterialTheme.typography.subtitle1,
  color = MaterialTheme.colors.(ドットのあとはシステムが自動で補完してくれます)
)

色だけをコピーして利用することもできるので、色のためのハードコーディングを防止し、複数のテーマをサポートすることにも役立ちます。

val background =
MaterialTheme.colors.onSurface.copy(
  alpha = 0.2f
)
Surface(color = background) {...}

Smart Default

Surfaceコンポーネントを例として挙げた時、バックグラウンドの色を設定するとWrapされているコンテンツの色が自動でつくSmart Defaultが適用されます。例えば以下のサンプルではcolor = primaryと設定しているので、ラッピングされているコンテンツの色は自動的にprimary色が適用されます。
同様にComposableなコンポーネントでもこの仕組は適用されます(Floating Action Buttonなど)。

Surface(color = MaterialTheme.colors.primary) {
  // ここでのデフォルトカラーは `onPrimary` になる。
  Text(...)
  Button(...)
}
@Composable
fun FloatingActionButton(color = MaterialTheme.colors.secondary) {
  // ここでのデフォルトカラーは `secondary` になる。
}

ダークテーマ対応

isSystemInDarkTheme()でダークモードの判別をし、対応したカラーリストを渡すだけで完了です!
すごく便利!

@Composable
fun PinkTheme(
  darkTheme: Boolean = isSystemInDarkTheme(),
  content: @Composable () -> Unit
) {
  if (darkTheme) PinkDarkColors else PinkLightColors
...

(おまけ)
判別式では、どのようなことが行われているか調査してみました。
isSystemInDarkTheme()関数の中では、下記のような判定が行われています。

@Composable
@ReadOnlyComposable
fun isSystemInDarkTheme(): Boolean {
   val uiMode = LocalConfiguration.current.uiMode
   return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
}

Dynamic Theme(ダイナミックテーマ)

ここからの内容は、 Make the easy things easy and the hard things possiblethe hard things possibleに関わってくる部分の内容になります。

ダイナミックテーマのサンプルとしてJetcasterが用意されており、一番手前に表示されているアルバムに対応した色を取得して、それを動的にテーマに適用しています。
例。

既存のPaletteライブラリを使って画像からdominantColorを取得して、それを使っている。
これでアニメーションもできる。(軽く書いているけどすごい。。)

(動画内のコードと現状のコードでは、異なる場合があります)

val currentImage = ...
val palette = // Paletteライブラリを使って画像からpaletteを取得
val dominantColor = // paletteからdominant colorを取得

val colors = MaterialTheme.colors.copy(
  primary = animate(dominantColor),
)

MaterialTheme(colors = colors) {
  content()
}

Layout

  • Colum: コンポーネントを縦に配置する
  • Row: コンポーネントを横に配置する
  • Stack: コンポーネント同士を上に積み重ねる
  • ConstraintLayout: おなじみの制約でレイアウト

Modifierでクリックやpadding、toggleable、verticalScroll()、zoomable()などのイベントも付与することが可能です。

カスタムレイアウトを作るには?

以下のようなレイアウトをLayoutブロックを用いたカスタムレイアウトでサンプルアプリで実際に実装されています。このセクションでは切り出して別記事で記載しています。

image.png

カスタムレイアウトの紹介ページ:https://qiita.com/tmasa517/items/8bb39ba2d54f54dca567

Animation(アニメーション)

animateDpAsStateなどの、Compose側で用意されているanimate関数を使用してスムーズなアニメーションを行うことが可能です。

基本的なアニメーション構文

 var sizeState by remember { mutableStateOf(200.dp)}
 val size by animateDpAsState(
        targetValue = sizeState,
        tween(
            durationMillis = 1000
        )
    )
...
Box(modifier = Modifier
        .size(size)
        .background(color),
        contentAlignment = Alignment.Center){
        Button(onClick = { sizeState += 50.dp }) {
            Text("Increase Size")
        }
    }
...

サンプルリンク:https://github.com/tmasa517/ComposeDemo/blob/f22d54de74e9588a0945805f2eb70d76c28f9ce7/ComposeSampleSolution/app/src/main/java/com/example/composesample/AnimationTopic.kt#L22

Transition

Transtitionを用いて、各状態ごとに対応した値を設定でき、それに応じてのアニメーションの設定も可能とされています。

private enum class SelectionState { Unselected, Selected }

/**
* Class holding animating values when transitioning topic chip states.
*/
private class TopicChipTransition(
cornerRadius: State,
selectedAlpha: State,
checkScale: State
) {
val cornerRadius by cornerRadius
val selectedAlpha by selectedAlpha
val checkScale by checkScale
}

@Composable
private fun topicChipTransition(topicSelected: Boolean): TopicChipTransition {
val transition = updateTransition(
targetState = if (topicSelected) SelectionState.Selected else SelectionState.Unselected
)
val corerRadius = transition.animateDp { state ->
when (state) {
SelectionState.Unselected -> 0.dp
SelectionState.Selected -> 28.dp
}
}
val selectedAlpha = transition.animateFloat { state ->
when (state) {
SelectionState.Unselected -> 0f
SelectionState.Selected -> 0.8f
}
}
val checkScale = transition.animateFloat { state ->
when (state) {
SelectionState.Unselected -> 0.6f
SelectionState.Selected -> 1f
}
}

サンプルコード:https://github.com/android/compose-samples/blob/01d35510a3d5d72099ffbb2c6e25ef4782291d3d/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt#L164

今後Android Studioにアニメーションのinspectorが追加される

今後canaryのリリースで、以下のようにアニメーションをキーフレームごとで確認できる機能が追加される予定です。
image.png
https://www.youtube.com/watch?v=DDd6IOlH3io より

アニメーションのスクショ&テストができる

clockTestRuleにアクセスし、時間軸を操作することにより、スクショなどの比較などが可能です。また、それを用いてのアニメーションテストもできます。

    private fun compareTimeScreenshot(timeMs: Long, goldenName: String) {
        // Start with a paused clock
        composeTestRule.clockTestRule.pauseClock()

        // Start the unit under test
        showAnimatedCircle()

        // Advance clock (keeping it paused)
        composeTestRule.clockTestRule.advanceClock(timeMs) // 時間を操作する

        // Take screenshot and compare with golden image in androidTest/assets
        assertScreenshotMatchesGolden(goldenName, onRoot()) // 保存されている画像とスクショを比較する
    }
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2