Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android從零開搞系列:自定義View(6)ScrollTo+ScrollBy+Scroller+NestedScrolling機制(上)

Android從零開搞系列:自定義View(6)ScrollTo+ScrollBy+Scroller+NestedScrolling機制(上)

編輯:關於Android編程

本菜開源的一個自己寫的Demo,希望能給Androider們有所幫助,水平有限,見諒見諒…
https://github.com/zhiaixinyang/MDoveApp/

實現這種效果有很多種方案,今天在這就主要借這個機會來記錄一下scrollTo,scrollBy以及Scroller的用法。

讓我們先了解一發


關於scrollTo方法:

先看一下scrollTo()的源碼解釋:

    /**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    //翻譯過來:設置視圖的滾動位置。 這將導致調用{@link #onScrollChanged(int,int,int,int)},並且視圖將失效。

說實話不是很好理解。但是根據效果我們可以這麼理解:scrollTo的效果是移向你傳進的坐標。如果你再次調用這個方法,傳值不變時!你會發現並沒有任何的位置移動。(和scrollBy方法有區別)因為它已經到達了這個位置因此不會再移動,而scrollBy卻不然。

此外此方法移動的是View內部的位置而不是View整體。如果View不是一個ViewGroup的話,例如TextView,那麼移動的就是TextView的文字內容;如果是ViewGroup那麼便是ViewGroup中的子元素。


關於scrollBy方法:

看一下scrollBy的源碼:

    //注釋基本和scrollTo相同
    /**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

我們可以看出,scrollBy內部是調用的scrollTo,但是這裡的傳參導致了它們二者的不同。

scrollBy傳入了mScrollX…這是個什麼東西?我們在View中可以調用getScrollX()方法拿到這個值;其實這個值就是滑動的距離,對於X軸來說左滑動為正(增加),對於Y軸上滑動為正(增加)。

scrollBy的效果與scrollTo也截然相反。scrollBy就基於當前位置的移動,簡單說它可以不斷的移動。

只用文字去敘述有點單調,接下來上高清無碼gif:

這裡我分別對倆這進行了多次點擊,但是 傳的值是固定的

這裡寫圖片描述

!!!!這裡有個需要注意的地方!!!!

在傳值的時候,我們需要注意正負號問題:簡單來說往左滑動時x為正,否則為負;上滑動時y為正,反之為負。

因為這裡和mScrollX和mScrollY這倆個變量有關。這兩個方法最終都會直接或間接的引用到這倆個變量。而它們倆的正負是這麼判斷的:如果View的左邊緣在View內容區域(這倆個方法的移動都只是移動自己的內容區域)左邊緣的右邊為正,反之為負;如果View的上邊緣在View內容區域的上邊緣的下邊mScrollY為正反之為負。

為什麼是這樣:當我們把內容區域的某個邊緣當做參考點來理解就是這種情況。如果View的內容區域的左邊緣為(0,…)那麼View的左邊緣在它的右邊,理所應當為(+…,…)。同理mScrollY也是如此。


第三個我們來看一下Scroller

它其實就是一個輔助類:

    //簡單重寫了onTouchEvent
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                int x=-5;
                //在0.2內使View的x軸從0移到x
                //但是具體怎麼移動需要我們自己去實現,解釋在下面
                scroller.startScroll(0,0,x,0,200);
                invalidate();
                break;
        }
        return true;
    }
    /**
     * invalidate()最終會調用computeScroll()
     * 而View中的computeScroll()是一個空實現
     * 因此需要我們自己去重寫這個方法,去實現對應的效果
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        //如果返回true,說明滑動還不到結束的時候,應當繼續。
        if (scroller.computeScrollOffset()){
            //而促使它滑動的方式依舊是scrollBy或是scrollTo
            //注意此處x的位置,我們是使用的scroller.getCurrX()的返回值,至於它的作用往下來
            scrollBy(scroller.getCurrX(),0);
            //請求重新繪制View
            invalidate();
        }
    }

Scroller這個類本身不具備任何移動View的作用。它的startScroll方法,我們進源碼就會發現僅僅是一些簡單的賦值。真正起到移動效果的是invalidate()方法,通過這個方法使得View重繪,因此就會調用computeScroll(),所以我們重寫computeScroll()。

可能有朋友在這裡會由衷的贊歎一句:尼瑪SB嗎?饒了這麼一大圈不還是scrollTo/scrollBy麼!

不不不,它有一個最重要的作用。先讓我們注意一下我們在傳參的時候,傳了一個時間參數(200)。
這個值在computeScrollOffset()中體現它的作用:

        //根據代碼中的變量名,我們也能猜出:這裡通過時間的流逝來計算移動進行的比例
        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
        //如果時間流逝小於應該傳入的值,那麼就繼續執行
        if (timePassed < mDuration) {
            switch (mMode) {
            //如果是滾動狀態
            case SCROLL_MODE:
                //通過類插值器的效果來計算x的變化量,使其隨時間進行均勻變化。
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
         //省略部分代碼
         }

這裡用於計算移動的比例,因此Scroller最大的效果就是移動可以隨時間的變化而變化,簡單說可以做一些彈性的效果。讓滑動不在單點生硬。


其實理解這些方法的使用,最開始的那個效果真的很簡單,所以接下來就是簡單貼一下代碼:

相關代碼

onTouchEvent相關:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!scroller.isFinished())
                    scroller.abortAnimation();
                //記錄手指按下時的坐標
                lastY = y;
                downY=event.getY();
                //消費點擊事件
                return true;
            case MotionEvent.ACTION_MOVE:
                float dy = y - lastY;
                scrollBy(0, (int) -dy);
                lastY = y;
                break;
        }
        return super.onTouchEvent(event);
    }

scrollTo相關:

    //topViewHeight就是我們需要滑動的View的高度
    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > topViewHeight) {
            y = topViewHeight;
        }
        if (y != getScrollY()) {
            super.scrollTo(x, y);
        }
    }

onFinishInflate相關:

    //此方法在布局加載完成後回調,因此在此獲得View的引用
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        topView=getChildAt(0);
    }

onSizeChanged相關:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //獲取topView的高度
        topViewHeight = topView.getMeasuredHeight();
        //畫三角形所需的路線
        path=new Path();
        path.moveTo(avatarLeft-25,topViewHeight);
        path.lineTo(avatarLeft+25,topViewHeight);
        path.lineTo(avatarLeft,topViewHeight-25);
        path.close();
    }
    //畫三角形
    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.drawPath(path,paint);
        super.dispatchDraw(canvas);
    }

OK,到此關於scrollTo和scrollBy以及Scroller的用法就結束了,接下來就是關於NestedScrolling機制的分析,讓我們下一次博客見。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved