「みんなのコミック」は2018年10月31日を持ちまして更新を終了いたしました。
2020/11/09 追記: みんコミAdvent Calendarその他の知見を元に、漫画表示用カスタムビュー「MangaView」を公開しました。
はじめに
この記事は「みんコミ Advent Calendar」の18日目の記事です。
すみません。根雪れい(@neyuki_rei)さんの「おかあさん(10)と僕。」を読んでいたらアドベントカレンダーの更新が遅れてしまいました。
10歳になったおかあさんと僕の生活をマンガにしましたよ~。見てくださいね ! https://t.co/UfxCfaOYRZ #みんコミ #みんなのコミック pic.twitter.com/2nHBnFus4n
— 根雪れい.おかあさん(10)と僕。連載中 (@neyuki_rei)
いやはや。まさかのお風呂回に驚きました。布団の柄とかが微妙に凝っていて細部を見ていくと楽しいです。
さて、アドベントカレンダー本編です。
「みんコミ」のAndroidアプリ(バージョン1.0.3)をベースに執筆しています。スクリーンショットは極力控える方針ですので、本記事を読む際には、「Google Play Store」からアプリをインストールしておくことをお勧めします。
「みんコミ」アプリのコミックビューアー。
僕はたいていタブレット(Nexus 9、Xperia Z3 Tablet Compact)で読んでいます(Nexus 5Xは画面が小さいので……)。
しかし、今日(正確には昨日)Nexus 5Xで作品を読んでいて、拡大した状態で勢いよくフリックしてもスクロールが加速しないことに気づきました。
個人的にはぴゅんと表示が跳んで欲しいと思います。
この処理はScroller
を使うと比較的簡単に実装できます。
サンプルプロジェクトをGitHubに置いておきます。
https://github.com/keiji/adventcalendar_2015_mincomi
サンプル「スクロールサンプル(ScrollerActivity)」では、赤い■をフリックすると移動するようにプログラムしています。
まず、GestureDetectorでflingジェスチャーを取得します。
使い方は簡単です。GestureDetectorのインスタンスにonTouchが受け取るMotionEventを渡すだけで、onTouchEventの状況から判定して所定のジェスチャーのコールバック(今回の場合はonFling)が呼びます
<br />public class ScrollerView extends View {
private static final String TAG = ScrollerView.class.getSimpleName();
private static final int SIZE = 100;
private static final Paint PAINT = new Paint();
static {
PAINT.setColor(Color.RED);
}
private Rect mRect;
private final GestureDetectorCompat mGestureDetector;
public ScrollerView(Context context) {
super(context);
mGestureDetector = new GestureDetectorCompat(context, mOnGestureListener);
setFocusable(true);
}
GestureDetector.OnGestureListener mOnGestureListener = new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return true;
}
};
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent " + event.getAction());
boolean handled = mGestureDetector.onTouchEvent(event);
if (handled) {
return true;
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mRect == null) {
mRect = new Rect(0, 0, SIZE, SIZE);
int left = (canvas.getWidth() - SIZE) / 2;
int top = (canvas.getHeight() - SIZE) / 2;
mRect.offset(left, top);
}
canvas.drawRect(mRect, PAINT);
}
}
サンプルでは、Compatibility Libraryに含まれているGestureDetectorCompatを使用しています。
簡単と言っても、OnGestureListenerの各メソッドの戻り値には気をつけて下さい。Android Studioで自動生成するとデフォルトで各メソッドはfalse
を返しますが、そのままではACTION_DOWN以外のイベントを受け取れません(当然、ジェスチャも受け取れません)。
適宜true
を返すようにしましょう。
つぎはScroller
です。
インスタンス化し、onFlingで受け取った各種の値をそのままflingメソッドに渡します。これで、flingが始まってからの時間に応じて自動的にスクロール量を計算してくれます。
なおGoogleは、OverScrollerを使用するように推奨しています。
<br />public class ScrollerView extends View {
private static final String TAG = ScrollerView.class.getSimpleName();
private static final int SIZE = 100;
private static final Paint PAINT = new Paint();
static {
PAINT.setColor(Color.RED);
}
private Rect mRect;
private final OverScroller mScroller;
private final GestureDetectorCompat mGestureDetector;
public ScrollerView(Context context) {
super(context);
mScroller = new OverScroller(context);
mGestureDetector = new GestureDetectorCompat(context, mOnGestureListener);
setFocusable(true);
}
GestureDetector.OnGestureListener mOnGestureListener = new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling(mRect.centerX(), mRect.centerY(),
Math.round(velocityX), Math.round(velocityY),
0, getWidth() - SIZE, 0, getHeight() - SIZE,
SIZE / 2, SIZE / 2);
ViewCompat.postInvalidateOnAnimation(ScrollerView.this);
return true;
}
};
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent " + event.getAction());
boolean handled = mGestureDetector.onTouchEvent(event);
if (handled) {
return true;
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mRect == null) {
mRect = new Rect(0, 0, SIZE, SIZE);
int left = (canvas.getWidth() - SIZE) / 2;
int top = (canvas.getHeight() - SIZE) / 2;
mRect.offset(left, top);
}
canvas.drawRect(mRect, PAINT);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
mRect.offsetTo(mScroller.getCurrX(), mScroller.getCurrY());
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
fling
からViewCompat.postInvalidateOnAnimation(this);
を実行するとcomputeScroll()
が呼び出されます。
mScroller.computeScrollOffset()
でスクロールの状態を計算し、(flingが続いていれば)その結果をRect
の位置として設定しています(ここで得られる値は、Scrollerのflingメソッドに与えた値を基にした絶対座標です)。
mScroller.computeScrollOffset()
がtrueである限りpostInvalidateOnAnimation
とcomputeScroll
の連鎖が続き、やがてScrollerが完了すると移動も止まります。
なお、OverScrollerはその名の通り、最後の値を設定すればオーバースクロールを有効にして、バウンスさせることができます。例では赤い■のサイズの半分の量だけオーバースクロールさせるように設定しています。
あらためて、実装例をGitHubに置いておきます。
サンプルプロジェクトをGitHubに置いておきます。
https://github.com/keiji/adventcalendar_2015_mincomi
実装参照
- http://developer.android.com/intl/ja/training/gestures/detector.html
- http://developer.android.com/intl/ja/training/gestures/scroll.html
「有山圭二」は「みんなのコミック」及び運営の「株式会社イーブックイニシアティブジャパン」とは一切関係がありません。
また、本アドベントカレンダーの内容はあくまで参加者個人の見解です。
「みんなのコミック」の評価を目的とするものではありませんので、ご了承下さい。
それでは明日19日の担当は、この記事を書くに当たってGestureDetectorCompatの存在を知ったけど、Compat系のクラスには嫌な思い出しかないという「有山圭二」さんです。
よろしくお願いします。