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を、mDecelerationをとすると、
のの最大値は
となるため、は
となります。はつまるところフリックスクロールが停止するときの位置ですので、これに「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画面分だけスクロールできるわけですね。
あーすっきり。
参考: