Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 8CollapsingToolbarLayout源碼分析

8CollapsingToolbarLayout源碼分析

編輯:關於Android編程

純色Toolbar滑動

最簡單代碼

先從最簡單的看起

   

        

            

        

    

效果如下所示,toolbar可以伸展

\

AppBarLayout裡有個接口,叫做OnOffsetChangedListener,如果AppBarLayout滑動了就會觸發裡面的回調onOffsetChanged

    /**
     * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical
     * offset changes.
     */
    public interface OnOffsetChangedListener {
        /**
         * Called when the {@link AppBarLayout}'s layout offset has been changed. This allows
         * child views to implement custom behavior based on the offset (for instance pinning a
         * view at a certain y value).
         *
         * @param appBarLayout the {@link AppBarLayout} which offset has changed
         * @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px
         */
        void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset);
    }

AppBarLayout滑動的時候會調用setHeaderTopBottomOffset,裡面調用dispatchOffsetUpdates(appBarLayout),如下所示,會把移動的消息發給listeners

      private void dispatchOffsetUpdates(AppBarLayout layout) {
            final List listeners = layout.mListeners;

            // Iterate backwards through the list so that most recently added listeners
            // get the first chance to decide
            for (int i = 0, z = listeners.size(); i < z; i++) {
                final OnOffsetChangedListener listener = listeners.get(i);
                if (listener != null) {
                    listener.onOffsetChanged(layout, getTopAndBottomOffset());
                }
            }
        }

而CollapsingToolbarLayout在onAttachedToWindow的時候加入
((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
其實就是注冊了一個listener,AppBarLayout滑動了,CollapsingToolbarLayout 內的mOnOffsetChangedListener就會知道並作出相應動畫,這裡其實就是文字的縮小。主要代碼在CollapsingTextHelper內,主要就是根據當前AppBarLayout的offset來修改mScale。

此時CollapsingToolbarLayout和AppBarLayout一樣大小,包含statusbar 大小為256dp
mTotalScrollRange=range - getTopInset()=256dp-S=609
mDownPreScrollRange 0
mDownScrollRange =256dp=672
我曾經以為mTotalScrollRange= mDownPreScrollRange+ mDownScrollRange,這裡不成立了。
我試著把mDownScrollRange強行改為609,滑動依然正常,因為下滑的時候offset是在變大的,所以不會到-672.

exitUntilCollapsed

再看設置了exitUntilCollapsed 之後,exitUntilCollapsed意思就是滑出直到折疊狀態,即滑出的時候最多到折疊狀態,無法完全滑出

\

exitUntilCollapsed會改變上滑的范圍,上滑的范圍就是mTotalScrollRange,

    private int getUpNestedPreScrollRange() {
        return getTotalScrollRange();
    }
   public final int getTotalScrollRange() {
        if (mTotalScrollRange != INVALID_SCROLL_RANGE) {
            return mTotalScrollRange;
        }

        int range = 0;
        for (int i = 0, z = getChildCount(); i < z; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final int childHeight = child.getMeasuredHeight();
            final int flags = lp.mScrollFlags;

            if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
                // We're set to scroll so add the child's height
                range += childHeight + lp.topMargin + lp.bottomMargin;

                if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                    // For a collapsing scroll, we to take the collapsed height into account.
                    // We also break straight away since later views can't scroll beneath
                    // us
                    //減去標記了SCROLL_FLAG_EXIT_UNTIL_COLLAPSED的child的最小高度
                    range -= ViewCompat.getMinimumHeight(child);
                    break;
                }
            } else {
                // As soon as a view doesn't have the scroll flag, we end the range calculation.
                // This is because views below can not scroll under a fixed view.
                break;
            }
        }
        return mTotalScrollRange = Math.max(0, range - getTopInset());
    }

由上可知,在算mTotalScrollRange的時候會減去標記了SCROLL_FLAG_EXIT_UNTIL_COLLAPSED的child的最小高度,這裡就是減去CollapsingToolbarLayout的minHeight,但是又有個問題,CollapsingToolbarLayout我們並沒有設置minHeight,我們只是在Toolbar裡設置了minHeight。CollapsingToolbarLayout在onLayout的時候會調用setMinimumHeight(getHeightWithMargins(mToolbar));,這樣CollapsingToolbarLayout就有了minHeight,這個值是toolbar的height加上下margin,跟Toolbar的minHeight沒關系。試試看把Toolbar的minHeight去掉,毫不影響。所以此時mTotalScrollRange會減去CollapsingToolbarLayout的minHeight,這樣上滑的時候就會留出一部分高度,不全部滑出,留出的高度就是CollapsingToolbarLayout的minHeight=toolbar高度+上下margin

定住toolbar

Toolbar設置app:layout_collapseMode=”pin”

\

這居然可以定住toolbar,和appbarlayout的設計又有點不符合,appbarlayout是認為底部可以存在不滑動的區域,但頂部不可以,那這裡怎麼做到的,實際上,他是隨著appbarlayout往上offset了,然後他自己之後又offset了一次,使得toolbar相對屏幕的位置不變。實際上,假設appbarlayout往上滑了11,那麼appbarlayout的offset是-11,此時我們又offset了一次,把toolbar相對CollapsingToolbarLayout的offset設置為11,這樣toolbar相對屏幕就相當於沒變化,核心代碼在android.support.design.widget.CollapsingToolbarLayout.OffsetUpdateListener#onOffsetChanged<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;"> //CollapsingToolbarLayout.OffsetUpdateListener#onOffsetChanged for (int i = 0, z = getChildCount(); i < z; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child); switch (lp.mCollapseMode) { case LayoutParams.COLLAPSE_MODE_PIN: //調整offset使得child看起來不動 if (getHeight() - insetTop + verticalOffset >= child.getHeight()) { offsetHelper.setTopAndBottomOffset(-verticalOffset); } break; case LayoutParams.COLLAPSE_MODE_PARALLAX: //調整offset實現視差滑動 offsetHelper.setTopAndBottomOffset( Math.round(-verticalOffset * lp.mParallaxMult)); break; } }

帶背景圖toolbar

對應case1

上滑的過程中,背景由圖片變成純色,狀態欄也由透明變為純色,這個變化是什麼時候呢?這個臨界點由getScrimTriggerOffset決定

        //CollapsingToolbarLayout.OffsetUpdateListener#onOffsetChanged
      // Show or hide the scrims if needed
            if (mContentScrim != null || mStatusBarScrim != null) {
                setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop);
            }

    /**
     * The additional offset used to define when to trigger the scrim visibility change.
     */
    final int getScrimTriggerOffset() {
        return 2 * ViewCompat.getMinimumHeight(this);
    }

截了個圖,大概是這個位置,圖片可見部分的高度就是getScrimTriggerOffset的值,下一瞬間圖片就會變成純色。實際上就是在上面蓋了個mContentScrim,mContentScrim就是一個ColorDrawable ,顏色為colorPrimary.由此可見修改CollapsingToolbarLayout的minHeight就可以修改變化瞬間的位置

\

變成純色的同時,狀態欄也從透明變為有顏色colorPrimaryDark。mScrimAlpha由1變為255,狀態欄變為純色,實際上是在狀態欄的位置畫了一個純色的矩形,由mStatusBarScrim來實現,mStatusBarScrim的顏色也可以指定。

       if (mStatusBarScrim != null && mScrimAlpha > 0) {
            final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
            if (topInset > 0) {
                mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(),
                        topInset - mCurrentOffset);
                mStatusBarScrim.mutate().setAlpha(mScrimAlpha);
                mStatusBarScrim.draw(canvas);
            }
        }

初始態覆蓋狀態欄

對應case3
給ImageView加上fitSystemWindow,為什麼就有效果,讓初始態覆蓋狀態欄
不加的話,ImageView會被設置一個offset(insetTop),讓他處於狀態欄下邊,如果加了,那就進不到L7,所以可以覆蓋狀態欄。

//android.support.design.widget.CollapsingToolbarLayout#onLayout
         if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) {
                final int insetTop = mLastInsets.getSystemWindowInsetTop();
                if (child.getTop() < insetTop) {
                    // If the child isn't set to fit system windows but is drawing within the inset
                    // offset it down
                    ViewCompat.offsetTopAndBottom(child, insetTop);
                }
            }

enterAlwaysCollapsed

再來看看enterAlwaysCollapsed有什麼用
我拿CollapsImageActivity3試了一下,app:layout_scrollFlags=”scroll|enterAlways|enterAlwaysCollapsed” 發現有bug,下滑pre的時候顯示如下,應該是下滑的范圍(mDownPreScrollRange)少算了個statubar。暫時沒有什麼好的解決方案,看google後期會不會修復這個bug還是放棄enterAlwaysCollapsed。

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