WebView でスムーズにスクロールさせたいときに指定する引数

WebView のスクロールには絶対位置指定の scrollTo(x,y) と 相対位置指定の scrollBy(dx,dy) の他に、スムーズにスクロールさせる flingScroll(vx, vy) があります。

flingScroll は API Level 1 から存在するにもかかわらずほとんどドキュメント化されておらず、「フリック操作をシミュレートする関数」くらいの情報しか存在しません。


そこで WebView のソースを読んでみました。


どうやら OverScroller.fling に委譲している風。


OverScroller はその名の通り、iPhone 由来のあのフリックによる加速度付きのスクロールを実現・提供するクラスです。このクラスのインスタンスに初速だけ指定すれば、あとは時間と共に自動スクロールする仕組みです。

この「初速」が引数 vx, vy なのですが、例えば下方向に1画面分だけスクロールさせたいときはどのような値を与えれば良いでしょうか

OverScroller が提供しているのは単純な等加速度直線運動です。
例えば位置を更新するupdate メソッドを見ると、

834            double distance;
835            final float t = duration / 1000.0f;
...
838                distance = mVelocity * t + mDeceleration * t * t / 2.0f;
...
845            mCurrentPosition = mStart + (int) distance;

とあるように、マイナスの加速度(減速方向の加速度)mDeceleration により減速しています。この値は初速により符号が変わるだけで、定数 GRAVITY として定義されていました。

499        static void  initializeFromContext(Context context) {
500            final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
501            GRAVITY = SensorManager.GRAVITY_EARTH // g (m/s^2)
502                    * 39.37f // inch/meter
503                    * ppi // pixels per inch
504                    * ViewConfiguration.getScrollFriction();
505        }

ここまで分かれば単なる二次関数の最大値を求める問題です。
すなわち、初速のvelocityをv_0、mDecelerationを\alphaとすると、
y = \frac{1}{2} \alpha t^2 +v_0 t + y_0
yの最大値は
y_{\max} = \frac{v_0^2 + 2 \alpha y_0}{2 \alpha} = \frac{v_0^2}{2 \alpha}
となるため、v_0
v_0=\sqrt{2 \alpha y_{\max}}
となります。y_{\max}はつまるところフリックスクロールが停止するときの位置ですので、これに「1画面分のピクセル値」を指定すればいいわけです。

もったいぶりましたが、コードにするとたったこれだけ!

final float ppi = getApplicationContext().getResources().getDisplayMetrics().density * 160.0f;
final float GRAVITY = SensorManager.GRAVITY_EARTH // g (m/s^2)
                    * 39.37f // inch/meter
                    * ppi // pixels per inch
                    * ViewConfiguration.getScrollFriction();
final float velocity = (float) Math.sqrt(2 * GRAVITY * h);
webView.flingScroll(0, (int) velocity);

ここで指定する h を webView.getHeight() にすれば1画面分だけスクロールできるわけですね。
あーすっきり。


参考: