Custom View

某講義の準備対応ということで下記サイトのサンプル掘削してます。

基本的な機能はタイトル通りなのですが、こないだの勉強会 (OJAG@Naha #25) で発信されてた Instagram クローンな iOS アプリと比べると完全にフレームワークに依存してるなぁ、と。
# その方が楽でしょ、という話もありますが

とりあえず

掘削したナニについて纏めを控えておくことにします。てソース確認してたら微妙なナニがいくつか出てきた。サンプルだから仕方無い、なのかどうなのか。
そのうちの一つは未選択な状態で trash ボタンを押した時の挙動。ArrayList の size の戻りが 0 より大きければ RelativeLayout#remove して ArrayList#remove してるんですが、とりあえず選択状態かどうかを確認しないとアレではないかと。

        mTrashButton = (Button)findViewById(R.id.trash_button);
        mTrashButton.setClickable(true);
        mTrashButton.setOnClickListener(new View.OnClickListener() {

            public void onClick(View v) {
                if(mViewsArray.size() > 0) {
                    RelativeLayout layout = (RelativeLayout)findViewById(R.id.style_layout);
                    layout.removeView(mViewsArray.get(mCurrentView);
                    mViewsArray.remove(mCurrentView);
                    mViewsCount -= 1;
                }
            }
        });

この mCurrentView というのは private な属性なんですが、public な getter/setter が用意されてて、Custom View 側の onTouchEvent なメソドの中で MotionEvent.ACTION_DOWN な分岐で設定してるんですが、逆に未選択な状態になったときにこれをリセットしてなかったりしてるあたりが微妙な所以。

きちんと掘削

主だったメソドのみ列挙してみます。

順に確認、ということでまずはコンストラクタから。最初、引数の Style てクラスは Android 標準の何か、なのかと思ってたんですが、Activity クラスの名前が Style なんずね。どこでどう繋ってるかが分からず、軽くパニックでした。
ちなみに Activity と View で互いの参照を持ってて属性の参照やら設定とかをしてますね。あと、コンストラクタ関連で興味深いのは mScaleDetector な属性を云々してる部分ですかね。init メソドにて以下な形で初期化。

        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());

ここで出てくる ScaleListener クラスは TouchView の内部クラスになってて定義が以下 (ログは除く)。

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();
            mScaleFactor = Math.max(0.05f, Math.min(mScaleFactor, 2.0f));
            invalidate();
            return true;
        }
    }

これ、拡大縮小 (scale) なイベントを検知して倍率まで検知できる模様。mScaleFactor は onDraw メソド (確か invalidate メソド呼び出し契機でナニ) の中で使われてます。

        canbas.scale(mScaleFactor, mScaleFactor);

解説なドキュメントによればバージョン 2.2 以降に依存している模様。また、onTouchEvent メソドの先頭で関連メソドが呼び出されてて

    public boolean onTouchEvent(MotionEvent event) {
        mScaleDetector.onTouchEvent(event);

ACTION_MOVE な分岐で pinch の最中かどうかを判定してますね。

    case MotionEvent.ACTION_MOVE:
        final int pointerIndex = event.findPointerIndex(mActivePointerId);
        final float x = event.getX(pointerIndex);
        final float y = event.getY(pointerIndex);
        if(!mScaleDetector.isInProgress()) {

この ScaleDetector クラス関連は色々な意味で面倒な部分をミドル側で吸収してくれてるのが分かるんですが、違った意味での微妙さを感じます。例えばどこまで独自実装が可能なのかとか (なんとなく独自のソレ、って不可能そうな印象)。

onDraw メソド

えーと、おおよそ何してるかは分かったのですが、選択時に onDraw で赤い枠線が描画されるその契機が不明。基本的に invalidate メソドを呼び出してるのは

  • ACTION_MOVE の時
  • pinch 操作
  • 白黒表示になる時

なんですが、ACTION_DOWN および ACTION_UP でも何かをしてる訳でもないですね。ただ、onDraw で

         mImage.draw(canvas);

した後で

         if(mSelected) {

が真の場合、枠の描画をしてるので、onDraw の度に mSelected の値で枠が出たり消えたりする、という気もしていたり。
ACTION_DOWN でこのイベントは終了、な意味での true を戻しているあたりがポイントなのかどうか。

続きは

別途、ということでとりあえずエントリ投入。