カスタムコンポーネントの作成

Last-modified: 2009-07-07 (火) 19:40:50

カスタムコンポーネントの作成には大きく下記のアプローチがあります。

リファレンス:http://developer.android.com/guide/topics/ui/custom-components.html#compound

既存部品の拡張

既存の部品を拡張して、機能を追加、変更する方法について説明します。
ここでは、標準のEditTextに必須の入力チェックを追加するビューを生成します。

 

完成図
extends_view.gif

 

サンプルのソースコード
Text.java


  1. ビューを継承したクラスを生成する
  2. 処理を拡張する
  3. レイアウト(XML)に定義する

ビューを継承したクラスを生成する

今回はEditTextを拡張しますので、EditTextを継承したクラスを生成し、初期化処理でAttributeから必須の設定(required)を読み込むようにします。

public class Text extends EditText implements TextWatcher{
	private boolean required = false;
	public Text(Context context) {
		this(context, null);
	}
	public Text(Context context, AttributeSet attrs) {
		super(context, attrs);
		initView(context, attrs);
	}
	/**
	 * ビューの初期化処理
	 * @param context
	 * @param attrs
	 */
	private void initView( Context context, AttributeSet attrs){
		addTextChangedListener( this);
		if( attrs != null){
			required = attrs.getAttributeBooleanValue( null, "required", false);
			// hintが設定されている場合はそちらを優先
			if( this.getHint() == null){
				this.setHint( R.string.hint_required);
			}
		}
	}

処理を拡張する

チェック処理を実装し、テキストの変更とフォーカスアウトのタイミングでチェック処理を行います。

	@Override
	public void afterTextChanged(Editable s) {
		isValid();
	}
	@Override
	protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
		// フォーカスアウト時は入力チェック
		if(! focused){
			isValid();
		}
		super.onFocusChanged(focused, direction, previouslyFocusedRect);
	}
	/**
	 * 入力チェック
	 * @return
	 */
	public boolean isValid(){
		String errorMessage = null;
		String text = getEditableText().toString();
		// 必須チェック
		if( isRequired()){
			if( StringUtil.isEmpty( text)){
				errorMessage = getResources().getString( R.string.error_required);
			}
		}
		boolean result = false;
		if(! StringUtil.isEmpty( errorMessage)){
			setError( errorMessage);
		}
		else{
			setError( null);
			result = true;
		}
		return result;
	}

レイアウト(XML)に定義する

レイアウトに定義を行う場合はクラスをパッケージ名から指定し、必須の属性(required)を指定します。

<android.wiki.sample.Text
        required="true"
        android:text="000000"
        android:id="@+id/login.id"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="@string/hint.required"
        android:singleLine="true" />

描画処理も独自のフルカスタマイズ・ビュー

他の方法より難易度は高いですが、これができれば自由に部品を作れるようになるので、是非覚えておきたい方法です。
ここでは、サイズの異なるフォントでテキストを描画する下記のビューを作成ます。

 

完成図
full_custom_view.gif

 

サンプルのソースコード
MultiTextView.java


  1. ビューを継承したクラスを生成する
  2. onDraw(),onMeasuer()をオーバーライドする
  3. カスタムAttributeを使用する
  4. レイアウト(XML)に定義する

ビューを継承したクラスを生成する

まずはビューを継承したクラスを生成し、コンストラクタを定義します。

public class MultiTextView extends View {
	/**
	 * コンストラクタ
	 * @param context
	 */
	public MultiTextView( Context context) {
		super( context);
	}
	/**
	 * XMLからの生成用コンストラクタ
	 * AttributeSetを引数に取るコンストラクタがないとXMLからの生成時にエラーになる
	 * @param context
	 * @param attrs
	 */
	public MultiTextView( Context context, AttributeSet attrs) {
		super( context, attrs);
	}

onDraw(),onMeasure()をオーバーライドする

onDraw(),onMeasure()をオーバーライドし、それぞれに描画、ビューのサイズを設定の処理を記述します。

	@Override
	protected void onDraw(Canvas canvas) {
		// 背景の描画
		Paint bgPaint = new Paint();
		bgPaint.setColor( Color.DKGRAY);
		canvas.drawRect( getLeft(), getTop(), getRight(), getBottom(), bgPaint);
		// メインテキスト用ペイント
		Paint mainTextPaint = new Paint();
		mainTextPaint.setColor( Color.WHITE);
		mainTextPaint.setTextSize( 25);
		mainTextPaint.setAntiAlias( true);
		// メインテキストの描画
		if( mainText != null){
			PointF textPoint = getTextPoint(mainTextPaint, mainText, width / 2 - 5, height / 2);
			canvas.drawText( mainText, textPoint.x, textPoint.y, mainTextPaint);
		}
		// サブテキスト用ペイント
		Paint subTextPaint = new Paint();
		subTextPaint.setColor( Color.LTGRAY);
		subTextPaint.setTextSize( 12);
		subTextPaint.setAntiAlias( true);
		// サブテキストの描画
		if( subText != null){
			PointF textPoint1 = getTextPoint(subTextPaint, subText, 40, 35);
			canvas.drawText( subText, textPoint1.x, textPoint1.y, subTextPaint);
		}
	}
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// ビューのサイズを設定する
		setMeasuredDimension( width, height);
	}

カスタムAttributeを使用する

ここまでで基本的な部分は出来ていますが、最後にXMLから表示する文字列を指定できるようにAttributeの処理を追加します。

	/**
	 * XMLからの生成用コンストラクタ
	 * AttributeSetを引数に取るコンストラクタがないとXMLからの生成時にエラーになる
	 * @param context
	 * @param attrs
	 */
	public MultiTextView( Context context, AttributeSet attrs) {
		super( context, attrs);
		// 独自のAttribute処理
		initAttribute( attrs);
	}
	/**
	 * 独自のAttribute処理。
	 * mainText,subTextを取得する。
	 * @param attrs
	 */
	private void initAttribute(AttributeSet attrs){
		mainText = attrs.getAttributeValue(null, "mainText");
		subText = attrs.getAttributeValue(null, "subText");
	}

レイアウト(XML)に定義する

ビューのクラスをパッケージ名から定義し、独自Attributeを定義します。

<android.wiki.sample.MultiTextView
        android:id="@+id/multiText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        mainText="10"
        subText="+2" />

既存のビューを組み合わせたコンポジット・ビュー

既存のビューを組み合わせて新たにひとつのビューを作成する方法について説明します。
ここでは、テキストの入力と日付選択を行うためのボタンをセットにしたコンポーネントを作成します。

 

完成図
composite_view.gif

 

サンプルのソースコード
DateView.java


  1. レイアウトを継承したクラスを生成する
  2. 部品を生成し、レイアウトに追加する
  3. 処理を記述する
  4. レイアウト(XML)に定義する

レイアウトを継承したクラスを生成する

まずはレイアウトを継承したビューを生成します。
レイアウトは作りたい部品に合わせて用途に合ったレイアウトを選択してください。

今回は横に2つ並べるだけなので、LinearLayoutを使用しています。

public class DateView extends LinearLayout{
	public DateView(Context context) {
		super(context);
	}
	public DateView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

部品を生成し、レイアウトに追加する

次に初期化処理としてテキストとボタンの生成、ボタンへのリスナ設定を行います。

	private void init( Context context, AttributeSet attrs){
		setOrientation( HORIZONTAL);
		// テキストの生成
		text = new Text( context, attrs);
		addView( text);
		// ボタンの生成
		imageButton = new Button( context, attrs);
		imageButton.setText("日付");
		addView( imageButton, new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT));
		// リスナ設定
		imageButton.setOnClicKListener( this);
	}
	@Override
	public void onClicK(View v) {
		// デフォルトで今日の日付
		if( calendar == null){
			calendar = Calendar.getInstance();
		}
		int year = calendar.get( Calendar.YEAR);
		int month = calendar.get( Calendar.MONTH);
		int day = calendar.get( Calendar.DATE);
		DatePickerDialog dateDialog =  new DatePickerDialog(getContext(), this, year, month, day);
		dateDialog.show();
	}

処理を記述する

DatePickerDialogでSetが選択された際にテキストに日付の文字列を設定する処理を記述します。
その他日付の設定処理や委譲メソッドの生成など任意の処理を記述します。

	@Override
	public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
		calendar.set( Calendar.YEAR, year);
		calendar.set( Calendar.MONTH, monthOfYear);
		calendar.set( Calendar.DATE, dayOfMonth);
		updateText();
	}
	private void updateText(){
		int year = calendar.get( Calendar.YEAR);
		int month = calendar.get( Calendar.MONTH) + 1;
		int date = calendar.get( Calendar.DATE);
		text.setText( year + "/" + month + "/" + date);
	}

レイアウト(XML)に定義する

レイアウトに定義を行う場合はクラスをパッケージ名から指定します。

   <android.wiki.sample.DateView
       android:editable="true"
       android:id="@+id/date"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:singleLine="true" />