Android 怎么獲取 CollapsingToolbarLayout 的收縮展開狀態(tài)?

CollapsingToolbarLayout出來已經(jīng)很久了,具有很炫酷的效果,相信大家都比較熟悉,這里就不介紹它實現(xiàn)的效果了。在使用過程中,我們可能因為產(chǎn)品需求而獲取這個是收縮還是展開的狀態(tài),在這我為大家介紹一種方式來獲取這個狀態(tài)。


????這個結(jié)構(gòu)是一般使用CollapsingToolbarLayout的層級結(jié)構(gòu),一般和AppBarLayout嵌套使用。在AppBarLayout里面有這樣一個接口:

    /**
     * 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);
    }

從字面意思可以看出,這個是說當(dāng)這個垂直方向發(fā)生偏移的時候調(diào)用的接口,我們就可以通過這個接口暴露出來的verticalOffset,也就是垂直偏移量來判斷當(dāng)前是什么狀態(tài)。
????我們先來寫個Demo測試一下:

demo代碼

????打印信息如下:
進來的時候默認為展開狀態(tài)的打印信息

????進來的時候默認是展開狀態(tài),偏移量為0;我們向上滑動讓它至收縮狀態(tài)將會打印如下信息:
變?yōu)槭湛s狀態(tài)打印信息

可以發(fā)現(xiàn),將由0變?yōu)榱?300;我們在看看由收縮變?yōu)檎归_狀態(tài)的日志信息:

收縮狀態(tài)變?yōu)榱苏归_狀態(tài)日志信息

??從上述可以看見偏移量由-270變?yōu)榱?.由此我們可以根據(jù)這個偏移量得到相應(yīng)的狀態(tài)信息。
??當(dāng)0的時候我們可以判定為展開狀態(tài),那當(dāng)什么時候我們判斷為收縮狀態(tài)呢?那個-300的值是怎么獲取呢?我們可以通過appBarLayout.getTotalScrollRange()這個方法:

/**
     * Returns the scroll range of all children.
     *
     * @return the scroll range in px
     */
    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
                    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());
    }

通過代碼和注釋便可知道這個方法就是獲取這個AppBarLayout子類可以滑動的距離,在Demo的onCreate方法中我們調(diào)用這個方法:

Log.e(TAG, "getTotalScrollRange():" + app_bar.getTotalScrollRange());

打印出來如下:

image.png

為什么會是0呢?理論上應(yīng)該是300啊!然后我把這個方法放在OnOffsetChangedListeneronOffsetChanged方法里面打印:

打印信息如下:

在這個監(jiān)聽里面我們就獲取到了正確的值,那為什么會出現(xiàn)這種情況呢?
通過斷點調(diào)試:
onCreate進入的時候調(diào)用getTotalScrollRange()方法:
onCreate調(diào)用的時候child高度

onCreate調(diào)用的時候child高度lp信息

可以看到childHeight==0,LayoutParms的各個屬性也為0,所以為0。
等到初始化完畢會調(diào)用OnOffsetChangedListener.onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset)方法,這個時候就能夠獲取到有效的值。
為什么進來的時候為0,這個不是本篇文章討論的內(nèi)容,請查閱View初始化相關(guān)資料。
由此,我們可以得到如下結(jié)論:

1.當(dāng)verticalOffset==0的時候我們可以判斷為展開狀態(tài);
2.當(dāng)Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()的時候我們可以判斷為收縮狀態(tài),這里要注意appBarLayout.getTotalScrollRange()要在OnOffsetChangedListener.onOffsetChanged()方法里面調(diào)用以獲取正確的值,防止出現(xiàn)等于0的情況;

下面來實際應(yīng)用一次,我們有個需求,在拉伸的時候可以下拉刷新,但是在收縮的時候不能夠下拉刷新,這個需求應(yīng)該很常見,相信在聽到這個需求的時候很多人應(yīng)該都會想到用事件分發(fā)來處理,但是我們可以這樣做。我們使用android-Ultra-Pull-To-Refresh來做下拉刷新容器,嵌套在CollapsingToolbarLayout里面,然后把behavior放在PtrClassicFrameLayout上,
可以看見代碼如下:
主界面布局:

<?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.zzw.T.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>

R.layout.content_scrolling.xml:

<?xml version="1.0" encoding="utf-8"?>
<in.srain.cube.views.ptr.PtrClassicFrameLayout 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/ptr"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:ptr_resistance="1.7"
    app:ptr_ratio_of_header_height_to_refresh="1.2"
    app:ptr_duration_to_close="300"
    app:ptr_duration_to_close_header="2000"
    app:ptr_keep_header_when_refresh="true"
    app:ptr_pull_to_fresh="false"
    tools:context="com.zzw.T.ScrollingActivity">


    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:showIn="@layout/activity_scrolling">


        <LinearLayout
            android:clickable="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin"
                android:text="EFGJERJOREJROEJGOERJ" />

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="@color/colorPrimary" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin"
                android:text="EFGJERJOREJROEJGOERJ" />

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="@color/colorPrimary" />

        ......
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</in.srain.cube.views.ptr.PtrClassicFrameLayout>

主要看content_scrolling里面的內(nèi)容,我們把觸發(fā)收縮展開的behavior放在PtrClassicFrameLayout 里面,用于它觸發(fā),里面是一個NestedScrollView 實際運用中可能RecyclrView用得多一下。JAVA代碼如下:

  package com.zzw.T;

import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;

import in.srain.cube.views.ptr.PtrClassicFrameLayout;
import in.srain.cube.views.ptr.PtrDefaultHandler;
import in.srain.cube.views.ptr.PtrFrameLayout;
import in.srain.cube.views.ptr.PtrHandler;

public class ScrollingActivity extends AppCompatActivity {

    private static final String TAG = "ScrollingActivity";

    private AppBarLayout app_bar;
    private int verticalOffset;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        app_bar = (AppBarLayout) findViewById(R.id.app_bar);

        final PtrClassicFrameLayout ptrFrameLayout = (PtrClassicFrameLayout) findViewById(R.id.ptr);
        ptrFrameLayout.setPtrHandler(new PtrHandler() {
            @Override
            public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
                return verticalOffset >= 0 && PtrDefaultHandler.checkContentCanBePulledDown(frame, content, header);
            }

            @Override
            public void onRefreshBegin(PtrFrameLayout frame) {
                frame.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        ptrFrameLayout.refreshComplete();
                    }
                }, 2000);
            }
        });

        app_bar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                Log.e(TAG, "verticalOffset:" + verticalOffset);
                Log.e(TAG, "getTotalScrollRange():" + app_bar.getTotalScrollRange());
                ScrollingActivity.this.verticalOffset = verticalOffset;
            }
        });

        Log.e(TAG, "getTotalScrollRange():" + app_bar.getTotalScrollRange());
    }
}

我們通過判斷是否可以執(zhí)行下拉刷新這個操作來讓自己不復(fù)雜的處理事件的分發(fā)機制問題:

verticalOffset >= 0 && PtrDefaultHandler.checkContentCanBePulledDown(frame, content, header);

表示是拉伸狀態(tài)的時候才能刷新,效果圖如下:

當(dāng)然你也可以這樣,只是布局方式不一樣而已:


具體實現(xiàn)什么樣的效果還是要看需求。
這篇文章就到這了,水平有限,有什么意見可以在下方提出來,大家一起討論。謝謝
Demo點此下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容