官术网_书友最值得收藏!

3.3 彈性滑動

知道了View的滑動,我們還要知道如何實現(xiàn)View的彈性滑動,比較生硬地滑動過去,這種方式的用戶體驗實在太差了,因此我們要實現(xiàn)漸近式滑動。那么如何實現(xiàn)彈性滑動呢?其實實現(xiàn)方法有很多,但是它們都有一個共同思想:將一次大的滑動分成若干次小的滑動并在一個時間段內(nèi)完成,彈性滑動的具體實現(xiàn)方式有很多,比如通過Scroller、Handler#postDelayed以及Thread#sleep等,下面一一進行介紹。

3.3.1 使用Scroller

Scroller的使用方法在3.1.4節(jié)中已經(jīng)進行了介紹,下面我們來分析一下它的源碼,從而探究為什么它能實現(xiàn)View的彈性滑動。

        Scroller scroller = new Scroller(mContext);

        // 緩慢滾動到指定位置
        private void smoothScrollTo(int destX, int destY) {
            int scrollX = getScrollX();
            int deltaX = destX - scrollX;
            // 1000ms內(nèi)滑向destX,效果就是慢慢滑動
            mScroller.startScroll(scrollX, 0, deltaX, 0, 1000);
            invalidate();
        }

        @Override
        public void computeScroll() {
            if (mScroller.computeScrollOffset()) {
                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
                postInvalidate();
            }
        }

上面是Scroller的典型的使用方法,這里先描述它的工作原理:當我們構(gòu)造一個Scroller對象并且調(diào)用它的startScroll方法時,Scroller內(nèi)部其實什么也沒做,它只是保存了我們傳遞的幾個參數(shù),這幾個參數(shù)從startScroll的原型上就可以看出來,如下所示。

        public void startScroll(int startX, int startY, int dx, int dy, int duration){
            mMode = SCROLL_MODE;
            mFinished = false;
            mDuration = duration;
            mStartTime = AnimationUtils.currentAnimationTimeMillis();
            mStartX = startX;
              mStartY = startY;
              mFinalX = startX + dx;
              mFinalY = startY + dy;
              mDeltaX = dx;
              mDeltaY = dy;
              mDurationReciprocal = 1.0f / (float) mDuration;
          }

這個方法的參數(shù)含義很清楚,startX和startY表示的是滑動的起點,dx和dy表示的是要滑動的距離,而duration表示的是滑動時間,即整個滑動過程完成所需要的時間,注意這里的滑動是指View內(nèi)容的滑動而非View本身位置的改變。可以看到,僅僅調(diào)用startScroll方法是無法讓View滑動的,因為它內(nèi)部并沒有做滑動相關的事,那么Scroller到底是如何讓View彈性滑動的呢?答案就是startScroll方法下面的invalidate方法,雖然有點不可思議,但是的確是這樣的。invalidate方法會導致View重繪,在View的draw方法中又會去調(diào)用computeScroll方法,computeScroll方法在View中是一個空實現(xiàn),因此需要我們自己去實現(xiàn),上面的代碼已經(jīng)實現(xiàn)了computeScroll方法。正是因為這個computeScroll方法,View才能實現(xiàn)彈性滑動。這看起來還是很抽象,其實這樣的:當View重繪后會在draw方法中調(diào)用computeScroll,而computeScroll又會去向Scroller獲取當前的scrollX和scrollY;然后通過scrollTo方法實現(xiàn)滑動;接著又調(diào)用postInvalidate方法來進行第二次重繪,這一次重繪的過程和第一次重繪一樣,還是會導致computeScroll方法被調(diào)用;然后繼續(xù)向Scroller獲取當前的scrollX和scrollY,并通過scrollTo方法滑動到新的位置,如此反復,直到整個滑動過程結(jié)束。

我們再看一下Scroller的computeScrollOffset方法的實現(xiàn),如下所示。

        /**
         * Call this when you want to know the new location.  If it returns true,
         * the animation is not yet finished.
         */
        public boolean computeScrollOffset() {
            ...
            int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() -
            mStartTime);

            if (timePassed < mDuration) {
                switch (mMode) {
                case SCROLL_MODE:
                    final float x = mInterpolator.getInterpolation(timePassed *
                          mDurationReciprocal);
                          mCurrX = mStartX + Math.round(x * mDeltaX);
                          mCurrY = mStartY + Math.round(x * mDeltaY);
                          break;
                      ...
                      }
                  }
                  return true;
              }

是不是突然就明白了?這個方法會根據(jù)時間的流逝來計算出當前的scrollX和scrollY的值。計算方法也很簡單,大意就是根據(jù)時間流逝的百分比來算出scrollX和scrollY改變的百分比并計算出當前的值,這個過程類似于動畫中的插值器的概念,這里我們先不去深究這個具體過程。這個方法的返回值也很重要,它返回true表示滑動還未結(jié)束,false則表示滑動已經(jīng)結(jié)束,因此當這個方法返回true時,我們要繼續(xù)進行View的滑動。

通過上面的分析,我們應該明白Scroller的工作原理了,這里做一下概括:Scroller本身并不能實現(xiàn)View的滑動,它需要配合View的computeScroll方法才能完成彈性滑動的效果,它不斷地讓View重繪,而每一次重繪距滑動起始時間會有一個時間間隔,通過這個時間間隔Scroller就可以得出View當前的滑動位置,知道了滑動位置就可以通過scrollTo方法來完成View的滑動。就這樣,View的每一次重繪都會導致View進行小幅度的滑動,而多次的小幅度滑動就組成了彈性滑動,這就是Scroller的工作機制。由此可見,Scroller的設計思想是多么值得稱贊,整個過程中它對View沒有絲毫的引用,甚至在它內(nèi)部連計時器都沒有。

3.3.2 通過動畫

動畫本身就是一種漸近的過程,因此通過它來實現(xiàn)的滑動天然就具有彈性效果,比如以下代碼可以讓一個View的內(nèi)容在100ms內(nèi)向左移動100像素。

        ObjectAnimator.ofFloat(targetView, "translationX", 0, 100).setDuration
        (100).start();

不過這里想說的并不是這個問題,我們可以利用動畫的特性來實現(xiàn)一些動畫不能實現(xiàn)的效果。還拿scrollTo來說,我們也想模仿Scroller來實現(xiàn)View的彈性滑動,那么利用動畫的特性,我們可以采用如下方式來實現(xiàn):

        final int startX = 0;
        final int deltaX = 100;
        ValueAnimator animator = ValueAnimator.ofInt(0, 1).setDuration(1000);
        animator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                float fraction = animator.getAnimatedFraction();
                mButton1.scrollTo(startX + (int) (deltaX * fraction), 0);
            }
        });
        animator.start();

在上述代碼中,我們的動畫本質(zhì)上沒有作用于任何對象上,它只是在1000ms內(nèi)完成了整個動畫過程。利用這個特性,我們就可以在動畫的每一幀到來時獲取動畫完成的比例,然后再根據(jù)這個比例計算出當前View所要滑動的距離。注意,這里的滑動針對的是View的內(nèi)容而非View本身。可以發(fā)現(xiàn),這個方法的思想其實和Scroller比較類似,都是通過改變一個百分比配合scrollTo方法來完成View的滑動。需要說明一點,采用這種方法除了能夠完成彈性滑動以外,還可以實現(xiàn)其他動畫效果,我們完全可以在onAnimationUpdate方法中加上我們想要的其他操作。

3.3.3 使用延時策略

本節(jié)介紹另外一種實現(xiàn)彈性滑動的方法,那就是延時策略。它的核心思想是通過發(fā)送一系列延時消息從而達到一種漸近式的效果,具體來說可以使用Handler或View的postDelayed方法,也可以使用線程的sleep方法。對于postDelayed方法來說,我們可以通過它來延時發(fā)送一個消息,然后在消息中來進行View的滑動,如果接連不斷地發(fā)送這種延時消息,那么就可以實現(xiàn)彈性滑動的效果。對于sleep方法來說,通過在while循環(huán)中不斷地滑動View和sleep,就可以實現(xiàn)彈性滑動的效果。

下面采用Handler來做個示例,其他方法請讀者自行去嘗試,思想都是類似的。下面的代碼在大約1000ms內(nèi)將View的內(nèi)容向左移動了100像素,代碼比較簡單,就不再詳細介紹了。之所以說大約1000ms,是因為采用這種方式無法精確地定時,原因是系統(tǒng)的消息調(diào)度也是需要時間的,并且所需時間不定。

        private static final int MESSAGE_SCROLL_TO = 1;
        private static final int FRAME_COUNT = 30;
        private static final int DELAYED_TIME = 33;

        private int mCount = 0;

        @SuppressLint("HandlerLeak")
        private Handler mHandler = new Handler() {
            public void handleMessage(Message msg) {
                switch (msg.what) {
                case MESSAGE_SCROLL_TO: {
                    mCount++;
                    if (mCount <= FRAME_COUNT) {
                        float fraction = mCount / (float) FRAME_COUNT;
                        int scrollX = (int) (fraction * 100);
                        mButton1.scrollTo(scrollX, 0);
                        mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,
                        DELAYED_TIME);
                    }
                    break;
                }

                default:
                    break;
                }
            };
        };

上面幾種彈性滑動的實現(xiàn)方法,在介紹中側(cè)重更多的是實現(xiàn)思想,在實際使用中可以對其靈活地進行擴展從而實現(xiàn)更多復雜的效果。

主站蜘蛛池模板: 报价| 河津市| 班玛县| 陵川县| 恭城| 华安县| 厦门市| 奇台县| 罗定市| 陕西省| 满洲里市| 湘乡市| 昂仁县| 汉沽区| 潢川县| 左云县| 肥西县| 金川县| 台南县| 金门县| 石楼县| 永安市| 克东县| 巩义市| 田阳县| 尼玛县| 吴旗县| 琼结县| 鹤峰县| 公主岭市| 白山市| 土默特右旗| 秭归县| 阜阳市| 垦利县| 绥德县| 长沙市| 馆陶县| 旬邑县| 涟水县| 崇信县|