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測試一下:

????打印信息如下:

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

可以發(fā)現(xiàn),將由0變?yōu)榱?300;我們在看看由收縮變?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());
打印出來如下:

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

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

可以看到
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點此下載