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

AmebaのDesign SystemとCompose

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

AmebaのDesign SystemとCompose

Avatar for Atsuhiro Iino

Atsuhiro Iino

August 18, 2022
Tweet

Other Decks in Programming

Transcript

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

    Configでの制御 ・規模が大きいアプリのため段階公開を した 運用 ・コーディング規約 ・Activity, Fragment, GroupieのItem, XML単位での実装例 ドキュメント作り
  2. 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 }
  3. 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 }
  4. Spindle Theme SpindleThemeを定義し、 CompositionLocalProviderに設 定する。 @Composable fun SpindleTheme(content: @Composable ()

    -> Unit) { MaterialTheme { CompositionLocalProvider( LocalSpindleColorPalette provides SpindleColorPalette, content = content ) } }
  5. 構成 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(/*...*/) } } } }
  6. @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(/*...*/) } } } } ボタンのサイズとスタイルを渡します
  7. 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(/*...*/) }
  8. 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
  9. @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(/*...*/) } } } } リップルの設定
  10. 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を渡してあ げる
  11. @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で括る
  12. @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が文字にもかかってしまう
  13. @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の レイヤーがくるようにした
  14. SpindleButton( text = "Button", buttonSize = SpindleButtonSize.Large, buttonStyle = SpindleButtonStyle.Contained,

    onClick = { /*...*/ }, iconRes = painterResource(id = R.drawable.plus) ) 実際の使い方
  15. @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関数で渡した方が汎用的 ではないか?
  16. 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と余分なものを定義する ことになるけど良いのか?
  17. @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で作成
  18. @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で作成
  19. @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
  20. @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
  21. SpindleContainedButton( text = "Button", buttonSize = SpindleButtonSize.Large, onClick = {

    /*...*/ }, icon = painterResource(id = R.drawable.plus) ) 最終的な 実際の使い方
  22. 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)
  23. @Composable private fun SpindleButtonLabel(/*...*/) { Row(/*...*/) { Icon(/*...*/) Spacer(/*...*/) Text(/*...*/)

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