使用AppBarLayout+CollapsingToolbarLayout實現自定義工具欄摺疊效果

最近在公司閒着沒事,下了幾款app看下有沒有好看的效果能不能動手實踐下,剛好就看到這個工具欄摺疊效果.

這一看不就是跟Material Design工具欄摺疊效果類似。我們捋一下效果是怎樣的,滑動的時候實現搜索欄漸變以及高度改變的工具欄摺疊效果。

知道大概效果,開始擼代碼。

首先我們先熟悉下Material Design摺疊欄的效果是怎樣的。

準備工作

相關控件瞭解

在創建activity的時候,android studio提供了一個叫ScrollingActivity的模版

點擊創建後之後出現一個有工具欄摺疊效果的activity。

ScrollActivity的佈局代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.m520it.test.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_scrolling" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/fab_margin"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

AppBarLayout是一種支持響應滾動手勢的app bar佈局(比如工具欄滾出或滾入屏幕),CollapsingToolbarLayout則是專門用來實現子佈局內不同元素響應滾動細節的佈局。

與AppBarLayout組合的滾動佈局(Recyclerview、NestedScrollView等)需要設置app:layout_behavior這個屬性

沒有設置的話,AppBarLayout將不會響應滾動佈局的滾動事件。

CollapsingToolbarLayout和ScrollView一起使用會有滑動bug,注意要使用NestedScrollView來替代ScrollView。

AppBarLayout的子佈局有5種滾動標識(就是上面代碼CollapsingToolbarLayout中配置的app:layout_scrollFlags屬性):

  • scroll:將此佈局和滾動時間關聯。這個標識要設置在其他標識之前,沒有這個標識則佈局不會滾動且其他標識設置無效。
  • enterAlways:任何向下滾動操作都會使此佈局可見。這個標識通常被稱爲“快速返回”模式。
  • enterAlwaysCollapsed:假設你定義了一個最小高度(minHeight)同時enterAlways也定義了,那麼view將在到達這個最小高度的時候開始顯示,並且從這個時候開始慢慢展開,當滾動到頂部的時候展開完。
  • exitUntilCollapsed:當你定義了一個minHeight,此佈局將在滾動到達這個最小高度的時候摺疊。
  • snap:當一個滾動事件結束,如果視圖是部分可見的,那麼它將被滾動到收縮或展開。例如,如果視圖只有底部25%顯示,它將摺疊。相反,如果它的底部75%可見,那麼它將完全展開。

CollapsingToolbarLayout可以通過app:contentScrim設置摺疊時工具欄佈局的顏色,通過app:statusBarScrim設置摺疊時狀態欄的顏色。默認contentScrim是colorPrimary的色值,statusBarScrim是colorPrimaryDark的色值。

CollapsingToolbarLayout的子佈局有3種摺疊模式(Toolbar中設置的app:layout_collapseMode)

  • off:這個是默認屬性,佈局將正常顯示,沒有摺疊的行爲。
  • pin:CollapsingToolbarLayout摺疊後,此佈局將固定在頂部。
  • parallax:CollapsingToolbarLayout摺疊時,此佈局也會有視差摺疊效果。

當CollapsingToolbarLayout的子佈局設置了parallax模式時,我們還可以通過app:layout_collapseParallaxMultiplier設置視差滾動因子,值爲:0~1。

FloatingActionButton這個控件通過app:layout_anchor這個設置錨定在了AppBarLayout下方。FloatingActionButton源碼中有一個Behavior方法,當AppBarLayout收縮時,FloatingActionButton就會跟着做出相應變化。

瞭解這麼多 大家可以新建個ScrollActivity模版去玩下這些屬性

接下來還有一個問題就是————工具欄的摺疊以及展開的狀態如何監聽?

從上面知道CollapsingToolbarLayout 是負責摺疊工具欄的佈局,AppBarLayout是負責素響應滾動細節的佈局。
那麼它們是如何實現聯動的?

查看AppBarLayout的源碼可以看到有這樣的一個接口OnOffsetChangedListerner

這個接口就是監聽當佈局AppBarLayout 出現滑動時響應的事件。

而CollapsingToolbarLayout 持有這個OnOffsetChangedListerner監聽對象

這樣我們可以看出當AppBarLayout出現滑動時,CollapsingToolbarLayou通過OnOffsetChangedListerner這個對象去響應AppBarLayout的滑動,做出對應工具欄的狀態

使用OnOffsetChangedListerner

代碼如下:

其中裏面offsetChanged 裏第二個參數verticalOffset含義指的是垂直滑動的距離
當滑動時我們打印看下verticalOffset的值

向上滑動得到的值是負的,初始值爲0 就是展開狀態。

這裏我們需要注意的是verticalOffset能滑動最遠距離爲

AppBarLayout的高度 減去 CollapsingToolbarLayout摺疊時的高度(這裏AppBarLayout可以通過layout_scrollFlags控制CollapsingToolbarLayout設置顯示或者隱藏狀態)

注意:在AppBarLayout 中設置android:fitsSystemWindows=”true”這個屬性回影響verticalOffset最終的值,會加上狀態欄的高度

至此,終於熟悉摺疊工具欄的效果時如何實現的,接下來就可以根據上面的原理去實現自定義的工具欄。

實現自定義摺疊工具欄

首先看下我根據上面的原理實現的效果:

效果分析

當我們滑動時

  • 搜索框背景出現透明的漸變
  • 高度逐漸變小至到跟toolbar工具欄高度一致
  • 搜索框逐漸往上移動到最頂點。

思路分析

如何給搜索框背景出現透明漸變?

我們知道view有個設置透明值的方法 setAlpha(). 參數爲 0f到1.0f。
滑動時這個值如何確定,在上面我們提到過AppBarLayout 裏有OnOffsetChangedListerner這個滑動監聽。

通過這個方法獲取到 滑動距離verticalOffset 除以 能滑動的總距離 得出 漸變值。

如何給搜索框設置高度漸變

1.首先確定搜索框的高度漸變成工具欄toolbar 漸變百分比差值。

llHeightOffScale = 1.0f - (toolBarHeight / llHeight)

2.toolBarHeight 指的是工具欄toolBar的高度 llHeight是指搜索框佈局的高度。

每次滑動時得到的漸變差值爲:

滑動漸變差值/llHeightOffScale = 滑動距離(verticalOffset)/ 能滑動總距離

求出滑動時搜索框對應高度縮放指

float llHeightScale = 1.0f-(llHeightOffScale*((-verticalOffset)/offSetHeight));

3.得出高度

params.height = (int)(llHeight* llHeightScale);

如何設置搜索框佈局位移漸變

這裏我是通過滑動時設置搜索框佈局的margin來改變(也可以通過調用view的setTranslationY來變化Y軸距離)

1.通過LayoutParams獲取topMargin值

//得到滑動差值 就是佈局marginTop
llOffDistance = params.topMargin;

2.計算出每次滑動時marginTop的值,並設置

搜索框佈局滑動時的距離/總的滑動差值llOffDistance = 滑動距離/能滑動總距離

根據這個計算出滑動時的marginTop值

float distance = llOffDistance - (-verticalOffset)*llOffDistance / offSetHeight;

3.設置給搜索框佈局

params.setMargins(0,(int)distance,0,0);
//重新佈局子控件
fl.requestLayout();

下面給出佈局代碼以及Activity代碼:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fl"
    android:background="#fff"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.m520it.myapplication.TestScrollActivity">

        <android.support.design.widget.AppBarLayout
            android:id="@+id/app_bar"
            android:layout_width="match_parent"
            android:layout_height="@dimen/app_bar_height"
            android:theme="@style/AppTheme.AppBarOverlay">

            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/toolbar_layout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:statusBarScrim="@android:color/transparent"
                app:contentScrim="@android:color/transparent"
                app:layout_scrollFlags="scroll|snap|exitUntilCollapsed">

                <ImageView
                    android:id="@+id/image"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    app:layout_collapseMode="none"
                    android:src="@mipmap/test"
                    android:scaleType="centerCrop"/>
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/tool_bar_height"
                    app:layout_collapseMode="none"
                    app:contentInsetStart="0dp"
                    app:popupTheme="@style/AppTheme.PopupOverlay" />

            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>

        <include layout="@layout/content_test_scroll" />



    </android.support.design.widget.CoordinatorLayout>
    <!--搜索框佈局 -->
    <RelativeLayout
        android:id="@+id/ll"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/app_bar"
        android:layout_marginTop="150dp"
        app:layout_anchorGravity="bottom">
        <TextView
            android:id="@+id/bac"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:background="@color/colorPrimary"/>
        <EditText
            android:id="@+id/fab"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_margin="@dimen/fab_margin"
            android:focusable="false"
            android:background="@drawable/et_bac"
            />
    </RelativeLayout>

</FrameLayout>

Activity代碼

public class TestScrollActivity extends AppCompatActivity {

    private float totalHeight;      //總高度
    private float toolBarHeight;    //toolBar高度
    private float offSetHeight;     //總高度 -  toolBar高度  佈局位移值
    private float llHeight;         //搜索框高度

    private float llHeightOffScale;     //高度差比值
    private float llOffDistance;        //距離差
    private float llOffDistanceScale;   //距離差比值
    private FrameLayout.LayoutParams params;

    @BindView(R.id.toolbar)
    Toolbar toolbar;
    @BindView(R.id.toolbar_layout)
    CollapsingToolbarLayout toolbarLayout;
    @BindView(R.id.app_bar)
    AppBarLayout appBar;
    @BindView(R.id.fab)
    EditText fab;
    @BindView(R.id.ll)
    RelativeLayout ll;
    @BindView(R.id.bac)
    TextView bac;
    @BindView(R.id.fl)
    FrameLayout fl;
    @BindView(R.id.image)
    ImageView image;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling2);
        ButterKnife.bind(this);

        bac.setAlpha(0f);
        totalHeight = getResources().getDimension(R.dimen.app_bar_height);
        toolBarHeight = getResources().getDimension(R.dimen.tool_bar_height);
        offSetHeight = totalHeight - toolBarHeight;

        /**
         *   移動效果值/最終效果值 =  移動距離/ 能移動總距離(確定)
         */
        appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {

                //第一次進入獲取高度,以及差值 高度差比值
                if (llHeight == 0){
                    llHeight = ll.getMeasuredHeight();
                    params = (FrameLayout.LayoutParams) ll.getLayoutParams();

                    //算出高度偏移量比值  相對與llHeight
                    llHeightOffScale = 1.0f - (toolBarHeight / llHeight);

                    //得到滑動差值 就是佈局marginTop
                    llOffDistance = params.topMargin;
                    //得到滑動比值
                    llOffDistanceScale = llOffDistance / offSetHeight;
                }

                //滑動一次 得到漸變縮放值
                float alphaScale = (-verticalOffset) / offSetHeight;

                //獲取高度縮放值
                float llHeightScale = 1.0f-(llHeightOffScale*((-verticalOffset)/offSetHeight));
                //計算maigintop值
                float distance = llOffDistance - (-verticalOffset)*llOffDistanceScale;

                image.setAlpha(1.0f-alphaScale);
                bac.setAlpha(alphaScale);
                params.height = (int)(llHeight* llHeightScale);
                params.setMargins(0,(int)distance,0,0);

                fl.requestLayout();


            }
        });
    }
}

感興趣的同學也可以根據AppBarLayout+CollapsingToolbarLayout再繼續寫出其它效果!

比如這樣的:

最後附上demo地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章