ScrollView實現以慣性滑動的形式滑動到任意位置/禁止慣性滑動/監聽慣性滑動

2016年11月04日 21:55:04 csdn
讀完這篇博客可以實現:
1.scrollview從任意位置通過慣性滑動到任意位置
2.獲取手離開屏幕后慣性滑動的距離(時間也可以)
3.既然可以控制慣性滑動了,那么有時候慣性滑動造成的各種被重復觸發事件導致的bug也就可以解決了。

  • 在scrollview中重寫fling()方法,他的參數是用戶滑動時,松開手的那一瞬間的初速度,他決定了view慣性滑動的時間和距離,這個參數可以根據需求傳入任意值,從而達到各種效果。

  • 傳入你要到達的位置(scrollY),計算出需要的初速度(這個初速度可以在上面那條中用)

/**
     * 通過目標y得到需要的初速度 (指頭向上滑,需要的初速度)
     * @param endY
     * @return
     * @author xuekai
     */
    public int getVelocityY(int endY){
        int signum=-1;//注意:如果指頭上滑,他肯定是-1,如果下滑,把這里改成1,我的需求肯定是上滑,所以寫死了
        double dis=(endY-upY)*signum;
       double g= Math.log(dis/ViewConfiguration.getScrollFriction()/ (SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * (context.getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f))* Math.log(0.9)*((float) (Math.log(0.78) / Math.log(0.9)) - 1.0)/(Math.log(0.78) );
       return (int) (Math.exp(g)/0.35f*(ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
                       * 39.37f // inch/meter
                       * (context.getResources().getDisplayMetrics().density * 160.0f)
                       * 0.84f));
    }
  • 通過上面第一條中的初速度可以計算出它將會滑動的距離(scrollY)
/**
     * 
     * @param velocityY 初速度
     * @return 滑動的距離
     * @author xuekai
     */
 public double getDis(int velocityY) {

        final double l = getG(velocityY);
        final double decelMinusOne = (float) (Math.log(0.78) / Math.log(0.9)) - 1.0;
        return ViewConfiguration.getScrollFriction() * (SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * (context.getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f) * Math.exp((float) (Math.log(0.78) / Math.log(0.9)) / decelMinusOne * l);
    }


    public double getG(int velocityY) {
        return Math.log(0.35f * Math.abs(velocityY) / (ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
                * 39.37f // inch/meter
                * (context.getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f));

    }

我是準備利用scrollview實現一個下拉刷新的功能,scrollview初始狀態下讓他的向上滾動1000(假設值)。然后在onTouch事件中做處理:只要是觸發action_up,并且scrollY小于1000,就自動滾動到1000,同時,在onScrollChanged中也做一個判斷,當處于抬起狀態(在action_down和action_up中設置boolean)并且scrollY小于1000,也滾動到1000.這樣一個簡單地下拉刷新實現了。
可是scrollTo這個方法會讓它瞬間到了某個位置,沒有下拉刷新中緩慢上升的效果,怎么辦呢? 好說,值動畫嘛?。?!
寫完了,但是發現沒效果,原因是松開手之后觸發了action_up,立即彈回去了。去掉它,只用onScrollChanged,又會受到慣性滑動的影響(慣性滑動的時候,已經是抬起狀態了。)
看源碼發現ScrollView中有一個方法 fling(int velocityY)

 /**
     * Fling the scroll view
     *
     * @param velocityY The initial velocity in the Y direction. Positive
     *                  numbers mean that the finger/cursor is moving down the screen,
     *                  which means we want to scroll towards the top.
     */
    public void fling(int velocityY) {
        if (getChildCount() > 0) {
            int height = getHeight() - mPaddingBottom - mPaddingTop;
            int bottom = getChildAt(0).getHeight();

            mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
                    Math.max(0, bottom - height), 0, height/2);

            if (mFlingStrictSpan == null) {
                mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
            }

            postInvalidateOnAnimation();
        }
    }

這個參數是松手那一瞬間的加速度。由于我的需求中,手指上滑不會出現這樣的問題,下滑才會,于是產生了這樣的代碼(在自定義scrollview中重寫它)

  @Override
    public void fling(int velocityY) {
        if (velocityY < 0) {
            super.fling(0);
            return;

        } else {
            super.fling(velocityY);
        }
    }

這樣的話問題解決了,并且在上滑的時候也有慣性滑動,但是上滑就變得死氣沉沉,因為上滑的初速度都是0了,這咋行,繼續看。
在上面那個方法里調用了

mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
                    Math.max(0, bottom - height), 0, height/2);  

點進去看

public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY, int overX, int overY) {
        // Continue a scroll or fling in progress
        if (mFlywheel && !isFinished()) {
            float oldVelocityX = mScrollerX.mCurrVelocity;
            float oldVelocityY = mScrollerY.mCurrVelocity;
            if (Math.signum(velocityX) == Math.signum(Math.signum(velocityY) == Math.signum(oldVelocityY)) {
                velocityX += oldVelocityX;
                velocityY += oldVelocityY;
            }
        }
        mMode = FLING_MODE;
        mScrollerX.fling(startX, velocityX, minX, maxX, overX);
        mScrollerY.fling(startY, velocityY, minY, maxY, overY);
    }

發現有一行代碼是正對Y方向的fling的
跟著繼續走

getSplineFlingDuration(velocity);
getSplineFlingDistance(velocity);

發現了這么兩個方法。這豈不是飛的距離,飛的時間都有了嗎,那這樣我就可以控制“飛行”了。
用反射,然而靜態內部類掉不到。照著寫一個吧。

 public double getSplineFlingDistance(int velocityY) {

        final double l = getG(velocityY);
        final double decelMinusOne = (float) (Math.log(0.78) / Math.log(0.9)) - 1.0;
        return ViewConfiguration.getScrollFriction() * (SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * (context.getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f) * Math.exp((float) (Math.log(0.78) / Math.log(0.9)) / decelMinusOne * l);
    }


    public double getG(int velocityY) {
        return Math.log(0.35f * Math.abs(velocityY) / (ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
                * 39.37f // inch/meter
                * (context.getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f));

    }

這樣就能得到了,也就是你在每次松開滑動的手的時候,可以知道他將滑動到的位置??墒沁@有什么卵用(不過有的一些需求下有用,對于我沒用)。 正好反了,我想要通過我要滑動到的位置來計算需要的初速度。

于是開始嘗試逆著算,數學里這叫還原法,不過不一定能還原出來。經過一系列的百度數學函數意思,結合高中忘得差不多的數學水平,終于還原出來了。

 /**
     * 通過目標y得到需要的初速度 (指頭向上滑,需要的初速度)
     * @param endY
     * @return
     */
    public int getVelocityY(int endY){
        int signum=-1;//如果指頭上滑,他肯定是-1;
        double dis=(endY-upY)*signum;
       double g= Math.log(dis/ViewConfiguration.getScrollFriction()/ (SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * (context.getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f))* Math.log(0.9)*((float) (Math.log(0.78) / Math.log(0.9)) - 1.0)/(Math.log(0.78) );
       return (int) (Math.exp(g)/0.35f*(ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
                       * 39.37f // inch/meter
                       * (context.getResources().getDisplayMetrics().density * 160.0f)
                       * 0.84f));
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容