LECTURE 4 UI 描画を担当する View クラス

○ はじめに

LECTURE 2 文字表示だけのアプリケーション (Activity/TextView) で少し触れましたが、実際に画面を描画するのは Activity ではなく android.view.View(およびサブクラス)です。android.widget パッケージには、View クラスを継承して作られた色々な UI 部品が用意されています。単一の View を一画面にどーんと表示する他に、android.view.ViewGroup のサブクラスを利用して、複数の View を画面上にレイアウトして表示することもできます。

ここでは、とりあえず画面を描画するためのメソッドだけをオーバーライドしたサブクラスを自作して、画面表示の仕組みの雰囲気を掴みたいと思います。

○ View ID

View を実際に画面と結びつけるためには、LECTURE 2 文字表示だけのアプリケーション (Activity/TextView) にて説明している様に、Activity#setContentView() を実行する必要があります。

public void Activity#setContentView(int layoutResID);
public void Activity#setContentView(android.view.View view);
public void Activity#setContentView(
  android.view.View view, android.view.ViewGroup.LayoutParams params);

setContentView() には、View のインスタンスを指定する他に、リソース ID を指定してリソース定義から UI レイアウトを読み込ませることも出来ます(Android 的には後者が推奨の方法)。

リソース ID 指定で setContentView() を実行する場合、後から View を操作する(後から View にプロパティを設定したり、表示/非表示を切り替えたりする等)ためには、View への参照を別途取得しなければなりません。これには、Activity#findViewById() を使います。

public View Activity#findViewById(int id);

このメソッドには、View を識別するための id を指定する必要があります。この id は、UI レイアウト定義ファイルにて記述します。例えば LECTURE 2 文字表示だけのアプリケーション (Activity/TextView) の UI レイアウト定義ファイルは

<?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"
    >
<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Hello World, MainActivity"
    />
</LinearLayout>

でした。このファイルでは、View の id を設定していないため、後から findViewById() で View の参照を取得することができません。TextView への参照を後から取得できる様にするため、以下の様に id を付与します。

<?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"
    >
<TextView android:id="@+id/textView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Hello World, MainActivity"
    />
</LinearLayout>

TextView のタグに android:id="@+id/textView" という属性設定を付与しました。これにより、TextView に対して "textView" という id が付いたため、後から findViewById() で参照を取得することができます。

    /** Called with the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView textView = (TextView)findViewById(R.id.textView);
        //textView.xxxx();
    }

なお、"@+id/..." という表現により、新しい id 値を R.java ( ant時に自動生成 ) に用意してくれる様になります(+ が無いと、既にある場合はそれを使ってしまうため、id としては不適当)。

○ サンプルプログラムと説明

自作 View のサンプルプログラムについて説明します。004dispimage.zip からダウンロードできます。はじめに のページから、全てのサンプルプログラムを 固めた zip ファイルをダウンロードすることもできます。

なお、ソースコードは SDK Tools 22.0.4 / プラットフォーム Android 4.2 でコンパイル可能です。local.properties 内に sdk.dir 変数が定義されていますが、ここは環境依存になりますので、お使いの環境に合わせて変更してください。

ソースコード配布は修正 BSD ライセンスに従います(はじめに のページの免責事項を参照してください)。

QVGA(320x480:mdpi) のエミュレータで実行すると、↓の様に表示されます。
DispImage 実行画面

ここでは

  • DispImage.java
  • CustomView.java
  • dispimage.xml
  • strings.xml
  • colors.xml

について説明します。

  • DispImage.java
        1	/*
        2	 * Copyright (c) 2013, Ryo Ichinose
        3	 * All rights reserved.
        4	 *
        5	 * Redistribution and use in source and binary forms, with or without 
        6	 * modification, are permitted provided that the following conditions are met:
        7	 *
        8	 *    * Redistributions of source code must retain the above copyright 
        9	 *      notice, this list of conditions and the following disclaimer.
       10	 *    * Redistributions in binary form must reproduce the above copyright 
       11	 *      notice, this list of conditions and the following disclaimer in the 
       12	 *      documentation and/or other materials provided with the distribution.
       13	 *    * Neither the name of the auther nor the 
       14	 *      names of its contributors may be used to endorse or promote products 
       15	 *      derived from this software without specific prior written permission.
       16	 *
       17	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
       18	 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
       19	 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
       20	 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
       21	 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
       22	 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
       23	 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
       24	 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
       25	 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
       26	 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
       27	 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       28	 */
       29	
       30	package riolab.sample;
       31	
       32	import android.app.Activity;
       33	import android.os.Bundle;
       34	
       35	public class DispImage extends Activity
       36	{
       37	    /** Called when the activity is first created. */
       38	    @Override
       39	    public void onCreate(Bundle savedInstanceState)
       40	    {
       41	        super.onCreate(savedInstanceState);
       42	        setContentView(R.layout.dispimage);
       43	
       44		CustomView view = (CustomView)findViewById(R.id.customView);
       45		view.setBgColor(getResources().getColor(R.color.color_bg));
       46		view.setFgColor(getResources().getColor(R.color.color_fg));
       47	    }
       48	}
    
    
    • 42 行目

      今回は UI レイアウト定義ファイルの名前を dispimage.xml にしました。そのため、Activity#setContentView() に設定するリソース ID もそれに対応して R.layout.dispimage となっています。

    • 44 行目

      Activity#setContentView() で UI を構築した後に、今回自作した View への参照を Activity#findViewById() で取得します。

      public View Activity#findViewById(int id);
      
      

      id には、res/layout/dispimage.xml で設定した "customView" という文字列を指定します。 戻り値は汎用的な View のため、自作した CustomView にキャストして使います。

    • 45 〜 46行目

      CustomView#setBgColor() および CustomView#setFgColor() を使って、CustomView で使用する前景色/背景色を指定します。色定義は、android.graphics.Color のメンバを使って指定することもできますが、今回は res/values/colors.xml にリソース定義してそれを読み込む様にしました。

      リソースから色定義を読み込むには、android.content.res.Resources#getColor() を使います。

      public int Resources#getColor(int id);
      
      

      colors.xml に定義したリソースは、R.color.<色識別名称> として ID 指定することができます。

  • CustomView.java
        1	/*
        2	 * Copyright (c) 2013, Ryo Ichinose
        3	 * All rights reserved.
        4	 *
        5	 * Redistribution and use in source and binary forms, with or without 
        6	 * modification, are permitted provided that the following conditions are met:
        7	 *
        8	 *    * Redistributions of source code must retain the above copyright 
        9	 *      notice, this list of conditions and the following disclaimer.
       10	 *    * Redistributions in binary form must reproduce the above copyright 
       11	 *      notice, this list of conditions and the following disclaimer in the 
       12	 *      documentation and/or other materials provided with the distribution.
       13	 *    * Neither the name of the auther nor the 
       14	 *      names of its contributors may be used to endorse or promote products 
       15	 *      derived from this software without specific prior written permission.
       16	 *
       17	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
       18	 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
       19	 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
       20	 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
       21	 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
       22	 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
       23	 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
       24	 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
       25	 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
       26	 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
       27	 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       28	 */
       29	
       30	package riolab.sample;
       31	
       32	import java.util.ArrayList;
       33	import java.util.List;
       34	import java.util.Map;
       35	
       36	import android.content.Context;
       37	import android.content.res.Resources;
       38	import android.graphics.Canvas;
       39	import android.graphics.Paint;
       40	import android.graphics.Paint.FontMetrics;
       41	import android.util.AttributeSet;
       42	import android.view.View;
       43	
       44	public class CustomView extends View
       45	{
       46	    private static final int TEXT_SIZE = 24;
       47	    private final Paint paint = new Paint();
       48	    private float textHeight;
       49	
       50	    private List<String> textList = new ArrayList<String>();
       51	    private int fgColor;
       52	    private int bgColor;
       53	
       54	    public CustomView(Context context)
       55	    {
       56		super(context);
       57		init();
       58	    }
       59	
       60	    public CustomView(Context context, AttributeSet attrs)
       61	    {
       62		super(context, attrs);
       63		init();
       64	    }
       65	
       66	    public CustomView(Context context, AttributeSet attrs, int defStyle)
       67	    {
       68		super(context, attrs, defStyle);
       69		init();
       70	    }
       71	
       72	    private void init()
       73	    {
       74		paint.setAntiAlias(true);
       75		paint.setTextSize(CustomView.TEXT_SIZE);
       76	
       77		FontMetrics metrics = paint.getFontMetrics();
       78		textHeight = (-metrics.top) + metrics.bottom;
       79	
       80		Resources res = getContext().getResources();
       81		textList.add(res.getString(R.string.text1));
       82		textList.add(res.getString(R.string.text2));
       83		textList.add(res.getString(R.string.text3));
       84	    }
       85	
       86	    public void setBgColor(int color)
       87	    {
       88		bgColor = color;
       89	    }
       90	
       91	    public void setFgColor(int color)
       92	    {
       93		fgColor = color;
       94		paint.setColor(fgColor);
       95	    }
       96	
       97	    @Override
       98	    public void onDraw(Canvas canvas)
       99	    {
      100		super.onDraw(canvas);
      101		canvas.drawColor(bgColor);
      102	
      103		float x = 30.0f;
      104		float y = 150.0f;
      105	
      106		for (String text : textList) {
      107		    canvas.drawText(text, x, y, paint);
      108		    y += textHeight;
      109		}
      110	    }
      111	}
    
    

    本サンプルのメインとなる、自作の View クラスです。背景を背景色で塗りつぶして、3 行の文字列を画面に描画します。

    • 44 行目

      android.view.View を継承しています。これにより、画面表示クラスとして使うことができます。

    • 47 行目

      画面内容の描画時に使う、線種やフォントサイズ等の描画属性を格納するための android.graphics.Paint インスタンスを生成しています。SDK に付属しているサンプルアプリケーションを参考にして、final にしてあります。

    • 54 〜 70 行目

      コンストラクタです。プログラム中で直接 View のインスタンスを生成する場合は前者のみで構いませんが、UI レイアウト定義ファイルを読み込んで UI を構築する場合は、後者の形式のコンストラクタが必要になります。

    • 72 〜 84 行目 (init())

      初期化処理を行います。まず、描画属性を格納する Paint インスタンスに、アンチエイリアスの有効化およびフォントサイズを設定しています。他にも様々な描画属性を設定できますので、API ドキュメントを参照してください。

      次に、Paint から取得できる android.graphics.Paint.FontMetrics を使い、文字列を描画する場合の文字高さを求めています。FontMetrics#top および FontMetrics#bottom はそれぞれ、文字のベースラインと呼ばれる位置から上/下方向の長さを表しています。ただし、top の方は画面の Y 座標が減る方向の長さを表すためかマイナスの値になっていますので、反転してから bottom と加算しています。

      最後に、文字列リソース定義から文字列リソースを取得しています。リソースから文字列定義を読み込むには、android.content.res.Resources#getString()/Resources#getText() を使います。

      public String Resources#getString(int id);
      public CharSequence Resources#getText(int id);
      
      

      strings.xml に定義したリソースは、R.string.<文字列識別名称> として ID 指定することができます。

    • 86 〜 89 行目 (setBgColor())

      背景色を設定しています。

    • 91 〜 95 行目 (setFgColor())

      前景色を設定する際に、同時に Paint に対しても前景の表示色を設定しています。

    • 97 〜 110 行目 (onDraw())

      実際に画面表示内容を描画するメソッドです。View クラスのメソッドをオーバライドしています。まず、android.graphics.Canvas#drawColor() を使って、画面全体を指定色で塗りつぶしています。

      public void Canvas#drawColor(int color);
      
      

      106 〜 109 行目で、設定された全ての文字列を 1 行に 1 文字列ずつ描画しています。文字列の描画には、android.graphics.Canvas#drawText() を使います。

      public void Canvas#drawText(String text, float x, float y, Paint paint);
      public void Canvas#drawText(
          CharSequence text, int start, int end, float x, float y, Paint paint);
      
      
  • dispimage.xml
        1	<?xml version="1.0" encoding="utf-8"?>
        2	<!--
        3	 * Copyright (c) 2013, Ryo Ichinose
        4	 * All rights reserved.
        5	 *
        6	 * Redistribution and use in source and binary forms, with or without 
        7	 * modification, are permitted provided that the following conditions are met:
        8	 *
        9	 *    * Redistributions of source code must retain the above copyright 
       10	 *      notice, this list of conditions and the following disclaimer.
       11	 *    * Redistributions in binary form must reproduce the above copyright 
       12	 *      notice, this list of conditions and the following disclaimer in the 
       13	 *      documentation and/or other materials provided with the distribution.
       14	 *    * Neither the name of the auther nor the 
       15	 *      names of its contributors may be used to endorse or promote products 
       16	 *      derived from this software without specific prior written permission.
       17	 *
       18	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
       19	 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
       20	 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
       21	 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
       22	 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
       23	 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
       24	 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
       25	 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
       26	 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
       27	 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
       28	 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       29	-->
       30	
       31	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       32	    android:orientation="vertical"
       33	    android:layout_width="fill_parent"
       34	    android:layout_height="fill_parent"
       35	    >
       36	<riolab.sample.CustomView android:id="@+id/customView" 
       37	    android:layout_width="fill_parent" 
       38	    android:layout_height="fill_parent" 
       39	    />
       40	</LinearLayout>
    

    本サンプルの UI レイアウト定義ファイルです。res/layout/dispimage.xml として保存します。36 行目にて、Activity で findViewById() を使って View への参照を取得するため、CustomView に対して "customView" という id を設定しています。

  • strings.xml
        1	<?xml version="1.0" encoding="utf-8"?>
        2	<!--
        3	 * Copyright (c) 2013, Ryo Ichinose
        4	 * All rights reserved.
        5	 *
        6	 * Redistribution and use in source and binary forms, with or without 
        7	 * modification, are permitted provided that the following conditions are met:
        8	 *
        9	 *    * Redistributions of source code must retain the above copyright 
       10	 *      notice, this list of conditions and the following disclaimer.
       11	 *    * Redistributions in binary form must reproduce the above copyright 
       12	 *      notice, this list of conditions and the following disclaimer in the 
       13	 *      documentation and/or other materials provided with the distribution.
       14	 *    * Neither the name of the auther nor the 
       15	 *      names of its contributors may be used to endorse or promote products 
       16	 *      derived from this software without specific prior written permission.
       17	 *
       18	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
       19	 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
       20	 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
       21	 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
       22	 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
       23	 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
       24	 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
       25	 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
       26	 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
       27	 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
       28	 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       29	-->
       30	
       31	<resources>
       32	    <string name="app_name">DispImage</string>
       33	    <string name="text1">This is a</string>
       34	    <string name="text2">sample application</string>
       35	    <string name="text3">using original View class.</string>
       36	</resources>
    

    文字列リソース定義ファイルです。デフォルト定義の app_name 以外に、3つの文字列をリソースとして定義しています。

  • colors.xml
        1	<?xml version="1.0" encoding="utf-8"?>
        2	<!--
        3	 * Copyright (c) 2013, Ryo Ichinose
        4	 * All rights reserved.
        5	 *
        6	 * Redistribution and use in source and binary forms, with or without 
        7	 * modification, are permitted provided that the following conditions are met:
        8	 *
        9	 *    * Redistributions of source code must retain the above copyright 
       10	 *      notice, this list of conditions and the following disclaimer.
       11	 *    * Redistributions in binary form must reproduce the above copyright 
       12	 *      notice, this list of conditions and the following disclaimer in the 
       13	 *      documentation and/or other materials provided with the distribution.
       14	 *    * Neither the name of the auther nor the 
       15	 *      names of its contributors may be used to endorse or promote products 
       16	 *      derived from this software without specific prior written permission.
       17	 *
       18	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
       19	 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
       20	 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
       21	 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
       22	 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
       23	 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
       24	 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
       25	 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
       26	 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
       27	 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
       28	 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       29	-->
       30	
       31	<resources>
       32	    <color name="color_bg">#000000</color>
       33	    <color name="color_fg">#00ffff</color>
       34	</resources>
    

    色リソース定義ファイルです。2つの色をリソースとして定義しています。ここで使用している色指定は #RRGGBB です。

以上で解説は終わりです。次回は、自作した View に画像を表示する方法について書きたいと思います。

○ ウィンドウタイトルを表示しない

↑のサンプルプログラムの DispImage.java で、android.view.Window を import した上で onCreate にて requestWindowFeature(Window.FEATURE_NO_TITLE) を実行すると、ウィンドウタイトルが表示されなくなります。

※Activity#setContentView() の前に実行してください。

public final boolean Activity#requestWindowFeature(int featureId);

QVGA(320x480:mdpi) のエミュレータでの実行結果は↓の様になります。サンプルプログラムと説明 の画面とよく比べると、アプリケーション名を表示したタイトル部が無くなっていることが分かると思います。
ウィンドウタイトル非表示