Quantcast
Channel: アカベコマイリ » Android
Viewing all articles
Browse latest Browse all 10

Android の ImageView をスクロールさせる 2

$
0
0

さいきん書いた、Android の ImageView をスクロールさせるで作ったサンプルは、移動範囲を一定に留めるという実装にしたのだが、これは一般的な挙動ではないようだ。

多くのアプリ、特に iOS 向けの画像ビューアーや電子書籍アプリで採用されている挙動を見ると、だいたい以下のようになっていた。

  • コンテンツの表示モードがフィット、原寸のいずれでもスワイプ移動を実行できる
  • 画面に指が触れている間はスワイプ移動、離すと移動が終了する
  • 移動が終了した時点でコンテンツが画面に収まるならば、その位置へ移動
  • 移動が終了した時点でコンテンツが画面に収まらないなら、収まるように位置を補正する
  • 移動が終了した時点でコンテンツが画面に収まらないとき、余白のサイズが一定以上なら、前 or 次のコンテンツを選択

この動きを再現すべく、前回のサンプルを改造してみる。

移動と位置の補正

移動を Android のタッチ操作イベントで表すと、MotionEvent.ACTION_DOWN が起点となり、ACTION_MOVE は移動中、ACTION_UP で終了となる。これは指の押し下げ、移動、押し上げに対応している。

前回のサンプルでは、これらの内、ACTION_MOVE と ACTION_UP のどちらでも同じ移動処理を実行していたが、今回は位置の補正を ACTION_UP のみとする。つまり、指をグリグリさせている間はどんな位置へも移動できて、指を離したときだけ位置を適正な範囲に直す。

処理としては、以下のようになる。

/**
 * View がタッチされた時に発生します。
 *
 * @param v     タッチされた View。
 * @param event イベント データ。
 *
 * @return タッチ操作を他の View へ伝搬しないなら true。する場合は false。
 */
public boolean onTouch( View v, MotionEvent event ) {
    switch( event.getAction() ) {
    case MotionEvent.ACTION_DOWN:
        this.mTouchBeginX = event.getX();
        this.mTouchBeginY = event.getY();
        break;

    case MotionEvent.ACTION_MOVE:
        float x = event.getX(), y = event.getY();
        this.scrollImage( x, y, true );

        this.mTouchBeginX = x;
        this.mTouchBeginY = y;
        break;

    case MotionEvent.ACTION_UP:
        this.scrollImage( event.getX(), event.getY(), false );
        break;
    }

    return true;
}

/**
 * 画像のスクロールを実行します。
 *
 * @param x        スクロール基準となる X 座標。
 * @param y        スクロール基準となる Y 座標。
 * @param isMoving 移動中の場合は true。それ以外は false。
 *
 * @return 画像を切り替えない場合は 0。前に戻すなら -1、次に進めるときは 1。
 */
private void scrollImage( float x, float y, boolean isMoving ) {
    if( isMoving ) {
        int moveX = ( int )( this.mTouchBeginX - x );
        int moveY = ( int )( this.mTouchBeginY - y );
        this.mImageView.scrollBy( moveX, moveY );

    } else if ( this.mImageScaleType == ImageView.ScaleType.FIT_CENTER ) {
        this.scrollFinish( x, y, 0, 0 );

    } else {
        this.scrollFinish( x, y, this.mOverX, this.mOverY );
    }
}

移動中に呼び出された scrollImage メソッドは、単純な移動を実行する。ACTION_UP 時は scrollFinish メソッドで補正をおこなう。

今回はフィット・原寸に依存せず移動をおこなうので、大半の処理は共通化できる。これらの差異は画面からはみ出る量なので、共通メソッドである scrollFinish では、それをパラメータとして括りだしている。

位置の補正と画像きりかえ

スワイプ移動が完了した時に呼び出される scrollImage メソッドの実装は以下のようになる。

/**
 * 画像のスクロールが完了した時の処理を実行します。
 *
 * @param x     スクロール基準となる X 座標。
 * @param y     スクロール基準となる Y 座標。
 * @param overX 画面を基準として、画像の表示領域が X 軸にはみ出ている量。
 * @param overY 画面を基準として、画像の表示領域が Y 軸にはみ出ている量。
 */
private void scrollFinish( float x, float y, int overX, int overY ) {
    int moveX = this.calcScrollValue( ( int )( this.mTouchBeginX - x ), this.mImageView.getScrollX(), overX );

    switch( this.mSwitchFlag ) {
    case -1: this.updateImage( this.mPictureManager.prev() ); return;
    case  1: this.updateImage( this.mPictureManager.next() ); return;

    default:
        int moveY = this.calcScrollValue( ( int )( this.mTouchBeginY - y ), this.mImageView.getScrollY(), overY );
        this.mImageView.scrollBy( moveX, moveY );
        break;
    }
}

まず位置の補正だが、これは前回のサンプルと同様に、calcScrollValue でおこなう。このメソッドは、タッチ座標、ImageView のスクロール位置、画面からはみ出る量をパラメータとして渡すと、適正なスクロール量を返すようになっている。
よって、この値を X、Y 軸で算出して ImageView.scrollBy メソッドに指定すれば、自動的に適切な位置へ移動される。

ただし、今回は一般的な画像ビューアーのように画像の切り替えもおこないたいので、画像の端から見て一定以上のスワイプ移動が発生したら、前後の画像が選択されるようにする。上記コードでいうと、232、233 行目の処理がそれにあたる。

calcScrollValue の中では位置の補正と共に、画像の切り替えフラグとなるフィールドも更新し、それを判定している。実装は以下のようになる。

/**
 * スクロール量を算出します。
 *
 * @param move 移動する予定の量。
 * @param pos  現在のスクロール座標
 * @param over 画像が表示領域の一辺からはみ出る量。
 *
 * @return スクロール量。
 */
private int calcScrollValue( int move, int pos, int over ) {
    int newPos = pos + move;
    if( newPos < -over ) {
        move = -( over + pos );
        this.mSwitchFlag = ( newPos < -( over + this.mSwitchSize ) ? -1 : 0 );

    } else if( over < newPos ) {
        move = over - pos;
        this.mSwitchFlag = ( ( over + this.mSwitchSize ) < newPos ? 1 : 0 );

    } else {
        this.mSwitchFlag=0;
    }

    return move;
}

mSwitchFlag というフィールドがフラグとなる。右スワイプ時に一定範囲を超えたら前、左なら次の画像を選択するフラグを立てる。もっと上手くやるなら、戻り値を long にして上位・下位ワードにフラグとスクロール量の int 値をパックしてもよさそうだ。

mSwitchSize が切り替えの許容量となる。画像の端に到達した時、それとこの量を足した幅を移動したら、切り替えとみなす。この量は画面の短辺の 1/4 としている。480×800 の端末なら、短辺が 480 なので 120px となる。このさじ加減は難しいところである。短辺の 1/4 だと、タブレットのような大画面では切り替えが大変かもしれない。

サンプル プロジェクト

最後に、サンプル アプリのプロジェクトを公開しておく。

サンプル プロジェクト
TestImageScroll2.zip 366KB

Android 2.1 update 1 ( API Level 7 ) でビルドし、エミュレータと初代 Xperia ( SO-01B ) にて動作確認をおこなった。

文章やコードだけで動きを説明するのは難しいのだが、サンプルを実際に動かしてみれば、ああ、あの動きのことをいっているのだな、と合点がゆくと思う。


Viewing all articles
Browse latest Browse all 10

Trending Articles