转自:
CollapsingToolbarLayout的使用
2020.7.8记录:
项目更新为androidX了,里面的包名相应的也要替换,否则会找不到包。
android.support.design.widget.CoordinatorLayout -> androidx.coordinatorlayout.widget.CustomCoordinatorLayout
android.support.design.widget.AppBarLayout -> com.google.android.material.appbar.AppBarLayout
android.support.design.widget.CollapsingToolbarLayout -> com.google.android.material.appbar.CollapsingToolbarLayout
android.support.v7.widget.Toolbar -> androidx.core.widget.Toolbar
android.support.design.widget.TabLayout -> com.google.android.material.appbar.TabLayout
android.support.v4.widget.NestedScrollView -> androidx.core.widget.NestedScrollView
注意:使用前需要添加Design依赖包,使用toolbar时需要隐藏标题头
CollapsingToolbarLayout作用是提供了一个可以折叠的Toolbar,它继承至FrameLayout,给它设置layout_scrollFlags,它可以控制包含在CollapsingToolbarLayout中的控件(如:ImageView、Toolbar)在响应layout_behavior事件时作出相应的scrollFlags滚动事件(移除屏幕或固定在屏幕顶端)。
NestedScrollView:它是support-v4包提供的控件,继承至FrameLayout, 并实现了NestedScrollingParent,NestedScrollingChild, ScrollingView接口.
它的作用类似于Android.widget.ScrollView,不同点在于NestedScrollView支持嵌套滑动.
需实现的布局:
最外层是CoorinatorLayout,然后里面包含了AppBarlayout和NestedScrollView
AppBarLayout里包含了CollspsingToolbar和Tableyout,它的作用是可以将所有的子控件都当成一个整体
CollapsingToolbarLayout里面则包含了一个ImageView和ToolBar作为伸缩的区域。
1. AppBarLayout的子布局有5种滚动标识(layout_scrollFlags属性):
- scroll:将此布局和滚动时间关联。这个标识要设置在其他标识之前,没有这个标识则布局不会滚动且其他标识设置无效。
- enterAlways:任何向下滚动操作都会使此布局可见。这个标识通常被称为“快速返回”模式。
- enterAlwaysCollapsed:假设你定义了一个最小高度(minHeight)同时enterAlways也定义了,那么view将在到达这个最小高度的时候开始显示,并且从这个时候开始慢慢展开,当滚动到顶部的时候展开完。
- exitUntilCollapsed:当你定义了一个minHeight,此布局将在滚动到达这个最小高度的时候折叠。
- snap:当一个滚动事件结束,如果视图是部分可见的,那么它将被滚动到收缩或展开。例如,如果视图只有底部25%显示,它将折叠。相反,如果它的底部75%可见,那么它将完全展开。
实践证明,scroll和enterAlwaysCollapsed,scroll和exitUntilCollapsed使用时效果无阴影,scroll和enterAlways配合使用时,效果有阴影。
2.CollapsingToolbarLayout中通过layout_collapseMode属性来指定其内部的子控件是折叠还是固定在屏幕上方。
<!-- layout_collapseMode(折叠模式)-有两个值: 1.parallax:在内容滚动时,CollapsingToolbarLayout中的View(比如ImageView)也可以同时滚动, 实现视差滚动效果,通常和layout_collapseParallaxMultiplier(设置视差因子)搭配使用。如果不想实现联动效果,可以设置为0,效果就和scrollview一样了 2.pin - 当CollapsingToolbarLayout完全收缩后,Toolbar还可以固定在屏幕上。 -->
xml文件:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" tools:context="fanggao.qf.collapsingtoolbarlayout.MainActivity"> <!-- android:fitsSystemWindow = "true" 表示整个布局展示是整个屏幕出去状态栏,标题栏和导航栏剩下的区域--> <android.support.design.widget.AppBarLayout android:id="@+id/layout_appbar" android:layout_width="match_parent" android:layout_height = "wrap_content" > <!-- app:expandedTitleMarginStart="10dp" 设置扩张时候(还没有收缩时)title离屏幕左边的距离 app:contentScrim="?attr/colorPrimary" 设置当完全CollapsingToolbarLayout折叠(收缩)后的背景颜色 --> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/ctb" android:layout_width="match_parent" android:layout_height="250dp" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:expandedTitleMarginStart="10dp" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" app:layout_collapseMode="parallax" android:src = "@drawable/cat" /> <!--标题--> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="30dp" app:layout_collapseMode="pin" app:title="Toolbar"/> </android.support.design.widget.CollapsingToolbarLayout> <!--选项卡--> <android.support.design.widget.TabLayout android:id="@+id/tabLayout" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabIndicatorColor="@color/colorAccent" app:tabMode="scrollable" app:tabSelectedTextColor="@color/colorAccent" app:tabTextColor="@android:color/black"/> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:id="@+id/nestedScrollView" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="wrap_content"/> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
主程序:
public class MainActivity extends AppCompatActivity { private Toolbar toolbar; private ImageView image; private ViewPager viewpager; private TabLayout tabLayout; private CollapsingToolbarLayout collapsingToolbarLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initData() { toolbar.setLogo(R.mipmap.ic_launcher); setSupportActionBar(toolbar); //设置返回按钮
getSupportActionBar().setDisplayHomeAsUpEnabled(true); //设置收缩展开toolbar字体颜色
collapsingToolbarLayout.setExpandedTitleColor(Color.WHITE); collapsingToolbarLayout.setCollapsedTitleTextColor(Color.BLACK); //设置tablayout与viewPager
viewpager.setAdapter(new TestViewPageAdapter()); tabLayout.setupWithViewPager(viewpager); } private void initView() { toolbar = (Toolbar) findViewById(R.id.toolbar); image = (ImageView) findViewById(R.id.image); viewpager = (ViewPager) findViewById(R.id.viewPager); tabLayout = (TabLayout) findViewById(R.id.tabLayout); collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.ctb); } class TestViewPageAdapter extends PagerAdapter{ @Override public Object instantiateItem(ViewGroup container, int position) { TextView textView = new TextView(MainActivity.this); textView.setGravity(Gravity.CENTER); textView.setText("pager "+(position+1)); textView.setTextSize(30); textView.setTextColor(Color.BLUE); container.addView(textView); return textView; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View)object); } @Override public int getCount() { return 5; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } /*获得标题*/
/*该方法必须写,不然tablayout不能显示标题*/ @Override public CharSequence getPageTitle(int position) { return "TAB"+(position+1); } } }
效果:
滑动前
向下滑动后:
如果有viewpager嵌套fragment的场景,可以在fragment的根布局中加上NestedScrollView,这样就不会产生滑动冲突事件。如果使用的是ScrollView,会导致上下2个分离,CollapsingToolbarLayout未滑动时,scrollview也可以滑动(不支持嵌套滑动),这样就跟实际需求背离了。
NestedScrollView参考资料:
https://www.jianshu.com/p/f55abc60a879
add:
如何监听CollapsingToolbarLayout的展开与折叠?
这里我们使用官方提供的AppBarLayout.OnOffsetChangedListener就可以实现了,不过封装下效果更好。代码如下。

import com.google.android.material.appbar.AppBarLayout abstract class AppBarStateChangeListener : AppBarLayout.OnOffsetChangedListener { private var mCurrentState = State.IDLE enum class State { EXPANDED, COLLAPSED, IDLE } override fun onOffsetChanged(appBarLayout: AppBarLayout?, i: Int) { appBarLayout?.let { if (i == 0) { if (mCurrentState != State.EXPANDED) { onStateChanged(it, State.EXPANDED) } mCurrentState = State.EXPANDED } else if (Math.abs(i) >= it.totalScrollRange) { if (mCurrentState != State.COLLAPSED) { onStateChanged(it, State.COLLAPSED) } mCurrentState = State.COLLAPSED } else { if (mCurrentState != State.IDLE) { onStateChanged(it, State.IDLE) } mCurrentState = State.IDLE } } } abstract fun onStateChanged(appBarLayout: AppBarLayout?, state: State) }
使用:
appbar_layout.addOnOffsetChangedListener(object : AppBarStateChangeListener() { override fun onStateChanged(appBarLayout: AppBarLayout?, state: State) { if( state == State.EXPANDED ) { //展开状态
}else if(state == State.COLLAPSED){ //折叠状态 }else{ //中间状态
} } })
FAQ:
1.CoordinatorLayout中的recyclerview下拉刷新卡顿,当从下往上快速滑动时,每次需要等待几秒才能刷新,怎么解决?
当recyclerview快速滑动时,不能触发下拉刷新,这是因为onSrollStateChanged()回调没有及时调用,当快速滑动时,会调用
SCROLL_STATE_SETTLING(快速滑动)状态,该状态等到fling结束才会调用onSrollStateChanged()方法改变为SCROLL_STATE_IDLE(停止)状态(大概时间为2-3秒)。
recycleviewCars.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if(newState == RecyclerView.SCROLL_STATE_SETTLING){ recycleviewCars.stopScroll(); } } });