Skip to content

Androidカスタムコンポーネントのススメ

  • はじめに
    Androidアプリケーションの機能実装のアプローチとして、コンポーネント(Component / Control、Widgetと称すこともあります)を拡張していく方法があります。特にコンポーネントの振る舞いに紐づく機能は、それをActivityクラスに実装するよりコンポーネントに実装する方が、全体的にシンプルなコードになることがあります。
    例として、必須入力チェック機能付きEditTextコンポーネントを作成してみます。
    ※いつもの如くeclipseは英語版のままなので、適宜日本語に置き換えてください ;-)

  • 開発手順
    1. Androidプロジェクトを作成します。プロジェクト名は「CustomComponent」、パッケージ名は「jp.tworks.android.customcomponent」としました。パッケージ名は後に使いますので覚えておきましょう。
      プロジェクトの作成

    2. EditTextに必須入力チェック機能を付けるために、必要な属性を考えてみます。EditTextが必須チェックを行うか否かのフラグ(boolean)と必須チェックエラーを検出したときのエラーメッセージ(String)があれば良さそうですね。

    3. さっそく属性を定義します。/res/values/attrs.xml を追加します。
      /res/values からコンテキストメニューを開き、[New] –> [Other...] を選択します。
      attrs.xmlの追加

    4. Android XML Values Fileを選択します。
      attrs.xmlの追加

    5. ファイル名に「attrs.xml」を入力したらFinishをクリックします。
      attrs.xmlの追加

    6. attrs.xmlを以下のように編集します。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      <?xml version="1.0" encoding="utf-8"?>
      <resources>
       
          <declare-styleable name="ValidEditText">
              <attr name="required" format="boolean" />
              <attr name="required_message" format="string" />
          </declare-styleable>
       
      </resources>

      declare-styleable name=”xxxxxxxx” のxxxxxxxxは、これから作成するカスタムコンポーネントのクラス名を指定しておきましょう。

    7. 次にカスタムコンポーネントのクラスを作成します。プロジェクトにクラスを追加します。srcからコンテキストメニューを開き「New」–>「Class」をクリックします。
      クラスの追加

    8. Package名を「jp.tworks.android.customcomponent.widget」、クラス名を「ValidEditText」、継承元クラスをAndroid標準の「android.widget.EditText」としました。
      クラスの追加

    9. ValidEditTextクラスのコンストラクタを定義しましょう。widgetには3タイプのコンストラクタがありますが、ここではそのうちの1つ(引数が2つのバージョン)を定義します。

      1
      2
      3
      4
      5
      6
      7
      
      public class ValidEditText extends EditText {
       
      	public ValidEditText(Context context, AttributeSet attrset) {
      		super(context, attrset);
      	}
       
      }
    10. 必須チェックを行うか否かのフラグ(boolean)と必須チェックエラーを検出したときのエラーメッセージ(String)を管理するメンバ、およびそれへ値をセットするロジックを追記します。それぞれの値はlayout.xmlに「required=”true”」「required_message=”必須入力エラー”」のようにXMLの属性として記述されている前提とします。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      
      public class ValidEditText extends EditText {
       
      	// 必須チェック有無
      	private boolean required;
       
      	// 必須チェックエラー時のエラーメッセージ
      	private String required_message;
       
      	// コンストラクタ
      	public ValidEditText(Context context, AttributeSet attrset) {
      		super(context, attrset);
       
      		//----- layout.xmlに記述される属性を取得する -----
      		// /res/values/attrs が R.styleable として参照できる
      		// 先に定義した ValidEditText の属性名をすべて取得する
      		TypedArray attrsarray = context.obtainStyledAttributes(attrset, R.styleable.ValidEditText);
       
      		//----- ValidEditTextの属性値を個別に取得する -----
      		// 必須チェック有無
      		// 属性値がbooleanの場合は、第2引数で初期値を指定可能
      		required = attrsarray.getBoolean(R.styleable.ValidEditText_required, false);
       
      		// 必須チェックエラー時のエラーメッセージ
      		// 属性値がStringの場合、layout.xmlの属性値は @string/xxxxx 形式で記述可能
      		required_message = attrsarray.getString(R.styleable.ValidEditText_required_message);
      	}
       
      }

      AttributeSet#obtainStyledAttributes で、attrs.xmlで定義した属性を一挙に取得できます。またTypedArray#getBooleanやTypedArray#getStringで属性値を取得します。

    11. 入力チェックロジックを追記します。ValidEditTextクラスの外から呼べるように、メソッドのスコープをpublicにします。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      	// Validation
      	public boolean isValid(Context context) {
      		// 必須チェック
      		if (required == true) {
      			if (getText().length() == 0) {
      				// 何も入力されていないのでエラーを表示する
      				Toast.makeText(context, required_message, Toast.LENGTH_LONG).show();
      				return false;
      			}
      		}
       
      		return true;
      	}
    12. /res/values/string.xmlを編集して、必須入力エラーが検出されたときのエラーメッセージを定義します。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      <?xml version="1.0" encoding="utf-8"?>
      <resources>
       
          <string name="hello">Hello World, Main!</string>
          <string name="app_name">CustomComponent</string>
       
          <string name="required_message">何か入力してね!</string>
       
      </resources>
    13. /res/layout/main.xmlを編集し画面を定義します。上記で作成したValidEditTextとButtonを1つずつ追加します。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
      <?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" >
       
          <jp.tworks.android.customcomponent.widget.ValidEditText
              android:id="@+id/validTextEdit1"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content">
       
              <requestFocus />
       
          </jp.tworks.android.customcomponent.widget.ValidEditText>
       
          <Button
              android:id="@+id/button1"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Button" />
       
      </LinearLayout>

      見た目はこのような感じです。
      main.xml

    14. 引き続き、ValidEditTextに属性と属性値を追記します。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      
      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tworks="http://schemas.android.com/apk/res/jp.tworks.android.customcomponent"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >
       
          <jp.tworks.android.customcomponent.widget.ValidEditText
              android:id="@+id/validEditText1"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              tworks:required="true"
              tworks:required_message="@string/required_message" >
       
              <requestFocus />
       
          </jp.tworks.android.customcomponent.widget.ValidEditText>
       
          <Button
              android:id="@+id/button1"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Button" />
       
      </LinearLayout>

      XMLネームスペースの追加は、「xmlns:tworks=”http://schemas.android.com/apk/res/jp.tworks.android.customcomponent”」のように、http://schemas.android.com/apk/res/[アプリケーションのパッケージ名] という書き方になります。ValidEditTextの属性追加は「XMLネームスペース:属性=”属性値”」のような記述になります。ここでは「tworks:required=”true”」「tworks:required_message=”@string/required_message”」としました。

    15. 最後に/res/layout/main.xmlを読み込むActivityの実装を行います。Button(id:button1)をクリックしたときにValidEditText(id:validEditText1)のisValidメソッドを実行するようにします。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      
      public class Main extends Activity {
       
      	private ValidEditText validEditText;
       
          /** Called when the activity is first created. */
          @Override
          public void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.main);
       
              // ValidEditTextのインスタンスを取得
              validEditText = (ValidEditText)findViewById(R.id.validEditText1);
       
              // Buttonクリック時にValidEditTextのisValid()を実行する
              findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
       
      			@Override
      			public void onClick(View v) {
      				validEditText.isValid(Main.this);				
      			}
      		});
       
          }
      }
    16. これで完成です。ValidTextEditに何も入力せずにボタンを押すと、Toastによってエラーメッセージが表示されますね!
      実行結果

  • 所感
    Activityは、チェックロジックの方法を知らなくてもよい=そこはブラックボックスでもよいケースが非常に多いです。そういった部分をカスタムコンポーネントで実装することで、Activityに記述する処理を減らすことができます。こうすることで、Activityはほぼドメインロジック(ビジネスロジック)を記述するだけでよくなりますし、コードの可読性や保守性を向上させることも出来ます。
    #あとはVとMが素結合にできれば…