レイアウトにまつわる本当にあった怖い話

窓の外は息が凍るほど寒く、開発室はPCとサーバーの熱気で汗ばむほど暑かった。

私の住む日本の北にある街から遠く離れた海外の会社からソースが送られてきた。慣れた手つきで早速ソースを解凍してビルドし、手元に並ぶスマートフォンのひとつにインストールした。アプリの画面が表示され、それなりに問題なく動作しているようだった。

私は安堵の息を吐いた。ビルドが通ることさえ怪しいと思っていたからだ。
ところが、こんなレイアウト定義を見つけた私の背筋は凍りついた。

<LinearLayout
    android:layout_width="480px"
    android:layout_height="42px"
    android:orientation="horizontal" >

    <EditText
        android:id="@+id/input1"
        android:layout_width="354px"
        android:layout_height="30px"
        android:layout_marginTop="12px"
        android:layout_marginLeft="12px"
        android:layout_marginRight="6px"
        android:hint="Input here" />

    <Button
        android:id="@+id/button1"
        android:layout_width="24px"
        android:layout_height="24px"
        android:layout_gravity="center_vertical"
        android:layout_marginTop="3px"
        android:layout_marginRight="12px"
        android:layout_marginBottom="3px"
        android:text="Go" />

</LinearLayout>

私の口から発せられた言葉は短かった。

「正気か……。」

その次にメールに書かなければならない言葉も短かった。

「デザイナーから渡されたレイアウト指示を見せてごらん?」

たったこれだけのことを伝えるためだけに「そうだね、私たちの指示が悪かったんだね。君たちは悪くないね。君たちの手元では動いているもんね。そうだね、わかったよ、悪くない。責めてないってば。」と、小学生をいなすような、くだらないやり取りがしばらく続くかと思うと、胃が締め付けられるようだった。

f:id:incesticide:20140405230907p:plain

※デザイン条件

  • ディスプレイサイズ:480x800
  • 解像度:hdpi

こんな絵を描いて渡して、こんな日に連絡が取れないデザイナーは後で問いただす話し合う(そして、デザインを誰かがチェックするという無駄な作業が増えてしまう)として、急いで問題の解決を図らねば……。

問題点と解決方法

1.ディスプレイサイズや解像度の変化に耐えられない。

px指定という常識外なことに目が向きがちだが、それだけではない。

デザイナーは、デザイン条件として画面の幅が480pxと決まっていたとしても、スマートフォンの画面のデザインであるならば、ガチガチの固定値を指定できないことを知らなければならない。
プログラマーは、デザイナーから指定されていても、すべての値をハードコーディングすべきではない。

このデザインからは意図を読み取れない部分が多いし、実装にも問題がたくさんある。

1-1.画面上と左右のマージン

画面上と左右にそれぞれ12pxのマージンが指定されている。

  • デザイン

ディスプレイサイズによって、マージン値が増減するのか、変わらないのか。

マージン値が増減するのであれば、ディスプレイサイズごとのマージンを指定する。(ユーザが使える領域が狭くなるだけなので、このようなケースはほとんどない。)
マージン値が変わらないのであれば、固定値を指定する。

デザイン条件が480x800となっている。
デザイン条件は320x480としたほうがよいというのがベストプラクティスらしい。ただ、320は3の倍数ではないため、hdpiの実装の時に困ってしまいそう。公式のドキュメントが見つからないので真偽は不明。

  • コーディング

マージン値にはpxを指定すべきではない。dpを指定する。

1-2.EditTextやButtonの高さとButton上下のマージン

EditTextの高さに30px、Buttonの高さに24px、Button上下のマージンに3pxが指定されている。

  • デザイン

システムのフォント設定やフォントサイズ設定を考慮してデザインされていないことは、Androidの画面のデザインとして致命的である。
フォントにはプロポーショナルフォント等幅フォントがあり、表示可能な文字数が異なる。
ICS以降、フォントサイズ設定には小・中・大・特大があり、特大が指定されることも考慮してデザインしなければならない。
EditTextの高さが30px、Buttonの高さが24pxでは、フォントサイズ設定が特大の時に表示できない可能性がある。

フォントサイズ設定に合わせて高さを可変とするか、フォントサイズ設定に依存しないか。

EditTextとButtonのどちらもフォントサイズ設定に合わせて高さを可変とする場合、Button上下のマージンを固定値で指定することができない。中央揃えを指定するとよい。
フォントサイズ設定に依存しない場合、フォントサイズを指定する。

  • コーディング

フォントサイズ設定に合わせて高さを可変とする場合、spを指定する。
フォントサイズ設定に依存しない場合、dpを指定する。

1-3.EditTextとButtonの位置関係

EditTextの幅に354px、EditTextとButtonの間に6px、Buttonの幅に24pxがそれぞれ指定されている。

  • デザイン

ディスプレイサイズによって、どの値が増減するのか、どの値が変わらないのか。

たとえば、ディスプレイサイズによって、EditTextの幅が増減し、EditTextとButtonの間の6px、Buttonの幅の24pxが変わらないとすると、EditTextの幅が可変であることを指定する。

システムのフォントサイズ設定が考慮されていない。

フォントサイズ設定に合わせる場合、Buttonの幅も可変とする。

今回のケースのように、EditTextとButtonが横並びになっているレイアウトでは、Buttonの幅を可変としたくないことが多い。

EditTextのhintに"Input here"、Buttonのtextに"Go"という文字列を設定している。
Buttonの幅を可変とすると、日本語をサポートすることになった時に、文字列次第ではデザインが大きく変化してしまう。そのため、多言語をサポートするアプリケーションでは、ボタンを文字列とせず、アイコン(メタファー)を表示する。
なお、ボタンを固定値として、どのような条件においても文字列を完全に表示することは不可能である。フォント設定や言語によって幅が変わる。
どうしても文字列を設定する場合は、最小幅を指定し、可変とすべきである。

  • コーディング

今回のケースでは、EditTextとButtonが横に並んでいる。EditTextの幅が指定されているが、EditTextの機能として必要な幅、というより、それぞれの幅の余りをEditTextの幅に充てられている。このような場合は、EditTextを可変とする。

2.意味が伝わりにくく、簡素なデザイン変更にも耐えられないコーディング。

2-1.LinearLayoutの幅と高さ
  • コーディング

LinearLayoutのlayout_widthとlayout_heightに固定値が設定されている。

ViewGroupに固定値を設定しても効果がない。minWidth, maxWidth, minHeight, maxHeightは指定することができる。
公式リファレンスを探したが、どこにも記載されていないようだ。そのためか、この仕様を誤解したコーディングをしばしば見かける。(そして、そのまま放置されている。テストしていないのだろうか。)

2-2.画面上と左右のマージン
  • コーディング

EditTextとButtonにマージンが設定されている。
LinearLayoutのpaddingとして設定したほうが意味が分かりやすくなるし、部品が増えた時に対応しやすくなる。

2-3.EditTextとButtonの位置関係
  • コーディング

EditTextとButtonの幅がEditTextの右マージンとして設定されている。
EditBoxとButtonの幅の比率によって指定された場合に対応できない。空のViewやSpace(API Level 14以上の場合)を指定したほうがよい。

2-4.EditTextのhintやButtonのtextの文字列
  • コーディング

EditTextのhintに"Input here"、Buttonのtextに"Go"という文字列が設定されている。
Viewのアトリビュートに文字列をハードコーディングすると、環境によってはビルドエラーとなることがある。
ローカライズを考慮していなくても、hintやtextはstringリソースに定義しなければならない。

解決

デザイン

f:id:incesticide:20140405231332p:plain
★:フォントサイズ設定により可変

※デザイン条件

  • ディスプレイサイズ:480x800
  • 解像度:hdpi

コーディング

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="8dp"
    android:paddingLeft="8dp"
    android:paddingRight="8dp"
    android:gravity="center_vertical"
    android:orientation="horizontal" >

    <EditText
        android:id="@+id/input1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_widht="1"
        android:hint="@string/input_here" />

    <Space
        android:layout_width="4dp"
        android:layout_height="0dp" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/go" />

</LinearLayout>

まとめ

デザイナーは、Android公式のドキュメントに目を通すべき。斜め読みでも。

プログラマーは、デザインの意図がわからなければデザイナーに訊くべき。ディスプレイサイズや解像度を考慮し、簡素なデザイン変更に耐えられるコーディングをすべき。

参考

ディスプレイサイズや解像度についての公式ドキュメント。

Androiddip(dp)ってpxに換算すると何なのさ!」というタイトル通りの内容がわかる記事です。とても参考になります。

関連記事

まだ書いてません。