Kyash Androidのレイアウト共通化の指針

Kyashで主にAndroidアプリを開発している@konifarです。

Androidアプリの画面を作る上でレイアウトの統一感をどう担保するかという話は、きっとどの会社でも考えるものですよね。

自分自身もまだ試行錯誤中なのですが、Kyashではこうやってるよという指針をスクショとコードを交えながらまとめておこうと思います。

res/values以下のリソースファイルをこんな感じで考えて使ってるよという話を中心にまとめるので、その前段のプロダクトコンセプトやデザインガイドラインの話については今回は割愛します。もし興味があれば、「気になる」「読みたい」みたいな感じでツイート or コメントしておけば、弊社デザイナーが書いてくれるはずです。自分も読みたいです。

余白や文字サイズの統一

どのアプリでもやっていると思いますが、余白や文字サイズはdimens.xmlにまとめています。

よく起こりがちな問題として「色んな値がたくさん入ってしまってわかりづらい」「どの値を追加すればいいかわからない」という2つがあると思います。Kyashでは、ベースのdimens.xmlとそれ以外にファイルをわけ、ネーミングルールを決めておくことで解決しています。

ベースとなるサイズ群は、次のような感じでdimens_base.xmlにまとめています。

Name Rule Example
Margin space_{size}dp, space_minus_{size}dp space_16dp, space_minus_16dp
Font text_{size_name} text_medium, text_large
Avatar avatar_{size}dp avatar_32dp, avatar_56dp

Marginのネーミングに具体的なサイズを入れているのは、結局その方が使うときにわかりやすいからです。 space_small のようにしてしまうと、xml上で指定するときに「あれ?smallってどんなサイズだったっけ?」と止まってしまうことがあるのでやめました。

「将来Marginが変わったときに置き換えが大変になるのでは?」という意見もあるかもしれませんが、経験上そういう時は大抵他の部分も色々変わるのでどちらにしろ全体的に直すことになります。それなら「この余白以外使うなよ」というルールとして使うくらいにとどめておいて、名前も使うときにわかりやすい名前にしておいた方がいいよね、という考えです。

FontについてはMarginと同じように具体的なサイズを名前に入れたいのですが、日本語と英語でフォントサイズを変えることもあるのでmediumlargeといった名前にしています。*1

他にもToolbarやButtonなどの共通のコンポーネントに関するものはすべてdimens_base.xmlにまとめています。

それ以外の各機能固有のものはdimens.xmlにまとめています。そんなに数はないので、おおまかにコメントでセクションわけして参照・編集しやすいようにしています。

<!-- welcome -->
<dimen name="welcome_indicator_height">72dp</dimen>

<!-- sign up -->
<dimen name="sign_up_stepper_circle_size">13dp</dimen>

<!-- timeline -->
<dimen name="timeline_notification_border_width">4dp</dimen>
<dimen name="timeline_notification_border_height">48dp</dimen>

ただし、1箇所のみで使いまわさないものはレイアウトxmlに直接サイズを直書きしています。

たとえば、このKyash Visaカードの部分はカスタムビューにしていて、この中でしか使わないサイズがほとんどなので直書きしています。

f:id:konifar:20180309141117p:plain

個人的には「styles.xmlを使えばほとんど直書きでいいのでは?」と思っているので、dimens.xmlは少しずつ整理して減らしていったほうがよさそうだと感じています。

カラーパレットの統一

これもどこでもやっていると思いますが、Kyash内で使う色はすべてcolors.xmlにまとめています。

<!-- theme -->
<color name="green">#6EB53B</color>
<color name="green_alpha_50">#806EB53B</color>
<color name="green_dark">#3B8500</color>
<color name="blue">#1BA9E1</color>
<color name="blue_alpha_50">#801BA9E1</color>
<color name="blue_dark">#007AAF</color>

<!-- gray scales -->
<color name="black">#000000</color>
<color name="black_alpha_12">#1F000000</color>
<color name="black_alpha_40">#66000000</color>
<color name="white">#FFFFFF</color>
<color name="white_alpha_50">#80FFFFFF</color>
<color name="gray_01">#666666</color>
<color name="gray_02">#9D9D9D</color>
<color name="gray_03">#CCCCCC</color>

<!-- components -->
<color name="facebook">#3b5998</color>

Kyashにおけるcolors.xmlの役割は、アプリ内で使う色を限定することです。ここにある色以外は使ってはいけないというルールです。

大まかにthemegray scalescomponentsの3つのセクションに分けていて、ネーミングはデザイナーと相談して決めました。Zeplinで指定するカラーネームに合わせています。

カラーパレットとしての用途なので、使うときに色がわかりやすいネーミングにしました。たとえば、Kyashにおいてblueは送金、green請求を表す色なのですが、そういった役割ではなく色をそのまま名前にしています。

請求 送金
f:id:konifar:20180309143233p:plain f:id:konifar:20180309143307p:plain

grayに関しては種類がたくさん出てきやすいので01、02といった順番を名前につけています。透過度はalpha_{透過度}というsuffixをつけています。

border_colorprimary_button_colorといった具合にエイリアスをつけることも考えましたが、参照するときに「あれ?これなんの色だっけ?」「ここでは何を指定すればいいんだっけ?」と迷ってしまうのでやめました。Kyashではcolors.xmlは「ここにある色以外は使わない」というルールとして使うだけにしています。

テーマの統一

テーマで共通化できるものはなるべくテーマで指定するようにしています。

たとえばActivityのデフォルトの遷移もテーマで指定しています。

<item name="android:windowAnimationStyle">@style/Kyash.Animation.Activity</item>
<style name="Kyash.Animation.Activity" parent="@android:style/Animation.Activity">
    <item name="android:activityOpenEnterAnimation">@anim/pull_in_from_right</item>
    <item name="android:activityOpenExitAnimation">@anim/pull_out_to_left</item>
    <item name="android:activityCloseEnterAnimation">@anim/pull_in_from_left</item>
    <item name="android:activityCloseExitAnimation">@anim/pull_out_to_right</item>
</style>

テーマはthemes.xmlを作ってそこにまとめています。styles.xmlに入れてもいいんですが、テーマまわりはvalues-v23で少し変えたりする必要もあるので別ファイルに切り出して管理しています。ベースとなるテーマはこんな感じです。

<style name="KyashThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/white</item>
    <item name="colorPrimaryDark">@color/gray_04</item>
    <item name="colorAccent">@color/blue</item>
    <item name="colorControlHighlight">@color/ripple_color</item>
    <item name="colorControlActivated">@color/blue</item>
    <item name="colorControlNormal">@color/gray_02</item>
    <item name="android:windowBackground">@color/white</item>
    <item name="android:lineSpacingMultiplier">1.2</item>
    <item name="android:textColor">@color/gray_01</item>
    <item name="android:textColorPrimary">@color/gray_01</item>
    <item name="android:textColorSecondary">@color/gray_02</item>
    <item name="android:textColorPrimaryInverse">@color/white</item>
    <item name="android:textColorSecondaryInverse">@color/white</item>
    <item name="android:statusBarColor">@color/status_bar</item>
    <item name="buttonStyle">@style/Kyash.Button.Blue</item>
    <item name="toolbarStyle">@style/Kyash.Toolbar</item>
    <item name="actionMenuTextColor">@color/gray_01</item>
    <item name="android:windowAnimationStyle">@style/Kyash.Animation.Activity</item>
    <item name="alertDialogTheme">@style/KyashThemeOverlay.Dialog.Alert</item>
</style>

これを継承して、透明なActivityのテーマ、緑色(請求)のテーマ、青色(送金)のテーマ、黒色(画像ビューア)のテーマ、といった具合にいくつか用意して、AndroidManifestで指定しています。

Kyashでは、送金のフロー中は青色、請求のフロー中は緑色で統一しているのですが、動的にテーマを切り替える時はonCreate()のなかでsetTheme()しています。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (isSend) {
        setTheme(R.style.KyashTheme_Blue)
    } else {
        setTheme(R.style.KyashTheme_Green)
    }
請求(Green) 送金(Blue)
f:id:konifar:20180309144723p:plain f:id:konifar:20180309144816p:plain

テーマに関してはもっとスマートな指定の仕方があると思うので、日々少しずつ改善しています。

スタイルの統一

共通のコンポーネントstyles.xmlにまとめ、それ以外をstyles_{機能名}.xmlというファイルにわけて管理しています。

それぞれのファイルの中ではprefixネーミングルールだけをゆるく決めていて、わりと自由にスタイルを作っています。

styles.xmlの中には、共通コンポーネントとして次の項目が定義されています。

  • TextAppearance
  • Toolbar
  • Button
  • EditText
  • Border
  • List
  • Icon
  • Indicator

TextAppearanceやButtonは、色を指定したものを用意しています。スタイルの数は増えてしまいますが、レイアウトを組むときには補完を効かせながらサクサク書けるので気に入っています。

<!--==================== TextAppearance ====================-->
<!-- Title -->
<style name="Kyash.TextAppearance.Title" parent="TextAppearance.AppCompat.Title">
    <item name="android:textSize">@dimen/text_xlarge</item>
    <item name="android:lineSpacingMultiplier">1.2</item>
</style>

<style name="Kyash.TextAppearance.Title.Navy">
    <item name="android:textColor">@color/navy</item>
</style>

ただ、少しボリュームが出てきたので、今後は styles_base_button.xmlstyles_base_text_appearance.xmlといった感じでファイルを分けていこうかと考えています。stylesファイルの数は多くなりますが、探す時はstyles_base_~とタイプして探すので特に問題ありません。

Iconもサイズごとにスタイルを用意しています。

<style name="CircleIcon.60">
    <item name="android:layout_width">@dimen/avatar_60dp</item>
    <item name="android:layout_height">@dimen/avatar_60dp</item>
</style>

<style name="CircleIcon.Clickable.60">
    <item name="android:layout_width">@dimen/avatar_60dp</item>
    <item name="android:layout_height">@dimen/avatar_60dp</item>
</style>

<style name="CircleIcon.Clickable.86">
    <item name="android:layout_width">@dimen/avatar_86dp</item>
    <item name="android:layout_height">@dimen/avatar_86dp</item>
</style>

たまにスタイルにないアイコンのサイズがデザインで上がって来たときには、「これ合ってる?」「こっちに合わせた方がよくない?」とデザイナーとやりとりしたりします。今デザインガイドラインを整えている最中なのですが、スタイルを整えておくことでエンジニアからフィードバックできる状態になっているのでとてもよいなと感じています。

layout_~系のattributesをstyles.xmlに入れることに対して抵抗がある方もいるかもしれませんが、今まで運用してきた経験から言えば特に問題ありません。むしろ、レイアウトxmlの見通しがよくなってよいなと感じています。

共通ではないけど各画面ごとに同じようなレイアウトがある場合は、styles_{機能名}.xmlにスタイルをまとめています。

たとえばウォレットタブのこの部分。同じような3つの行が見えますね。

f:id:konifar:20180309152633p:plain

この1行は、レイアウト上こんな感じになっています。

<RelativeLayout
    style="@style/WalletChargeRow"
    onClick="@{viewModel::onCvsClick}">

    <ImageView
        android:id="@+id/cvs_icon"
        style="@style/WalletRowIcon"
        android:src="@drawable/ic_wallet_charge_cvs_40dp" />

    <TextView
        android:id="@+id/cvs_title"
        style="@style/WalletRowTitle"
        android:layout_toEndOf="@id/cvs_icon"
        android:text="@string/wallet_charge_cvs_title" />

    <TextView
        style="@style/WalletRowMessage"
        android:layout_below="@id/cvs_title"
        android:layout_toEndOf="@id/cvs_icon"
        android:text="@string/wallet_charge_cvs_message" />

</RelativeLayout>

f:id:konifar:20180309160828p:plain

WalletChargeRowWalletRowIconWalletRowTitleWalletRowMessageというスタイルをstyles_wallet.xmlに定義して指定することで、スッキリとしたレイアウトになります。また、余白や文字、アイコンのサイズ変更があったときにもスタイルを一箇所変更するだけで済みます。

各機能ごとのstylesファイルに関しては、「どういうものをstyleにするべきか」といったことはあまり気にせずに共通化できるものは自由に追加してよいことにしています。コンポーネントとしてstyles.xmlに定義するのは抵抗あると思いますが、prefixルールだけ決めて別のstylesファイルに切り出すなら問題はありません。

カスタムビューや別レイアウトファイルにしてincludesするほどでもないけどちょっと共通化したい、という場合にはオススメです。

チーム内での共有

dimens.xmlcolors.xmlthemes.xmlstyles.xmlの指針についてざっとまとめてみました。

ではこれらをどのようにチーム内で浸透させているかというと、正直何もしていません。Androiderが2人しかおらず、席も隣なので「ここどうしよう?」「こうしましょうか」「ヨッシャ! (マージボタンポチッ)」みたいな感じで進めてきたからです。Wikiにまとめておこうかなぁと思いながら時が過ぎ、今に至ります。今回、このブログを書くことで雰囲気でやっていたことが少し言語化できてよかったです。

デザインのガイドラインを整えていくぞという話もあるので、そのタイミングで美しいスタイルの指針を同僚氏と話して決めていこうと思います。こういうレイアウトまわりはLintやドキュメント生成のツールを作ってもいいかもしれませんね。

こんな感じでKyashでは日々改善しながらプロダクトを成長させているので、具体的なコードの話をしたいという人は気軽にオフィスに遊びに来てください!