Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AmebaのDesign SystemとCompose

AmebaのDesign SystemとCompose

Atsuhiro Iino

August 18, 2022
Tweet

Other Decks in Programming

Transcript

  1. Amebaのデザインシステム と Compose

  2. 自己紹介 飯野 敦博 めっし CyberAgent Ameba事業部 Androidエンジニア 0601messi 2021年度 新卒入社

  3. Index 3:まとめ 1:Compose導入にあたって 2:デザシスとCompose

  4. Index 3:まとめ 1:Compose導入にあたって 2:デザシスとCompose

  5. Compose導入にあたって 1.5.10 -> 1.6.10 1.1.1 Composeのバージョン選定 ・勉強会 ・新卒1年目の人とCompose推進 チームへの浸透 ・Remote

    Configでの制御 ・規模が大きいアプリのため段階公開を した 運用 ・コーディング規約 ・Activity, Fragment, GroupieのItem, XML単位での実装例 ドキュメント作り
  6. Index 3:まとめ 1:Compose導入にあたって 2:デザシスとCompose

  7. Spindleとは Amebaを作る全ての人に向けて、 「Amebaらしさ」を作るための約束事や、 それを手助けするツールやガイドラインをまと めたもの Amebaのデザインシステムとして2年前に開発 されました。

  8. Spindleとは デザイン原則 スタイルライブラリ コンポーネントライブラリ パフォーマンスガイドライン アクセシビリティガイドライン コンテンツガイドライン

  9. 詳しくはspindle.ameba.design

  10. Spindleとは デザイン原則 スタイルライブラリ コンポーネントライブラリ パフォーマンスガイドライン アクセシビリティガイドライン コンテンツガイドライン

  11. Spindleとは デザイン原則 スタイルライブラリ コンポーネントライブラリ パフォーマンスガイドライン アクセシビリティガイドライン コンテンツガイドライン

  12. スタイルライブラリとCompose ・Spindle Color ・Spindle Theme

  13. Spindle Color SpindleColorPaletteを定義 ColorChartにprimitiveな色を指定 実際にプロダクトで扱うのは ColorChart外にあるものを使いま す。 object SpindleColorPalette {

    private object ColorChart { val brandAmebaGreen: Color = Color(0xff2d8c3c) val white100: Color = Color(0xffffffff) val gray5: Color = Color(0xfff5f6f6) } // 実際に使うのは以下 val surfacePrimary: Color = ColorChart.white100 val surfaceSecondary: Color = ColorChart.gray5Alpha }
  14. Spindle Theme 独自のComposition Localを作成 Amebaアプリではダークテーマが存在し ないため、CompositoinLocackに設定さ れた値が変わる可能性が低いため、 今回はstaticCompositionLocalOfを利用 SpindlThemeの中にcolorsを定義する private

    val LocalSpindleColorPalette = staticCompositionLocalOf<SpindleColorPalette> { error("No spindle color palette is provided!") } object SpindleTheme { val colors: SpindleColorPalette @Composable @ReadOnlyComposable get() = LocalSpindleColorPalette.current }
  15. Spindle Theme SpindleThemeを定義し、 CompositionLocalProviderに設 定する。 @Composable fun SpindleTheme(content: @Composable ()

    -> Unit) { MaterialTheme { CompositionLocalProvider( LocalSpindleColorPalette provides SpindleColorPalette, content = content ) } }
  16. Spindleとは デザイン原則 スタイルライブラリ コンポーネントライブラリ パフォーマンスガイドライン アクセシビリティガイドライン コンテンツガイドライン

  17. コンポーネントライブラリとCompose ・Button ・Checkbox ・List ・Modal ・TextField etc

  18. コンポーネントライブラリとCompose ・Button ・Checkbox ・List ・Modal ・TextField etc

  19. SpindleButton パターン 見た目に応じてUI上での役割が変化します。 形態 ボタンの形態はレイアウトによって使い分けつつ、画面にお けるボタンの強弱をサイズによって現しています。

  20. Style Variation Contained Button Outlined Button Neutral Button Lighted Button

    Danger Button
  21. Size Variation

  22. Option IconButton

  23. 構成 Box |-Button |-Row |-Icon |-Spacer |-Text @Composable fun SpindleButton(

    text: String, buttonSize: SpindleButtonSize, buttonStyle: SpindleButtonStyle, onClick: () -> Unit, icon: Painter? = null ) { CompositionLocalProvider(LocalRippleTheme provides SpindleButtonRippleTheme(buttonStyle)) { Box(/*...*/) { Button(/*...*/) {} Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/) } } } }
  24. @Composable fun SpindleButton( text: String, buttonSize: SpindleButtonSize, buttonStyle: SpindleButtonStyle, onClick:

    () -> Unit, icon: Painter? = null ) { CompositionLocalProvider(LocalRippleTheme provides SpindleButtonRippleTheme(buttonStyle)) { Box(/*...*/) { Button(/*...*/) {} Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/) } } } } ボタンのサイズとスタイルを渡します
  25. ButtonのSize ・enumで定義 ・Textや幅の最大値、最小値 enum class SpindleButtonSize( val height: Dp, val

    minWidth: Dp, val maxWidth: Dp, val textSize: TextUnit, val borderSize: Dp, val iconSize: Dp, val iconEndMargin: Dp ) { Large(/*...*/), Medium(/*...*/), Small(/*...*/) }
  26. enum class SpindleButtonStyle( val backgroundColor: Int, val textColor: Int, val

    iconColor: Int, val rippleColor: Int, val rippleAlpha: Float, val borderColor: Int? = null ) { Contained(/*...*/), Outlined(/*...*/), Danger(/*...*/), Neutral(/*...*/), Lighted(/*...*/), Contained(/*...*/) } ButtonのStyle ・enumで定義 ・色、リップル、border
  27. @Composable fun SpindleButton( text: String, buttonSize: SpindleButtonSize, buttonStyle: SpindleButtonStyle, onClick:

    () -> Unit, icon: Painter? = null ) { CompositionLocalProvider(LocalRippleTheme provides SpindleButtonRippleTheme(buttonStyle)) { Box(/*...*/) { Button(/*...*/) {} Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/) } } } } リップルの設定
  28. class SpindleButtonRippleTheme( private val styleType: SpindleButtonStyle ) : RippleTheme {

    @Composable override fun defaultColor(): Color { return colorResource(id = styleType.rippleColor) } @Composable override fun rippleAlpha(): RippleAlpha { return RippleAlpha( draggedAlpha = 0f, focusedAlpha = 0f, hoveredAlpha = 0f, pressedAlpha = styleType.rippleAlpha ) } } ・Buttonの種類によってRipple のAlpha,Colorが違う ・先ほど定義した SpindleButtonStyleを渡してあ げる
  29. @Composable fun SpindleButton( text: String, buttonSize: SpindleButtonSize, buttonStyle: SpindleButtonStyle, onClick:

    () -> Unit, icon: Painter? = null ) { CompositionLocalProvider(LocalRippleTheme provides SpindleButtonRippleTheme(buttonStyle)) { Box(/*...*/) { Button(/*...*/) {} Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/) } } } } Boxで括る
  30. @Composable fun SpindleButton( text: String, buttonSize: SpindleButtonSize, buttonStyle: SpindleButtonStyle, onClick:

    () -> Unit, icon: Painter? = null ) { CompositionLocalProvider(LocalRippleTheme provides SpindleButtonRippleTheme(buttonStyle)) { Button(/*...*/) { Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/) } } } } ・Boxを使わずにButtonのcontentの中 に書くこともできた ・Spindleではボタンをタップした際に リップルは文字の下にくるように定義さ れている ・Rippleが文字にもかかってしまう
  31. @Composable fun SpindleButton( text: String, buttonSize: SpindleButtonSize, buttonStyle: SpindleButtonStyle, onClick:

    () -> Unit, icon: Painter? = null ) { CompositionLocalProvider(LocalRippleTheme provides SpindleButtonRippleTheme(buttonStyle)) { Box(/*...*/) { Button(/*...*/) {} Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/) } } } } ・Boxで括ることで、レイヤー分 けとButtonの上にText、Iconの レイヤーがくるようにした
  32. SpindleButton( text = "Button", buttonSize = SpindleButtonSize.Large, buttonStyle = SpindleButtonStyle.Contained,

    onClick = { /*...*/ }, iconRes = painterResource(id = R.drawable.plus) ) 実際の使い方
  33. 果たして本当にこれで良いのか?

  34. @Composable fun SpindleButton( text: String, buttonSize: SpindleButtonSize, buttonStyle: SpindleButtonStyle, onClick:

    () -> Unit, icon: Painter? = null ) { CompositionLocalProvider(LocalRippleTheme provides SpindleButtonRippleTheme(buttonStyle)) { Box(/*...*/) { Button(/*...*/) {} Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/) } } } } Stringを直接渡してるが、 Composable関数で渡した方が汎用的 ではないか?
  35. enum class SpindleButtonStyle( val backgroundColor: Int, val textColor: Int, val

    iconColor: Int, val rippleColor: Int, val rippleAlpha: Float, val borderColor: Int? = null ) { Contained(/*...*/), Outlined(/*...*/), Danger(/*...*/), Neutral(/*...*/), Lighted(/*...*/), Contained(/*...*/) } Styleをenumで渡してるがborderColor 不要の時にnullと余分なものを定義する ことになるけど良いのか?
  36. ComposeのButton ・Button ・TextButton ・OutlinedButton ・IconButton など 今回Buttonのみで全てのSpindleスタイルに対応させていた

  37. 改善 ・textをStringで渡してた部分 ->汎用性はなくなるが、色やsizeは固定化されたSpindleに準じて使ってもらうため Stringのを渡す形のままで ・Buttonのみで全てのStyleに対応 ->ButtonとOutlinedButtonを分ける ->それに伴いStyleをenumで渡す形ではなく、1つ1つButtonの種類にあった componentを作成

  38. 改善 ・textをStringで渡してた部分 ->汎用性はなくなるが、色やsizeは固定化されたSpindleに準じて使ってもらうため Stringのを渡す形のままで ・Buttonのみで全てのStyleに対応 ->ButtonとOutlinedButtonを分ける ->それに伴いStyleをenumで渡す形ではなく、1つ1つButtonの種類にあった componentを作成

  39. Style Variation Contained Button Neutral Button Lighted Button OutlinedButton Outlined

    Button Danger Button Button
  40. @Composable fun SpindleButton( text: String, buttonSize: SpindleButtonSize, onClick: () ->

    Unit, modifier: Modifier = Modifier, textColor: Color = SpindleTheme.colors.textHighEmphasisInverse, backgroundColor: Color = SpindleTheme.colors.surfaceAccentPrimary, rippleColor: Int = R.color.contained_button_ripple, rippleAlpha: Float = 0.2f, icon: Painter? = null, iconColor: Color = SpindleTheme.colors.objectHighEmphasisInverse ) { CompositionLocalProvider(/*...*/) { Box(/*...*/) { Button(/*...*/) { } SpindleButtonIconText(/*...*/) } } } Buttonで作成
  41. @Composable fun SpindleOutlinedButton( text: String, buttonSize: SpindleButtonSize, onClick: () ->

    Unit, modifier: Modifier = Modifier, textColor: Color = SpindleTheme.colors.textAccentPrimary, borderColor: Color = SpindleTheme.colors.borderAccentPrimary, rippleColor: Int = R.color.outlined_button_ripple, rippleAlpha: Float = 1f, icon: Painter? = null, iconColor: Color = SpindleTheme.colors.objectAccentPrimary ) { CompositionLocalProvider(/*...*/) { Box(/*...*/) { OutlinedButton(/*...*/) {} SpindleButtonIconText(/*...*/) } } } OutlinedButtonで作成
  42. @Composable fun SpindleNeutralButton( text: String, buttonSize: SpindleButtonSize, onClick: () ->

    Unit, modifier: Modifier = Modifier, icon: Painter? = null ) = SpindleButton( modifier = modifier, text = text, buttonSize = buttonSize, textColor = SpindleTheme.colors.textMediumEmphasis, backgroundColor = SpindleTheme.colors.surfaceTertiary, rippleColor = R.color.neutral_button_ripple, rippleAlpha = 0.1f, iconRes = iconRes, iconColor = SpindleTheme.colors.objectMediumEmphasis, onClick = onClick ) ・NeutralButtonの色などここで指定しま す。 NeutralButton
  43. @Composable fun SpindleDangerButton( text: String, buttonSize: SpindleButtonSize, onClick: () ->

    Unit, modifier: Modifier = Modifier, icon: Painter? = null ) = SpindleOutlinedButton( modifier = modifier, text = text, buttonSize = buttonSize, textColor = SpindleTheme.colors.textCaution, borderColor = SpindleTheme.colors.borderCaution, rippleColor = R.color.danger_button_ripple, rippleAlpha = 0.05f, iconColor = SpindleTheme.colors.objectCaution, onClick = onClick, iconRes = iconRes ) ・SpindleOutlinedButtonを利用 ・Dangerの色やborderColorの指定する DangerButton
  44. SpindleContainedButton( text = "Button", buttonSize = SpindleButtonSize.Large, onClick = {

    /*...*/ }, icon = painterResource(id = R.drawable.plus) ) 最終的な 実際の使い方
  45. Index 3:まとめ 1:Compose導入にあたって 2:デザシスとCompose

  46. まとめ Android Viewより圧倒的に簡単且つ、 より柔軟にデザインシステムのガイドラインを表現できる

  47. class SpindleButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null,

    defStyleAttr: Int = R.attr.buttonStyle ) : AppCompatButton(context, attrs, defStyleAttr) { /*...*/ // Textの左にDrawableを描画する. private fun drawIconDrawable(canvas: Canvas) { canvas.withSave {/*...*/} } } ・Custom Viewを使い実装していた ・canvasなどを使って頑張っていた ・利用時にxmlでstyleを渡していた Before(Android View)
  48. @Composable private fun SpindleButtonLabel(/*...*/) { Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/)

    } } ・直感的にかける ・イメージが湧きやすい ・Spindleの定義に沿った表現ができやす くなった After(Compose)
  49. 終わり