虽说现在的标题栏是使用Toolbar来编写的,不过它看上去和传统的ActionBar没什么两样,只不过可以响应RecyclerView的滚动事件来进行隐藏和显示。还可以根据自己的喜好随意定制标题栏的样式
这里实现一个可折叠式标题栏的效果,要借助CollapsingToolbarLayout这个工具
可折叠式标题栏:CollapsingToolbarLayout
顾名思义,CollapsingToolbarLayout是一个作用于Toolbar基础之上的布局,它也是由Material库提供的
CollapsingToolbarLayout可以让Toolbar的效果变得更加丰富,不仅仅是展示一个标题栏,而且能够实现非常华丽的效果
不过,CollapsingToolbarLayout是不能独立存在的,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。而AppBarLayout又必须是CoordinatorLayout的子布局,因此要实现的功能其实需要综合运用前面所了解的各种知识
首先我们需要一个额外的Activity作为水果的详情展示界面,右击com.example.materialtest包→New→Activity→Empty Activity,创建一个FruitActivity,并将布局名指定成activity_fruit.xml
然后开始编写水果详情展示界面的布局。由于整个布局文件比较复杂,这里准备采用分段编写的方式
activity_fruit.xml中的内容主要分为两部分,一个是水果标题栏,一个是水果内容详情
首先实现标题栏部分,这里使用CoordinatorLayout作为最外层布局
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.coordinatorlayout.widget.CoordinatorLayout>
这里就注意要始终记得定义一个xmlns:app的命名空间,在Material Design的开发中会经常用到它
接着在CoordinatorLayout中嵌套一个AppBarLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp">
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
这里给AppBarLayout定义了一个id,将它的宽度指定为match_parent,高度指定为250 dp
接下来在AppBarLayout中再嵌套一个CollapsingToolbarLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentScrim="@color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
这里使用了新的布局CollapsingToolbarLayout。其中,id、layout_width和layout_height这几个属性比较简单
android:theme属性指定了一个ThemeOverlay.AppCompat.Dark.ActionBar的主题
app:contentScrim属性用于指定CollapsingToolbarLayout在趋于折叠状态以及折叠之后的背景色,其实CollapsingToolbarLayout在折叠之后就是一个普通的Toolbar,那么背景色肯定应该是colorPrimary了
app:layout_scrollFlags属性也是见过的,只不过之前是给Toolbar指定的,现在也移到外面来了。其中
- scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动,
- exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕。
接下来,在CollapsingToolbarLayout中定义标题栏的具体内容,如下所示:
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentScrim="@color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/fruitImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
在CollapsingToolbarLayout中定义了一个ImageView和一个Toolbar,也就意味着,这个高级版的标题栏将是由普通的标题栏加上图片组合而成的
app:layout_collapseMode它用于指定当前控件在CollapsingToolbarLayout折叠过程中的折叠模式,其中Toolbar指定成pin,表示在折叠的过程中位置始终保持不变,ImageView指定成parallax,表示会在折叠的过程中产生一定的错位偏移,这种模式的视觉效果会非常好
然后准备在activity_fruit.xml中加入一个悬浮按钮。这个界面是一个水果详情展示界面,那么就加入一个表示评论作用的悬浮按钮
首先需要提前准备好一个图标,这里放置了一张ic_comment.png到drawable-xxhdpi目录下
然后修改
activity_fruit.xml中的代码,如下所示:
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp">
...
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
...
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_comment"
app:layout_anchor="@id/appBar"
app:layout_anchorGravity="bottom|end" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
这里加入了一个FloatingActionButton,它和AppBarLayout以及NestedScrollView是平级的FloatingActionButton中使用app:layout_anchor属性指定了一个锚点,将锚点设置为AppBarLayout,这样悬浮按钮就会出现在水果标题栏的区域内,接着又使用app:layout_anchorGravity属性将悬浮按钮定位在标题栏区域的右下角
好了,现在终于将整个activity_fruit.xml布局都编写完了。接下来我们开始编写功能逻辑,修改FruitActivity中的代码,如下所示:
class FruitActivity : AppCompatActivity() {
companion object {
const val FRUIT_NAME = "fruit_name"
const val FRUIT_IMAGE_ID = "fruit_image_id"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fruit)
val fruitName = intent.getStringExtra(FRUIT_NAME) ?: ""
val fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID, 0)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
collapsingToolbar.title = fruitName
Glide.with(this).load(fruitImageId).into(fruitImageView)
fruitContentText.text = generateFruitContent(fruitName)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
finish()
return true
}
}
return super.onOptionsItemSelected(item)
}
private fun generateFruitContent(fruitName: String) = fruitName.repeat(500)
}
首先,在onCreate()方法中,通过Intent获取了传入的水果名和水果图片的资源id
接着使用了Toolbar的标准用法,将它作为ActionBar显示,并启用Home按钮。由于Home按钮的默认图标就是一个返回箭头,因此就不用额外设置别的图标了
接下来开始填充界面上的内容,调用CollapsingToolbarLayout的setTitle()方法,将水果名设置成当前界面的标题,然后使用Glide加载传入的水果图片,并设置到标题栏的ImageView上面
接着需要填充水果的内容详情,由于这只是一个示例程序,并不需要什么真实的数据,所以使用了一个generateFruitContent()方法将水果名循环拼接500次,从而生成了一个比较长的字符串,将它设置到了TextView上面
最后,在onOptionsItemSelected()方法中处理了Home按钮的点击事件,当点击这个按钮时,就调用finish()方法关闭当前的Activity,从而返回上一个Activity
还差最关键的一步,就是处理RecyclerView的点击事件,不然的话,根本就无法打开FruitActivity。修改FruitAdapter中的代码,如下所示:
class FruitAdapter(val context: Context, val fruitList: List<Fruit>) :
RecyclerView.Adapter<FruitAdapter.ViewHolder>() {
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.fruit_item, parent, false)
val holder = ViewHolder(view)
holder.itemView.setOnClickListener {
val position = holder.adapterPosition
val fruit = fruitList[position]
val intent = Intent(context, FruitActivity::class.java).apply {
putExtra(FruitActivity.FRUIT_NAME, fruit.name)
putExtra(FruitActivity.FRUIT_IMAGE_ID, fruit.imageId)
}
context.startActivity(intent)
}
return holder
}
...
}
这里给fruit_item.xml的最外层布局注册了一个点击事件监听器,然后在点击事件中获取当前点击项的水果名和水果图片资源id,把它们传入Intent中,最后调用startActivity()方法启动FruitActivity
运行程序,并点击界面上的任意一个水果,比如点击了葡萄
可以尝试向上拖动水果内容详情,会发现水果背景图上的标题会慢慢缩小,并且背景图会产生一些错位偏移的效果
这是由于用户想要查看水果的内容详情,此时界面的重点在具体的内容上面,因此标题栏就会自动进行折叠,从而节省屏幕空间
继续向上拖动,直到标题栏变成完全折叠状态
可以看到,标题栏的背景图片不见了,悬浮按钮也自动消失了,现在水果标题栏变成了一个最普通的Toolbar。这是由于用户正在阅读具体的内容,需要给他们提供最充分的阅读空间
而如果这个时候向下拖动水果内容详情,就会执行一个完全相反的动画过程
充分利用系统状态栏空间
仔细观察一下,会发现水果的背景图片和系统的状态栏总有一些不搭的感觉,如果能将背景图和状态栏融合到一起,那这个视觉体验绝对能提升好几个档次
想要让背景图能够和系统状态栏融合,需要借助android:fitsSystemWindows这个属性来实现
在CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout这种嵌套结构的布局中,将控件的android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里
对应到的程序,那就是水果标题栏中的ImageView应该设置这个属性了
不过只给ImageView设置这个属性是没有用的,必须将ImageView布局结构中的所有父布局都设置上这个属性才可以
修改activity_fruit.xml中的代码,如下所示:
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true"
app:contentScrim="@color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/fruitImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax" />
...
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
即使将android:fitsSystemWindows属性都设置好了也没有用,因为还必须在程序的主题中将状态栏颜色指定成透明色才行
指定成透明色的方法很简单,在主题中将android:statusBarColor属性的值指定成@android:color/transparent就可以了
打开res/values/themes.xml文件,添加一个透明主题
<style name="FruitActivityTheme" parent="AppTheme">
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
这里添加了一个FruitActivityTheme主题,它是专门给FruitActivity使用的
FruitActivityTheme的父主题是AppTheme,也就是说,它继承了AppTheme中的所有特性
在此基础之上,将FruitActivityTheme中的状态栏的颜色指定成透明色
最后,还需要让FruitActivity使用这个主题才可以,修改AndroidManifest.xml中的代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.materialtest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<activity
android:name=".FruitActivity"
android:theme="@style/FruitActivityTheme">
</activity>
</application>
</manifest>
使用android:theme属性单独给FruitActivity指定了FruitActivityTheme这个主题
重新运行程序