CollapsingToolbarLayout
出来已经很久了,具有很炫酷的效果,相信大家都比较熟悉,这里就不介绍它实现的效果了。在使用过程中,我们可能因为产品需求而获取这个是收缩还是展开的状态,在这我为大家介绍一种方式来获取这个状态。
这个结构是一般使用
CollapsingToolbarLayout
的层级结构,一般和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);
}
从字面意思可以看出,这个是说当这个垂直方向发生偏移的时候调用的接口,我们就可以通过这个接口暴露出来的verticalOffset
,也就是垂直偏移量来判断当前是什么状态。
我们先来写个Demo测试一下:
打印信息如下:
进来的时候默认是展开状态,偏移量为0;我们向上滑动让它至收缩状态将会打印如下信息:
可以发现,将由0变为了-300;我们在看看由收缩变为展开状态的日志信息:
从上述可以看见偏移量由-270变为了0.由此我们可以根据这个偏移量得到相应的状态信息。
当0的时候我们可以判定为展开状态,那当什么时候我们判断为收缩状态呢?那个-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
方法中我们调用这个方法:
Log.e(TAG, "getTotalScrollRange():" + app_bar.getTotalScrollRange());
打印出来如下:
为什么会是0呢?理论上应该是300啊!然后我把这个方法放在
OnOffsetChangedListener
的onOffsetChanged
方法里面打印:
打印信息如下:
在这个监听里面我们就获取到了正确的值,那为什么会出现这种情况呢?
通过断点调试:
onCreate
进入的时候调用getTotalScrollRange()
方法:
可以看到
childHeight==0
,LayoutParms
的各个属性也为0,所以为0。
等到初始化完毕会调用
OnOffsetChangedListener.onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset)
方法,这个时候就能够获取到有效的值。
为什么进来的时候为0,这个不是本篇文章讨论的内容,请查阅View初始化相关资料。
由此,我们可以得到如下结论:
1.当
verticalOffset==0
的时候我们可以判断为展开状态;
2.当Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()
的时候我们可以判断为收缩状态,这里要注意appBarLayout.getTotalScrollRange()
要在OnOffsetChangedListener.onOffsetChanged()
方法里面调用以获取正确的值,防止出现等于0的情况;
下面来实际应用一次,我们有个需求,在拉伸的时候可以下拉刷新,但是在收缩的时候不能够下拉刷新,这个需求应该很常见,相信在听到这个需求的时候很多人应该都会想到用事件分发来处理,但是我们可以这样做。我们使用android-Ultra-Pull-To-Refresh来做下拉刷新容器,嵌套在CollapsingToolbarLayout
里面,然后把behavior
放在PtrClassicFrameLayout
上,
可以看见代码如下:
主界面布局:
R.layout.content_scrolling.xml
:
......
主要看content_scrolling
里面的内容,我们把触发收缩展开的behavior
放在PtrClassicFrameLayout
里面,用于它触发,里面是一个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());
}
}
我们通过判断是否可以执行下拉刷新这个操作来让自己不复杂的处理事件的分发机制问题:
verticalOffset >= 0 && PtrDefaultHandler.checkContentCanBePulledDown(frame, content, header);
表示是拉伸状态的时候才能刷新,效果图如下:
当然你也可以这样,只是布局方式不一样而已:
具体实现什么样的效果还是要看需求。
这篇文章就到这了,水平有限,有什么意见可以在下方提出来,大家一起讨论。谢谢
Demo点此下载