Viewをカスタマイズ(独自実装)する


AndroidではViewクラスも拡張して独自機能を追加することが可能です。カスタマイズのサンプルとして、Viewにお絵かきできる機能を追加してみます。
今回、タッチ座標の取得にはonTouchEventを使っています。onTouchEventについては以前の記事タッチイベントを取得する(onTouchEventとMotionEvent)を参考にしてください。

独自Viewの作成方法は続きをどうぞ。

javaファイルの作成

Viewを継承するクラス名に応じてファイル名を決定します。
CustomView.java

1
2
3
4
5
6
package org.jpn.techbooster.sample.customViewActivity;
 
class CustomView extends View
{
    /* 省略 */
}

このとき、Viewのコンストラクタは3種類のいずれか、もしくはすべてを作成しておく必要があります。

コンストラクタの種類

  • public View(Context context)
  • public View(Context context, AttributeSet attrs)
  • public View(Context context, AttributeSet attrs, int defStyle)

レイアウトパラメータを指定された場合には、上から2番目のAttributeSetを持つコンストラクタが呼び出されます(layout側main.xmlファイルで定義した場合など)。またスタイルを指定した場合はdefStyleを持つ3つめのコンストラクタが利用されます。

今回は、単純にlayout側main.xmlで定義するのでpublic View(Context context, AttributeSet attrs)のみ実装しています。
CustomView.java

1
2
3
4
5
6
7
8
9
10
/**
 * XMLより呼び出す際のコンストラクタ
 * @param context, attrs
 */
public CustomView(Context context, AttributeSet attrs)
{
    super(context, attrs);
    setFocusable(true);
    initPaint();
}

コンストラクタ内でViewでフォーカスを取得できるように変更し、ペインタ機能のための初期化を行います。

独自クラスをLayoutファイルで記述する

res/layout/main.xml

1
2
3
<org.jpn.techbooster.sample.customViewActivity.CustomView
     android:id="@+id/View01" android:layout_width="wrap_content"
     android:layout_height="wrap_content" />

xmlファイルでの記述はURI+独自クラス名です。通常とは異なり、煩雑になるので注意してください。

Paintの実装

CustomView.java

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
/*
 * 描画用Paintの初期化
 * */
private void initPaint(){
    mPath = new Path();
 
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setDither(true);
    mPaint.setColor(0xFFFF0000);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeJoin(Paint.Join.ROUND);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setStrokeWidth(12);
}
 
/**
 * 画面サイズ変更時の通知
 * @param w, h, oldw, oldh
 */
protected void onSizeChanged(int w, int h, int oldw, int oldh){
    Log.v("View", "onSizeChanged Width:" + w + ",Height:" + h );
 
    //キャンバス作成
    mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    mCanvas = new Canvas(mBitmap);
}

はじめに、Canvasの用意とPaintなどリソースの初期化を行います。21行目、Canvasの作成に利用しているView#onSizeChangedメソッドはViewサイズが変更された場合に呼び出されるメソッドです。
Viewのサイズが確定するタイミングでもあるので、サンプルではBitmapの縦横サイズをここで決めています。

TouchEventに応じて線を引く

TouchEventでは、DownとUpをトリガに、押下中状態(MOVE)で線を引く処理を実装しています。
CustomView.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean onTouchEvent(MotionEvent event) {
    float x = event.getX();
    float y = event.getY();
 
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            touch_start(x, y);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE:
            touch_move(x, y);
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            touch_up();
            invalidate();
            break;
    }
    return true;
}

invalidateは再描画を行うメソッドです。invalidate()を通じてonDrawメソッドが呼び出されます。
(サンプルでは簡単化のために座標に動きがある場合は必ず再描画しています)

軌跡を描画する

CustomView.java

1
2
3
4
5
6
/* 描画関数 */
@Override
protected void onDraw(Canvas canvas) {
    canvas.drawBitmap(mBitmap, 0, 0, mPaint);
    canvas.drawPath(mPath, mPaint);
}

指の動きはCustomViewのメンバmPathに保存しています。canvas#drawPathで描画するとTouchEventを画面へ反映します。

おまけ

最後にタッチ開始・移動・終了の処理です。タッチによる描画処理、指の動きの保存(mPath)はAndroid SDKに付属のSample、ApiDemosのFingerPaintを参考に簡略化して実装しています。こちらも是非ご確認ください。
CustomView.java

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
29
30
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;//最小移動量
 
private void touch_start(float x, float y) {
    Log.v("View", "touch_start");
    mPath.reset();
    mPath.moveTo(x, y);
    mX = x;
    mY = y;
}
 
private void touch_move(float x, float y) {
    Log.d("View", "touch_move");
 
    float dx = Math.abs(x - mX);
    float dy = Math.abs(y - mY);
 
    if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
        mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
        mX = x;
        mY = y;
    }
}
private void touch_up() {
    Log.v("View", "touch_up");
 
    mPath.lineTo(mX, mY);
    mCanvas.drawPath(mPath, mPaint);
    mPath.reset();
}
2 Comments