2011年10月4日火曜日

Chapter10: 単位について(dp, sp, px)

今回はAndroidの単位についてです。
Webデザイナーの方は普段は単位はpxのみ気にしておけば、ほぼ大丈夫かと思いますが(印刷物はdpi気にしますが)Androidの場合は違います。

理由は、端末によって解像度が違うためですね。
日本のAndroidで主流の解像度は以下の通りです。
  • 800x480(hdpi)
  • 854x480(hdpi)
  • 960x640(hdpi)
また、主流ではないですが、以下の解像度の端末もあります。
  • 480x320(mdpi)
  • 320x240(ldpi)
これだけあると、それぞれ専用のレイアウトを準備しないといけなさそうです。実際、準備する場合もあります。マルチスクリーンに関しては、別の機会に書こうと思います。今はシンプルに単位だけに注目します。

Android Developersのサイトに、Supporting Multiple Screensという項目があります。まさに別の機会にとさっき書いたばかりの項目ですが、そこに、単位についても書いてあります。

  • dp(dip: density-independent pixel)
  • sp(scaled pixel)
  • px(pixel)
dp(dip)は、解像度に依存しないpixelです。
解像度に合わせて自動的にpixel変換が行われます。

hdpiの端末は、240dpiです。

240 / 160(基準値) = 1.5px = 1dp

という計算になります。画面上で1.5pxは描けませんが、1dpは描けます。

mdpiの端末は、160dpiです。

160 / 160(基準値) = 1px = 1dp

という計算になります。

これを、px換算の解像度に当てはめると、

800x480(hdpi)の端末だと、533dp x 320dpでした。
480x320(mdpi)の端末だと、480dp x 320dpでした。

なんとdp上だと、横の長さが320dpで一致しましたね。

以下、800x480(hdpi)の端末です。
次は480x320(mdip)の端末(エミュレータ)です。

16dpと書いている文字の大きさはどちらも同じですが、16pxと書いている文字の大きさは明らかに違います。特に、480x320の画面では、16dpも16pxも同じ大きさです。しかし、800x480の画面では違います。16dpの文字の大きさはpxに換算すると16dp x 1.5 = 24px の文字になるからです。

しかし、画面の幅自体は、どちらの端末も320dpです。画面の縦横比が違うので、ちょっと見づらいかもしれませんが、端末自体の大きさ(解像度でなく)が同じならば、文字のサイズは同じはずです。

dpを使えば、画面の解像度を意識する回数が減ります。dpを使っておけば、新しい解像度の端末が出ても、なんとなくちゃんと表示されて動いてくれるということも期待できます(モヤッとした言い回しですが)。

spという単位もありますが、spは文字の解像度設定に倣うという単位なので、基本的にレイアウトにではなく、フォントサイズ指定に使います。フォントサイズ指定ではdpではなくspを使いましょう。

コードレビュー

ではレイアウトのコードを見てみましょう。 Sample02プロジェクトの/res/layout/chapter10.xmlを開いてください。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <!-- 
        dp(dip)は、解像度に合わせてpxが変化する単位です。
        端末の解像度(dpi)に応じて、大きさが変化します。
        基本的にdp(dip)を使ってレイアウトを定義していきます。
        sp(scaled pixels)は主にフォント用の単位です。
        pxで定義する機会は、ほとんどないと言えますが意識する必要はあります。
     -->
    <!-- 普通はtextSizeにはdpは使いませんが、サンプルとして使ってます -->
    <TextView
        android:text="@string/chapter10_dp"
        android:textSize="16dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <!-- 普通はtextSizeにはspを使うのでこっちのが正解 -->
    <TextView
        android:text="@string/chapter10_sp"
        android:textSize="16sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <!-- 敢えてpxで指定してみた例。160dpi端末と240dpi端末で比較するといいです -->
    <TextView
        android:text="@string/chapter10_px"
        android:textSize="16px"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:text="@string/chapter10_description"
        android:textSize="16sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
textSizeに、各単位(16dp, 16sp, 16px)を指定しています。 解像度の違う端末で見てみると違いがわかりやすいです。なかなか準備できないので、エミュレータで比較してみるのがよいでしょう。

Chapter09: marginとpaddingについて

今回はmarginとpaddingについてです。
marginとpaddingについては、感覚的にはCSSと同じです。ただ、border領域がありません。borderは別途background属性でdrawable(画像もしくは描画を表すxml)指定で表現することになります。

  • padding・・・要素から青枠までの領域(background属性が適用される範囲)
  • margin・・・青枠から赤枠までの領域

marginは他要素からの距離になるので、Root要素(一番最初に定義されるLayoutなりView
なり)にmarginを定義しても、あまり意味がありません(定義できるけど動作が変?)。

marginを定義するのは、何かしらの子要素に対してのみ、行いましょう。


では、サンプルを見てみましょう。
Sample02アプリを起動して、メニューを押してchapter09を選んでください。
以下のような画面になります。
LinearLayoutのhorizontalを3つ準備して

  • marginのみ設定
  • paddingのみ設定
  • marginとpaddingの両方設定

を作ってみました。background属性がわかりやすいように、色を付けています。
パッと見た感じ、わかりにくいので、ちょっと解説用に矢印をつけたバージョンが以下です。
marginのみでTop, Leftなどを別々に指定みてみたところ、marginのみではその通りに反映されました。

ところが、paddingのみで指定したところ、marginのときのようにはなりませんでした。ベースラインがpaddingTopの高いほうに合わせられました。オレンジ枠の部分はmarginでもpaddingでもありません。

marginとpaddingの両方を指定したところ、思った通りの結果ですが、ベースラインはpaddingに合わせられ、marginも反映されました。


コードレビュー

ではレイアウトのコードを見てみましょう。 Sample02プロジェクトの/res/layout/chapter09.xmlを開いてください。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <!-- 
        marginを指定する場合は、layout_marginを使います。
        個別に設定する場合は、
        layout_marginTop,
        layout_marginBottom,
        layout_marginRight,
        layout_marginLeft
        です。
        
        paddingを指定する場合は、paddingを使います。
        個別に設定する場合は、
        paddingTop,
        paddingBottom,
        paddingRight,
        paddingLeft
        です。
        
        CSSのブロック要素と同じく、コンテンツの周囲がpaddingで
        paddingの外側がmarginです。
        marginは、周囲の要素に対して有効なため、
        Root要素(ここでは2行目で定義しているLinearLayout)でmarginを
        定義しても有効ではありません。定義はできますが、動作が不確定ぽいです。
        Root以下の子要素で定義してください。
     -->
    <TextView
        android:text="@string/chapter09_margin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <!-- marginのみ設定 -->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:text="@string/app_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="20dp"
            android:background="#008000" />
        <TextView
            android:text="@string/app_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:layout_marginLeft="20dp"
            android:background="#4b0082" />
    </LinearLayout>
    <!--
        paddingのみ設定
        より大きな数値でpaddingが設定されている要素にベースラインが合ってしまうようです。
     -->
    <TextView
        android:text="@string/chapter09_padding"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:text="@string/app_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingTop="10dp"
            android:paddingLeft="20dp"
            android:paddingBottom="10dp"
            android:paddingRight="20dp"
            android:background="#008000" />
        <TextView
            android:text="@string/app_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingTop="40dp"
            android:paddingLeft="20dp"
            android:paddingBottom="20dp"
            android:paddingRight="40dp"
            android:background="#4b0082" />
    </LinearLayout>
    <TextView
        android:text="@string/chapter09_margin_padding"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <!-- marginとpaddingを設定 -->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:text="@string/app_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:paddingTop="10dp"
            android:paddingLeft="20dp"
            android:paddingBottom="10dp"
            android:paddingRight="20dp"
            android:background="#008000" />
        <TextView
            android:text="@string/app_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:paddingTop="40dp"
            android:paddingLeft="20dp"
            android:paddingBottom="20dp"
            android:paddingRight="40dp"
            android:background="#4b0082" />
    </LinearLayout>
</LinearLayout>
marginは以下のようにして指定します。
  • layout_margin・・・上下左右すべて同じ値を指定したい場合
  • layout_marginTop・・・上のみ指定
  • layout_marginBottom・・・下のみ指定
  • layout_marginLeft・・・左のみ指定
  • layout_marginRight・・・右のみ指定
marginの場合、layout_という接頭辞が付きます。個別に設定する機会のほうが多いかと思います。これはCSSのmarginの指定と同じですね。

paddingは以下のようにして設定します。
  • padding・・・上下左右すべて同じ値を指定したい場合
  • paddingTop・・・上のみ指定
  • paddingBottom・・・下のみ指定
  • paddingLeft・・・左のみ指定
  • paddingRight・・・右のみ指定
paddingの場合はpaddingなんちゃらです。これもCSSのpaddingと同じような設定なので、特に違和感ないと思います。

「いやいや!marginとpaddingの概念はわかったけども、そこに指定する値はどうやって算出するのさ!そもそもdpって何よ!?」って思ってる人が多いと思います。
なので、次はAndroidのレイアウトにおける単位を取り扱います。

Chapter08: Viewを重ねる

今回は「Viewを重ねる」です。
Viewを重ねるというのは、画像の上に文字を表示したりする場合などが考えられます。私がよく使っている方法ですが、RelativeLayoutの下にImageViewとTextViewを入れ、margin, paddingをいじって場所決めをして重ねています(margin, paddingについてはChapter09で取り上げます)。

LinearLayoutでは基本的にViewが重なり合うということはないので、RelativeLayoutを使います。


では、サンプルを見てみましょう。
 Sample02アプリを起動して、メニューを押してchapter08を選んでください。
以下のような画面になります。
Chapter07とどこが違うねん!という感じですが、よく見ると、右上にSample02の文字があります。
Sample02という文字列はlayout_alignParentRight=trueが指定されています。これで親要素のRelativeLayoutに対して右寄せの位置に表示されます。しかしバリスタの画像のサイズが ちょっと小さかったので、重なってないように見えたので、ついでだからlayout_margin指定をして画像の上にもってきてみました。

親要素に対して相対的に配置すれば、View同士を重ねることができるわけです。


コードレビュー

ではレイアウトのコードを見てみましょう。 Sample02プロジェクトの/res/layout/chapter08.xmlを開いてください。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <!-- 
        親要素(RelativeLayout)に対して、子要素を相対的に配置します。
        親要素に対して相対的に配置すれば、Viewを重ねることができます。
        今回はTextViewをRelativeLayoutに対して右寄せにして、
        layout_margin***を指定することで、ImageViewの上に重ねました。
     -->
    <ImageView
        android:src="@drawable/barista"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:scaleType="fitCenter"
        android:adjustViewBounds="true" />
    <TextView
        android:text="@string/app_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginTop="40dp"
        android:layout_marginRight="40dp" />
</RelativeLayout>
TextViewに、layout_alignParentRight=trueを指定して、親要素のRelativeLayoutに対して右寄せにしています(23行目)。
その後、24行目、25行目でmarginの指定を行っています。親要素のRelativeLayoutからのマージンになっています。重ねるだけならば、そこまで難しいことはありません。複雑なレイアウトも、分解していけばこのレイアウトのような形で、RelativeLayout内で重ねるなどが多いんじゃないかなと思います(あくまで自分の経験の中でですが)

Chapter07: 画像を中央表示する

さて、Sample02プロジェクトを開始します。
まだインポートをしていない方は、「Sample02プロジェクトを公開しました」より、Eclipseへのインポートをお願いします。 今回は、レイアウトの中央に画像を表示する方法です。 まぁこれも、ぴんとくる人はくると思いますが、RelativeLayoutのlayout_centerInParentを使います。 Chapter02で使っている、中央に文字を表示していたやつです。

では、サンプルを見てみましょう。
Sample02アプリを起動してください。
以下のような画面になります。
画像はネスカフェのバリスタです。
Androidで画像を扱う場合は、ImageViewを使います(ゲームなどでは、SurfaceView)。普段はImageViewを使うと思っていて大丈夫です。

コードレビュー

ではレイアウトのコードを見てみましょう。 Sample02プロジェクトの/res/layout/chapter07.xmlを開いてください。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <!-- 
        layout_centerInParent=trueにしているので、
        画像が領域の中央に配置されます。
        ImageViewのscaleTypeをfitCenterにすることで、
        領域に合わせて自動でリサイズされます。
        scaleTypeは色々あるので、時間があれば他の値も試してみましょう。
        また、解像度の大きい画像を使うとメモリを消費しやすくてアプリが
        強制終了しやすくなります。
        なるべく画面の解像度に近いサイズにしましょう。
     -->
    <ImageView 
        android:src="@drawable/barista"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:scaleType="fitCenter"
        android:adjustViewBounds="true"
    />
</RelativeLayout>
RelativeLayoutが親要素です。その子要素のImageViewに、layout_centerInParentを指定しています(20行目)。

21行目で、scaleTypeという指定を行っています。fitCenterにしておくと、大きな画像の場合、表示領域の大きさ(layout_width, layout_height)に自動的に縮小されます。このscaleTypeもまた色々種類があるのですが、ほぼfitCenterしか使わないんじゃないかと思うので説明は省略します。

22行目のadjustViewBoundsは、描画の縦横比を維持するかどうかです。ImageViewのlayout_width, layout_heightの値が画像の縦横比の影響を受けて変化する、という認識なのですが、違うかもしれません(汗。レイアウトを静的に作るときには、trueにしておけばいいと思っています。

 外部リンクになりますが、Android Wiki UIコンポーネント / ImageView を見たら参考になります。

 スマートフォンはメモリが少ないですし、アプリ毎にメモリ割り当て上限が決まっているので、サイズの大きな画像を読み込ませるとOut Of Memoryという例外が発生してアプリが落ちます。なるべく、端末の解像度に近い画像を準備するようにしましょう(マルチ解像度については別のところで取り上げるのでそれまで待っててください)。

2011年10月3日月曜日

Sample02プロジェクトを公開しました

前回までで、Sample01のプロジェクトの内容が終わりました。
次のプロジェクトを準備してあるので、Sample02プロジェクトをダウンロードしてインポートしてください。やり方は、Eclipseでプロジェクトをインポートする(zipのまま)です。ダウンロードするURLの部分だけ、読み替えてください。よろしくお願いします。

Sample02プロジェクトへのリンク(github)

Sample02プロジェクトで取り扱う内容は、以下の通りです。

  • (Chapter07)画像を中央配置する(Chapter07)
  • (Chapter08)Viewを重ねて表示する
  • (Chapter09)margin, paddingについて
  • (Chapter10)Androidでの単位について(dp, sp, pxなど)

Chapter06: フッターを作る方法

今回はフッターを作る方法です。
サイトを作っていても、全画面表示でフッターを一番下に表示する方法など、結構面倒ですが、Androidのレイアウトではそんなに難しいことではありません。方法はまぁ色々とありますが、感のいい人ならひょっとしたら気づいているかもしれません。

今回は、layout_weightを使います。

layout_weightといえば、Chapter03とChapter04で使いましたね。

では、サンプルを見てみましょう。
サンプルアプリでメニューを押して、chapter06を選択してください。
以下のような画面になります。
フッター側に特別な指定を行うのではなく、上のViewにlayout_weight属性をつけます。

コードレビュー

ではレイアウトのコードを見てみましょう。
Sample01プロジェクトの/res/layout/chapter06.xmlを開いてください。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        >
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">
            <TextView
                android:text="@string/chapter06_long_text"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" />
        </LinearLayout>
    </ScrollView>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <Button
            android:text="@string/chapter06_button"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>
ScrollViewにlayout_weight=1をつけています。これにより、同じ階層にあるLinearLayoutは子要素が使っている領域分(ここではボタン)のみ使用することになります。こんな感じで、フッターは簡単に実装できます。 ここでのフッターは常に画面に表示されているフッターになりますが、そうでないフッター(リストの一番下にあるフッターなど)は、またそれ用の実装方法があります。 リストのフッターの実装方法は、ListViewのFooterViewを追加するという方法でJavaでの記述が必要になるので、これ以上はここでは言及しません。 トップ画面でフッターをつけてくれと言われたら、こんな方法がある程度に覚えておいてください。

2011年10月2日日曜日

Chapter05: スクロールするViewの配置

今回はスクロールするViewについてです。
LinearLayoutの中には、いくらでも子要素を入れることができますが、画面内におさまらないくらいに子要素を追加すると、画面からはみ出たままで、画面はスクロールすることができません。

そういうときには、ScrollViewを使います。

よく見かけるリスト化されたView(ListView)や、グリッド表示のView(GridView)は、もともとスクロールするための機能を内包しているので、この処理は必要ありません。長いフォームなどの場合や、長い文章を読ませたい場合などに使うとよいでしょう。

画面をスクロールさせるためのViewですが、2種類あります。

  • ScrollView(縦方向のスクロールを担当)
  • HorizontalScrollView(横方向のスクロールを担当)

ScrollViewという名前だと縦横のどちらも使えそうで、縦方向のスクロールViewがVerticalScrollViewとかじゃないのが、何故かという話がちょろっとネットで話題になってたこともありました。ちょっと横道にそれてしまいましたが、HorizontalScrollViewはボタンの配置とかで使うことがあるので、横方向の場合は違う!ということくらいは覚えておきましょう。

では、サンプルを見てみましょう。
サンプルアプリでメニューを押して、chapter05を選択してください。
以下のような画面なります。
ここぞとばかりにTextViewをコピーしまくってみました。下のほうが薄らグラデーションがかかってるのがわかりますでしょうか?続きがあるよというのを見た目的にわかるようにするため、ScrollViewにはそのような機能があります。この範囲の長さなども変更可能です(ここでは取り上げない)

コードレビュー

では、レイアウトのソースコードを見てみましょう。Sample01プロジェクトの/res/layout/chapter05.xmlを開いてください。
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <!-- 
        この例のように、レイアウトに必ずViewが入ってなければいけないわけではありません。
        縦方向のスクロールが欲しい場合は、ScrollViewを使います。
        横方向のスクロールが欲しい場合は、HorizontalScrollViewを使います。
        今回は、ScrollViewのサンプルです。
        ScrollViewは、直近の子要素として、1つのViewしか受け付けません。
        ですので、ScrollViewの下には、LinearLayoutなどを配置し、
        その下にスクロールされるであろう要素を並べます。
        
        また、リスト表示をする場合はListViewなどを使いますが、ListView系は
        すでにScrollViewを梱包しているので、ScrollViewの下にListViewを入れる必要は
        ありません。
        
        あくまで、長いフォームなどを作る場合などに、ScrollViewを使うとよいでしょう。
     -->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <!-- 手抜きでTextViewをたくさんコピーしています -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/chapter01_linearlayout_vertical" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/chapter01_linearlayout_vertical" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/chapter01_linearlayout_vertical" />
(長いので省略)
    </LinearLayout>
</ScrollView>
ScrollViewの子要素には、1つしか置くことができません。2つ以上の要素を置くとエラーでアプリが落ちます。スクロールバーを表示する対象が1つだけだからなのでしょう。
なので、ScrollViewの子要素にはLinearLayoutもしくはRelativeLayoutなどを配置するとよいでしょう。そのレイアウトの下にはいくらでも子要素を配置できます。

LinearLayoutの中には大量にTextViewを入れています(長いので省略していますが)。
ScrollViewを使えば、後はレイアウトの配置は同じなので、特に困ることはないと思います。しかし、ScrollViewの中にScrollViewを入れたりするとよくわからない感じになることがあるので、そういう使い方はなるべく避けましょう。

最後がアバウトな書き方になってすみません!