本文介绍以下很 google的UI控件:CoordinatorLayout、AppbarLayout、NestedScrollView以及CollapsingToolbarLayout。它们共同实现了下面这样的效果:
「CoordinatorLayout」:本质是一个「FrameLayout」,是一个“super-powered FrameLayout”,它的主要作用:作为顶层布局,协调子View之间的交互;
「AppBarLayout」:是一种支持响应滚动手势的app bar布局,与「CoordinatorLayout」控件一起使用,实现「AppBarLayout」内部子View的Material Design滚动效果;
「NestedScrollView」:若是需要「AppbarLayout」中的子View实现滚动效果,需要配合实现一个带有「Scroll」属性的View,这个View最好是「NestedScrollView」、「RecyclerView」或是存在「Scroll」属性的控件模块(本文是一个带有RecyclerView的Fragment模块);
「CollapsingToolbarLayout」:「CollapsingToolbarLayout」是一个ViewGroup,看名字知道,它主要是用来包装「Toolbar」控件,实现折叠(其实就是看起来像伸缩~)的效果,它需要作为「AppBarLayout」布局的直接子View。
使用「CoordinatorLayout」、「AppBarLayout」以及实现了「Scroll」属性的「Fragment」共同实现Toolbar的滚动显示和隐藏效果。 先上效果图以及代码:
main_activity.layout
fragment.layout
代码中用到的依赖库:
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation "com.android.support:design:$support_version"
implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.0.3'
上面的代码简单实现了Toolbar的滚动显示和滚动隐藏效果。在这里需要了解实现这样效果的两个关键属性:「app:layout_scrollFlags」、「app:layout_behavior」。
AppBarLayout 继承自LinearLayout,布局方向为垂直方向。所以你可以把它当成垂直布局的LinearLayout来使用。AppBarLayout是在LinearLayou上加了一些材料设计的概念,它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作。
「app:layout_behavior」:控件「ConstraintLayout」中实现「NestedScrollView」机制(Fragment模块中包含RecyclerView控件)的子View(不包括AppBarLayout),设置该属性的固定value:
app:layout_behavior="@string/appbar_scrolling_view_behavior"
这是一个系统behavior, 从字面意思就可以看到, 是为appbar设置滚动动作的一个behavior. 没有这个属性的话, Appbar就是死的, 有了它就有了灵魂.
我们可以通过给Appbar下的子View添加app:layout_scrollFlags来设置各子View执行的动作. scrollFlags可以设置的动作如下:
「app:layout_scrollFlags」:
scroll: 值设为scroll的View(Toolbar)会跟随滚动事件一起发生移动。即当带有「Scroll」属性的View滚动时,该View(Toolbar)也跟随一起滚动,就好像这个View也是属于这个ScrollView一样。
上面的效果图就是Toolbar设置了app:layout_scrollFlags="scroll"的效果图。
enterAlways: 顾名思义,View(Toolbar)总是(Always)“进入”。这个“进入”的意思,包括显示和隐藏;也即任何时候ScrollView滚动时,该View(Toolbar)都会同步滚动出现(隐藏)。
修改Toolbar的app:layout_scrollFlags:
app:layout_scrollFlags="scroll|enterAlways"
这个效果,需要设置View(Toolbar)的最大和最小高度(Toolbar默认的最小高度就是?attr/actionBarSize),直接上代码和效果图,理解这个效果:
注意:从图上可以看出, 在最大高度时,title的位置和toolbar上的图标行脱离了;并且View(Toolbar)滚动出现到最小高度时,title和图标都没有出现。 即使在布局里添加了 android:gravity=“bottom|start”, 在toolbar滚动的时候, title还在, 图标依然滚动到隐藏了,这个问题在后边的「CollapsingToolbarLayout」控件中得以解决。
(1) 折叠Title(Collapsing title):布局完全显示时,Toolbar的Title是最大的;但随着View(Toolbar)逐步折叠滚动隐藏,title逐渐减小并最终显示在Toolbar正常大小情况下的Title位置。
(2)内容纱布(Content scrim):根据滚动的位置(这个位置系统内定),决定是否对View(Toolbar)“盖上纱布”。可以通过setContentScrim(Drawable)来设置纱布的图片. 默认contentScrim是colorPrimary的色值。
(3)状态栏纱布(Status bar scrim):根据滚动的位置(这个位置系统内定),决定是否对状态栏“盖上纱布”,可以通过setStatusBarScrim(Drawable)来设置纱布图片,但是只能在LOLLIPOP设备上面有作用。默认statusBarScrim是colorPrimaryDark的色值。
(4)视差滚动子View(Parallax scrolling children): View(CollapsingToolbarLayout中可以有多个子View,此View不限于Toolbar)在当前的布局中按照“视差”的方式来跟随滚动。(PS:其实就是让这个View的滚动的速度比其他正常滚动的View速度稍微慢一点)。将布局参数app:layout_collapseMode设为parallax
(5)子View位置固定(Pinned position children):子View可以选择是否在全局空间上固定位置,这对于Toolbar来说非常有用,因为当布局在移动时,可以将Toolbar固定位置而不受移动的影响。 将app:layout_collapseMode设为pin。
按照上面对于控件「CollapsingToolbarLayout」的功能描述,我们对代码中「AppBarLayout」控件及其子View进行修改,并查看效果图:
可以看到,我们使用「CollapsingToolbarLayout」对「Toolbar」控件进行包装,并将一些原属于「AppBarLayout」布局内部的属性提取到「CollapsingToolbarLayout」控件中:
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:popupTheme="@style/AppTheme.PopupOverlay"
同时,设置「CollapsingToolbarLayout」的“内容纱布”的代码为:
app:contentScrim="?attr/colorPrimary"
设置「Toolbar」的位置固定:
app:layout_collapseMode="pin"
效果如图:
基本达到了文章开头的效果图样式,现在还欠缺的就是「Toolbar」最大化显示时的背景图片、背景图片的折叠效果、内容(Toolbar)栏以及状态栏的UI变化效果。
上面既然介绍到控件「CollapsingToolbarLayout」是一个ViewGroup,那么它就可以再容纳其他的子View,此时就可以添加我们需要的背景图片了。
看代码:
代码中在控件「CollapsingToolbarLayout」的布局中,增加一个ImageView类型的子View,并添加如下「CollapsingToolbarLayout」的功能代码
app:layout_collapseMode="parallax"
再看效果图:
注意:代码中设置的没有设置「Toolbar」的背景,只是在「CollapsingToolbarLayout」布局中增加了“内容纱布”背景代码。所以在「Toolbar」完全显示时的背景栏为透明的,达到最小高度时的背景为「ColorPrimary」
这节最后再说一下,因为目前大部分的app设计都是沉浸式的状态栏,即状态栏透明,与「Toolbar」的背景一样。所以我们可以将状态栏的背景设置为透明,这就需要修改主题了:
在布局里面, 将ImageView和所有它上面的父View都添加fitsSystemWindows属性
...
还是看代码,看效果图:
效果图:
最后的最后,文章开头的效果图中,还有一个「TabLayout」控件的效果。这个就直接在「Fragment」对应的布局文件中增加控件,并在代码中添加对应的「TabLayout.Tab」就可以了。
上代码:
fragment.layout
java代码中增加「TabLayout.Tab」:
...
val tabLayout = view.findViewById(R.id.tabs)
tabLayout.addTab(tabLayout.newTab().setText("标题一"))
tabLayout.addTab(tabLayout.newTab().setText("标题二"))
tabLayout.addTab(tabLayout.newTab().setText("标题三"))
tabLayout.addTab(tabLayout.newTab().setText("标题四"))
val defaultTab = tabLayout.newTab().setText("标题五")
tabLayout.addTab(defaultTab)
defaultTab.select()
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
var pos = tab.position
}
override fun onTabUnselected(tab: TabLayout.Tab) {
var pos = tab.position
}
override fun onTabReselected(tab: TabLayout.Tab) {
var pos = tab.position
}
})
...
最终的效果图,就是文章开头的样子了。
OK,到这里终于搞定这个比较Material Design的Toolbar效果了。本文参考了如下文章:
https://www.jianshu.com/p/bbc703a0015e