[Android]SurfaceViewでページめくりを実装する

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ひよつこ

[Android]SurfaceViewでページめくりを実装する

#1

投稿記事 by ひよつこ » 12年前

こんばんは。ひよつこです。
今回いわゆるページめくりのできるViewを作りたいと思いSurfaceViewを継承したものを作ってみました。

コード:

public class PageView extends SurfaceView implements OnTouchListener, Callback{

	private int				mWidth,mHeight;
	private Bitmap			prevImage;		//左ページの画像
	private Bitmap			currImage;		//現在表示中の画像
	private Bitmap			nextImage;		//右ページの画像
	private final Matrix	matrix = new Matrix();
	private Paint			mPaint = new Paint();
	private PageListener	listener = null;		//独自実装のリスナ
	private int				page = 0;
	private int				maxPage = 100;

	public PageView(Context context) {
		this(context, null);
	}

	public PageView(Context context, AttributeSet attrs){
		super(context, attrs);
		this.getHolder().addCallback(this);
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder){
		this.setOnTouchListener(this);
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
		mWidth = width;
		mHeight = height;
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder){
		this.setOnTouchListener(null);
		listener = null;
	}

	private PointF		touchStartPoint		= new PointF();
	private PointF		totalMove			= new PointF();
	private int			touchMode			= TOUCH_NONE;
	private static final int	TOUCH_NONE  = 0;
	private static final int	TOUCH_FLICK = 1;

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		switch(event.getAction() & MotionEvent.ACTION_MASK){
			case MotionEvent.ACTION_DOWN:
				//フリック操作開始
				if(touchMode == TOUCH_NONE & event.getPointerCount() == 1){
					touchStartPoint.x = event.getX();
					touchMode = TOUCH_FLICK;
				}
				break;
			case MotionEvent.ACTION_MOVE:
				//フリック操作中
				if(touchMode == TOUCH_FLICK){
					if(event.getX() < touchStartPoint.x && page < maxPage){
						turningPageRight(event.getX() - touchStartPoint.x);
					}else if(event.getX() > touchStartPoint.x && page > 0){
						turningPageLeft(event.getX() - touchStartPoint.x);
					}
				}
				break;
			case MotionEvent.ACTION_UP:
				//フリック操作終了
				if(touchMode == TOUCH_FLICK){
					matrix.reset();
					totalMove.x = event.getX() - touchStartPoint.x;
					if(touchStartPoint.x - event.getX() > mWidth / 2 && page < maxPage){
						//右向きにページめくり
						listener.onTurnedRight();
						toNext(prepareImage(listener.onChangedNext(page)));
						//↑で次の右ページの画像を取得
					}else if(event.getX() - touchStartPoint.x > mWidth / 2 && page > 0){
						//左向きにページめくり
						listener.onTurnedLeft();
						toPrevious(prepareImage(listener.onChangedPrevious(page)));
						//↑で次の左ページの画像を取得
					}else{
						this.doDraw();
					}
					touchMode = TOUCH_NONE;
				}
				break;
			}
		return true;
	}

	private void turningPageRight(float move){
		matrix.reset();
		matrix.postTranslate(move, 0.0f);
		Matrix mat = new Matrix();
		SurfaceHolder holder = getHolder();
		Canvas canvas = holder.lockCanvas();
		canvas.drawColor(0xff000000);
		canvas.drawBitmap(currImage, matrix, mPaint);
		mat.postTranslate(move + mWidth, 0.0f);
		canvas.drawBitmap(nextImage, mat, mPaint);
		holder.unlockCanvasAndPost(canvas);
	}

	private void turningPageLeft(float move){
		matrix.reset();
		matrix.postTranslate(move, 0.0f);
		Matrix mat = new Matrix();
		mat.postTranslate(move - mWidth, 0.0f);
		SurfaceHolder holder = getHolder();
		Canvas canvas = holder.lockCanvas();
		canvas.drawColor(0xff000000);
		canvas.drawBitmap(currImage, matrix, mPaint);
		canvas.drawBitmap(prevImage, mat, mPaint);
		holder.unlockCanvasAndPost(canvas);
	}

	protected void Draw(SurfaceHolder holder){
		//TODO 描画処理
		Canvas canvas = holder.lockCanvas();
		canvas.drawColor(0xff000000);
		canvas.drawBitmap(currImage, matrix, mPaint);
		holder.unlockCanvasAndPost(canvas);
	}

	private Bitmap prepareImage(Bitmap res){
		Bitmap result = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_8888);
		float scaleX = (float) mWidth / res.getWidth();
		float scaleY = (float) mHeight / res.getHeight();
		Matrix m = new Matrix();
		if(scaleX < scaleY){
			m.preScale(scaleX, scaleX);
			m.postTranslate(0, (mHeight - (res.getHeight() * scaleX) ) / 2);
		}else if(scaleX > scaleY){
			m.preScale(scaleY, scaleY);
			m.postTranslate((mWidth - (res.getWidth() * scaleY) ) / 2, 0);
		}else{
			m.postScale(scaleX, scaleY);
		}
		Canvas canvas = new Canvas(result);
		canvas.drawBitmap(res, m, new Paint());
		return result;
	}

	public void toNext(Bitmap moreNext){
		prevImage = currImage;
		currImage = nextImage;
		nextImage = moreNext;
		page++;
		this.doDraw();
	}

	public void toPrevious(Bitmap morePrev){
		nextImage = currImage;
		currImage = prevImage;
		prevImage = morePrev;
		page--;
		this.doDraw();
	}

	public void setListener(PageListener lis){
		if(lis != null){
			listener = lis;
		}
	}

	public void doDraw(){
		Draw(getHolder());
	}
}
しかし、実行してみたところページめくりの際に、画面から指を離すとすぐに次のページが描画されるため、
動きがカクカクした感じになってしまいました。
そこで滑らかにページめくりをする方法をお教え願いたいです。
(画面の端までページが移動するような感じ)
どうかよろしくお願いします。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#2

投稿記事 by ISLe » 12年前

Androidの機能を使わない縛りでしょうか。
そうでなければViewFlipperとかTweenアニメーションをキーワードに調べてみてください。

ひよつこ

Re: [Android]SurfaceViewでページめくりを実装する

#3

投稿記事 by ひよつこ » 12年前

返信ありがとうございます。

ViewFlipperやTweenアニメーションの件ですが、
今回のViewではピンチ操作でズーム処理をするので
SurfaceViewの高速で描画できる点が良いと思いSurfaceViewを使いましたが
SurfaceViewで今回のような処理は難しいでしょうか。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#4

投稿記事 by ISLe » 12年前

ViewFlipperは、View(あるいは派生したView)を入れ子に配置して使います。
ViewFlipperの中にSurfaceViewを配置すれば良いのではないでしょうか。

ピンチ操作でズーム処理するのにSurfaceViewの高速な描画が必要だというのは何か根拠があるのでしょうか。
例えばTweenアニメーションではSurfaceViewに限らず滑らかに動くわけですが。

ひよつこ

Re: [Android]SurfaceViewでページめくりを実装する

#5

投稿記事 by ひよつこ » 12年前

返信ありがとうございます。
反応遅れて済みませんでした。

ViewFlipperやViewのAnimationについても考えましたが、
このViewはOnTouchListenerを使って画面をスライドやピンチ操作中に
画像の平行移動やズーム処理をすることを考えています。

そのため、ViewFlipperやAnimationでは操作中の処理がなくなってしまい
他の手段を考える必要があります。

操作中の処理とその後の調整(画面外にスライドアウトなど)さえできればSurfaceViewでなくてもいいのですが...。
よろしくお願いします。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#6

投稿記事 by ISLe » 12年前

おっしゃっていることがよく分かりませんが、Viewの表示座標を変更する方法は取れないということですか?

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#7

投稿記事 by ISLe » 12年前

やりたいことというのは、ページめくりではなくて、指を離したあとも自動でスクロールするようにしたいということですかね。
だとしたらフリックでページめくりの処理というのは余計じゃないですかね。

タッチイベント外でスクロールさせたいのであれば、スレッドやタイマーを使って少しずつ座標をずらしてViewを更新してやれば良いでしょう。
メモリリークに注意してください。

ひよつこ

Re: [Android]SurfaceViewでページめくりを実装する

#8

投稿記事 by ひよつこ » 12年前

返信ありがとうございます。
説明が下手で済みませんでした。

もう少し詳しく説明させていただきますと、
画面をなぞる指にあわせて表示している画像を掃けさせ、
画面から指を離した時に画面の外に完全にフェードアウトさせる
というものを目指しています。

Threadを使った方法が良いかと思いますが
そうすると画面から指を離す度にThreadを開始し
処理が終わる度にThreadを停止しなくてはなりません。
しかしイマイチThreadを開始したり終了させたりする方法がわかりません。

まだ説明がわかりにくいかと思いますがよろしくお願いします。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#9

投稿記事 by ISLe » 12年前

Javaのマルチスレッドは他の言語処理系と比較して容易で、多方面で利用されており、情報も豊富です。
この機会に習得されることをお勧めします。
ひよつこ さんが書きました:そうすると画面から指を離す度にThreadを開始し
処理が終わる度にThreadを停止しなくてはなりません。
そんなことはありません。
ビューの生成と同時にスレッドを起動して、必要のないときは待機状態にしておくのが良いと思います。
ひよつこ さんが書きました:しかしイマイチThreadを開始したり終了させたりする方法がわかりません。
Threadクラスをごくふつうに使えば、勝手に起動して勝手に終了します。

ひよつこ

Re: [Android]SurfaceViewでページめくりを実装する

#10

投稿記事 by ひよつこ » 12年前

返信ありがとうございます。

とりあえずThreadをstartしてみたのですが
他のViewの処理をしようとしたところエラーで落ちました。

なぜ落ちたのでしょうか。
また、Threadを途中でとめておくにはどうしたらいいでしょうか。

初歩的な質問でしたら済みませんがよろしくお願いします。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#11

投稿記事 by ISLe » 12年前

ひよつこ さんが書きました:とりあえずThreadをstartしてみたのですが
他のViewの処理をしようとしたところエラーで落ちました。

なぜ落ちたのでしょうか。
申し訳ありませんが、わたしはなぜとだけ問われて答えられるような能力を持ち合わせておりません。
ひよつこ さんが書きました:また、Threadを途中でとめておくにはどうしたらいいでしょうか。
スレッドはwaitメソッドで停止します。
再開するにはnotifyメソッドやnotifyAllメソッドを使います。

ひよつこ

Re: [Android]SurfaceViewでページめくりを実装する

#12

投稿記事 by ひよつこ » 12年前

返信ありがとうございます。

やはりどうしても他のViewを操作したかったので、
しばらく調べてみたところHandlerというものを使えば良いとわかりました。
何やらメインスレッドと自分の作ったThreadを交信させる(?)ようなのですが
Handler.sendMessage()やHandler.post()などというメソッドなどがあり、
どれを使えばいいかがよくわかりません。

頻繁に呼び出すときにはどの関数を使えば良いのでしょうか。
よろしくお願いします。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#13

投稿記事 by ISLe » 12年前

Threadクラスは、Runnableインターフェースを実装した任意のクラスに対して使えます。
並行して実行されるrunメソッドであっても、メンバ等にアクセスする方法は通常のメソッドと同じです。

他のViewを参照したければ、そのViewを参照できるようにしなければいけません。

そもそも、ページめくりをできるようにしたいViewにRunnableインターフェースを実装すれば良いと思うのですが、どうして他のViewを操作する必要があるのでしょうか。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#14

投稿記事 by ISLe » 12年前

onTouchメソッドで、MotionEvent.ACTION_UPでもまだスクロールさせる必要があるときにHandlerを使ってスレッドを起動しよう、ということですかね。

Handlerを使うサンプルはRunnableインターフェースを匿名で実装するクラスばかりですけど、PageViewクラスの入れ子ならPageView.thisでPageViewのインスタンスを参照できます。
個人的にオススメはしませんけど。

スクロールが完了する前に再びタッチされたらどうするのかとか、起動済みのスレッドとの連携で面倒なことになりそうなので、自動スクロールのスレッドは一本化しておいたほうが良い気がします。

ひよつこ

Re: [Android]SurfaceViewでページめくりを実装する

#15

投稿記事 by ひよつこ » 12年前

返信ありがとうございます。
onTouchメソッドで、MotionEvent.ACTION_UPでもまだスクロールさせる必要があるときにHandlerを使ってスレッドを起動しよう、ということですかね。
その通りです。説明が難しかったので伝わって良かったです。

また、Handlerを使いたいというのはPageView内のスレッドでページめくり処理が終わった時に
リスナーを介してActivityからTextViewの表示を変更するためです。
なのでHandlerを使わないもっと良い方法があるなら教えて頂きたいです。

長くなって済みませんがよろしくお願いします。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: [Android]SurfaceViewでページめくりを実装する

#16

投稿記事 by ISLe » 12年前

ひよつこ さんが書きました:また、Handlerを使いたいというのはPageView内のスレッドでページめくり処理が終わった時に
リスナーを介してActivityからTextViewの表示を変更するためです。
なのでHandlerを使わないもっと良い方法があるなら教えて頂きたいです。
既にわたしは、以下のとおり書いております。
ISLe さんが書きました:そもそも、ページめくりをできるようにしたいViewにRunnableインターフェースを実装すれば良いと思うのですが
Handlerを使わなくてもリスナーを介してActivityにアクセスすることは可能です。

とりあえずスレッドの学習として、タッチと関係なく、自動でスクロールして次のページに移っていく処理(いわゆるスライドショー)を組み込んでみるというのはいかがですか。

閉鎖

“C言語何でも質問掲示板” へ戻る