Toolbar
去掉ActionBar,使用Toolbar
Toolbar继承了ActionBar的所有功能,而且灵活性很高,可以配合其他控件完成一些MaterialDesign的效果。新建的项目,默认都是会显示ActionBar的,在AndroidManifest.xml文件里的application标签的android:theme属性配置的,如下
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidStudy">
......
android:theme属性的值@style/Theme.AndroidStudy在文件res/values/styles.xml文件中配置,如下
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.AndroidStudy" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
......
</style>
</resources>
可以看到Theme.AndroidStudy 的 parent是Theme.MaterialComponents.DayNight.DarkActionBar,而如果想要使用Toolbar来替代ActionBar,就需要指定一个不带ActionBar的主题,例如:Theme.MaterialComponents.Light.NoActionBar
使用Toolbar来替代ActionBar,修改 activity_meterial.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".meterial_study.MeterialActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/purple_500"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</androidx.constraintlayout.widget.ConstraintLayout>
由于我们刚才在styles.xml中将程序的主题指定成了浅色主题,因此Toolbar现在也是浅色主题,那么Toolbar上面的各种元素就会自动使用深色系,从而和主体颜色区别开。那么能让Toolbar单独使用深色主题,这里我们使用了android:theme属性,将Toolbar的主题指定成了ThemeOverlay.AppCompat.Dark.ActionBar。但是这样指定之后又会出现新的问题,如果Toolbar中有菜单按钮,那么弹出的菜单项也会变成深色主题,于是这里又使用了app:popupTheme属性,单独将弹出的菜单项指定成了浅色主题
修改 MeterialActivity
class MeterialActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_meterial)
// 调用setSupportActionBar()方法并将Toolbar的实例传入
setSupportActionBar(toolbar)
}
}
修改标题栏上显示的文字内容
在 AndroidManifest.xml 中的 activity 标签下的 android:label 属性设置,如果没有指定的话,会默认使用application中指定的label内容,也就是应用名称
<activity
android:name=".meterial_study.MeterialActivity"
android:exported="true"
android:label="Toolbar Test">
使用action按钮丰富Toolbar
创建 res/menu/toolbar.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!--android:id给这个菜单项指定一个唯一的标识符,
android:title给这个菜单项指定一个名称
android:icon用于指定按钮的图标-->
<item
android:id="@+id/backup"
android:icon="@drawable/ic_backup"
android:title="Backup"
app:showAsAction="always" />
<item
android:id="@+id/delete"
android:icon="@drawable/ic_delete"
android:title="Delete"
app:showAsAction="ifRoom" />
<item
android:id="@+id/settings"
android:icon="@drawable/ic_settings"
android:title="Settings"
app:showAsAction="never" />
</menu>
app:showAsAction 来指定按钮的显示位置,主要有以下几种值可选:
always表示永远显示在Toolbar中,如果屏幕空间不够则不显示;ifRoom表示屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单当中;never则表示永远显示在菜单当中。
注意,Toolbar中的action按钮只会显示图标,菜单中的action按钮只会显示文字
修改MeterialActivity
class MeterialActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_meterial)
// 调用setSupportActionBar()方法并将Toolbar的实例传入
setSupportActionBar(toolbar)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
// 加载了toolbar.xml这个菜单文件
menuInflater.inflate(R.menu.toolbar, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// 处理各个按钮的点击事件
when (item.itemId) {
R.id.backup -> Toast.makeText(this, "backup clicked", Toast.LENGTH_SHORT).show()
R.id.delete -> Toast.makeText(this, "delete clicked", Toast.LENGTH_SHORT).show()
R.id.settings -> Toast.makeText(this, "settings clicked", Toast.LENGTH_SHORT).show()
}
return super.onOptionsItemSelected(item)
}
}
效果
image-20211212142047474.png
滑动菜单
DrawerLayout
DrawerLayout简单用法
滑动菜单,就是将一些菜单选项隐藏起来,而不是放置在主屏幕上,然后可以通过滑动的方式将菜单显示出来。这种方式既节省了屏幕空间,又实现了非常好的动画效果
DrawerLayout 是一个布局,在布局中允许放入两个直接子控件:第一个子控件是主屏幕中显示的内容,第二个子控件是滑动菜单中显示的内容'
修改 activity_meterial.xml'
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".meterial_study.MeterialActivity">
<!--DrawerLayout的第一个子控件,用于作为主屏幕中显示的内容-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/purple_500"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</FrameLayout>
<!--DrawerLayout的第而个子控件,作为滑动菜单中显示的内容-->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#ffffff"
android:text="this is menu"
android:textSize="30sp" />
</androidx.drawerlayout.widget.DrawerLayout>
DrawerLayout 的第二个子控件有一点需要注意,layout_gravity这个属性是必须指定的,因为我们需要告诉DrawerLayout滑动菜单是在屏幕的左边还是右边,指定left表示滑动菜单在左边,指定right表示滑动菜单在右边。这里我指定了start,表示会根据系统语言进行判断,如果系统语言是从左往右的,比如英语、汉语,滑动菜单就在左边,如果系统语言是从右往左的,比如阿拉伯语,滑动菜单就在右边
效果
image-20211212143411437.png
为 DrawerLayout 添加导航按钮
在Toolbar的最左边加入一个导航按钮,点击按钮也会将滑动菜单的内容展示出来
class MeterialActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_meterial)
// 调用setSupportActionBar()方法并将Toolbar的实例传入
setSupportActionBar(toolbar)
// 添加DrawerLayout的导航按钮
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(R.drawable.ic_menu)
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
// 加载了toolbar.xml这个菜单文件
menuInflater.inflate(R.menu.toolbar, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// 处理各个按钮的点击事件
when (item.itemId) {
R.id.backup -> Toast.makeText(this, "backup clicked", Toast.LENGTH_SHORT).show()
R.id.delete -> Toast.makeText(this, "delete clicked", Toast.LENGTH_SHORT).show()
R.id.settings -> Toast.makeText(this, "settings clicked", Toast.LENGTH_SHORT).show()
// 处理DrawerLayout导航按钮点击事件
android.R.id.home -> drawerLayout.openDrawer(GravityCompat.START)
}
return super.onOptionsItemSelected(item)
}
}
调用getSupportActionBar()方法得到了ActionBar的实例,虽然这个ActionBar的具体实现是由Toolbar来完成的,调用setDisplayHomeAsUpEnabled()方法让导航按钮显示出来,调用setHomeAsUpIndicator()方法来设置一个导航按钮图标。
在onOptionsItemSelected()方法中对Home按钮的点击事件进行处理,Home按钮的id永远都是android.R.id.home。然后调用DrawerLayout的openDrawer()方法将滑动菜单展示出来,openDrawer()方法要求传入一个Gravity参数,为了保证这里的行为和XML中定义的一致,我们传入了GravityCompat.START
Toolbar最左侧的这个按钮就叫作Home按钮,它默认的图标是一个返回的箭头,含义是返回上一个Activity。很明显,这里我们将它默认的样式和作用都进行了修改
NavigationView
使用NavigationView可以将滑动菜单页面的实现变得简单
添加依赖
// Material库
implementation "com.google.android.material:material:1.1.0"
引入了Material库之后,需要将res/values/styles.xml文件中AppTheme的parent主题改成Theme.MaterialComponents.Light.NoActionBar,否则在使用接下来的一些控件时可能会遇到崩溃问题
menu 和 headerLayout
NavigationView需要准备两个东西:menu和headerLayout。menu是用来在NavigationView中显示具体的菜单项的,headerLayout则是用来在NavigationView中显示头部布局的
nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!--checkableBehavior指定为single表示组中的所有菜单项只能单选-->
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_call"
android:icon="@drawable/nav_call"
android:title="Call" />
<item
android:id="@+id/nav_friends"
android:icon="@drawable/nav_friends"
android:title="Friends" />
<item
android:id="@+id/nav_location"
android:icon="@drawable/nav_location"
android:title="Location" />
<item
android:id="@+id/nav_mail"
android:icon="@drawable/nav_mail"
android:title="Friends" />
<item
android:id="@+id/nav_task"
android:icon="@drawable/nav_task"
android:title="Task" />
</group>
</menu>
nav_header.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="180dp"
android:background="@color/purple_500">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/icon_image"
android:layout_width="70dp"
android:layout_height="70dp"
android:src="@drawable/nav_icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/round_style" />
<TextView
android:id="@+id/mail_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="123456@gmail.com"
android:textColor="#ffffff"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<TextView
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tony"
android:textColor="#ffffff"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@+id/mail_text"
app:layout_constraintLeft_toLeftOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
圆角控件ShapeableImageView
上面使用 ShapeableImageView 为 icon_image 设置了圆角,也可以设置为其他形状,ShapeableImageView 也是Material 的一个控件,需要添加Material 依赖,前面已经添加过了
使用方法
- 添加依赖
- 在 values/style.xml 中创建style
- 布局文件中使用ShapeableImageView,并设置shapeAppearanceOverlay或shapeAppearance指向style,也可以使用strokeColo指定描边颜色,strokeWidth指定描边宽度, elevation和padding搭配做出阴影
在 values/style.xml 中创建style
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 圆角图片Style -->
<style name="round_style">
<item name="cornerSize">35dp</item>
<!--也可以用下面的逐个设置圆角,但要注意不是cornerRadius属性-->
<!--<item name="cornerSizeTopLeft">35dp</item>
<item name="cornerSizeTopRight">35dp</item>
<item name="cornerSizeBottomRight">35dp</item>
<item name="cornerSizeBottomLeft">35dp</item>-->
</style>
</resources>
布局文件中使用ShapeableImageView,并使用style
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/icon_image"
android:layout_width="70dp"
android:layout_height="70dp"
android:src="@drawable/nav_icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/round_style" />
使用 NavigationView
menu和headerLayout准备好了,可以使用NavigationView了,修改activity_main.xml中的代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".meterial_study.MeterialActivity">
<!--DrawerLayout的第一个子控件,用于作为主屏幕中显示的内容-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/purple_500"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</FrameLayout>
<!--DrawerLayout的第而个子控件,作为滑动菜单中显示的内容-->
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:text="this is menu"
android:textSize="30sp"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
修改 MeterialActivity
......
// 将Call菜单项设置为默认选中
nav_view.setCheckedItem(R.id.nav_call)
nav_view.setNavigationItemSelectedListener { item ->
// 处理NavigationView菜单项的点击事件
when (item.itemId) {
R.id.nav_call -> Toast.makeText(this, "call clicked", Toast.LENGTH_SHORT).show()
R.id.nav_friends-> Toast.makeText(this, "friends clicked", Toast.LENGTH_SHORT).show()
R.id.nav_location -> Toast.makeText(this, "location clicked", Toast.LENGTH_SHORT).show()
R.id.nav_mail -> Toast.makeText(this, "mail clicked", Toast.LENGTH_SHORT).show()
R.id.nav_task -> Toast.makeText(this, "task clicked", Toast.LENGTH_SHORT).show()
}
drawerLayout.closeDrawers()
true
}
......
调用了NavigationView的setCheckedItem()方法将Call菜单项设置为默认选中。接着调用了setNavigationItemSelectedListener()方法来设置一个菜单项选中事件的监听器,当用户点击了任意菜单项时,就会回调到传入的Lambda表达式当中,我们可以在这里编写具体的逻辑处理。这里调用了DrawerLayout的closeDrawers()方法将滑动菜单关闭,并返回true表示此事件已被处理
效果
image-20211212153929006.png
悬浮按钮 FloatingActionButton
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".meterial_study.MeterialActivity">
<!--DrawerLayout的第一个子控件,用于作为主屏幕中显示的内容-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
......
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floating_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="20dp"
android:elevation="8dp"
android:src="@drawable/ic_done"
app:backgroundTint="#ff0000"
app:rippleColor="#ffffff"
app:tint="#00ff00" />
</FrameLayout>
......
</androidx.drawerlayout.widget.DrawerLayout>
app:elevation:给FloatingActionButton指定一个高度值。高度值越大,投影范围也越大,但是投影效果越淡;高度值越小,投影范围也越小,但是投影效果越浓
app:backgroundTint:设置背景色,如下面效果图的红色
app:rippleColor:设置点击时候的上面的蒙版颜色
app:tint:设置图标颜色,如下面效果图的绿色,要注意不是android:tint
同样使用setOnClickListener()方法来设置按钮的点击事件
效果图
image-20211212160453183.png
Snackbar
Material库提供的更加先进的提示工具,允许在提示中加入一个可交互按钮,下面代码在Toolbar上的删除图标点击时,显示Snackbar提示
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// 处理各个按钮的点击事件
when (item.itemId) {
R.id.backup -> Toast.makeText(this, "backup clicked", Toast.LENGTH_SHORT).show()
R.id.delete -> {
Snackbar.make(floating_button, "删除数据", Snackbar.LENGTH_SHORT)
.setAction("取消") {
// 在这里取消删除数据
Log.d("TAG", "onCreate: ")
}
.show()
}
R.id.settings -> Toast.makeText(this, "settings clicked", Toast.LENGTH_SHORT).show()
// 处理DrawerLayout导航按钮点击事件
android.R.id.home -> drawerLayout.openDrawer(GravityCompat.START)
}
return super.onOptionsItemSelected(item)
}
Snackbar.make()方法的第一个参数需要传入一个View,指定了Snackbar是基于哪个View触发的;第二个参数就是Snackbar中显示的内容;第三个参数是Snackbar显示的时长
效果图
image-20211212174132277.png
CoordinatorLayout
前面的Snackbar将我们的悬浮按钮给遮挡住了,借助CoordinatorLayout可以解决
CoordinatorLayout可以说是一个加强版的FrameLayout,它可以监听其所有子控件的各种事件,并自动帮助我们做出最为合理的响应。刚才弹出的Snackbar提示将悬浮按钮遮挡住了,而如果我们能让CoordinatorLayout监听到Snackbar的弹出事件,那么它会自动将内部的FloatingActionButton向上偏移,从而确保不会被Snackbar遮挡
修改activity_meterial.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".meterial_study.MeterialActivity">
<!--使用CoordinatorLayout,以免Snackbar遮住FloatingActionButton-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/purple_500"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floating_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="20dp"
android:elevation="8dp"
android:src="@drawable/ic_done"
app:backgroundTint="#ff0000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:rippleColor="#ffffff"
app:tint="#00ff00" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
......
</androidx.drawerlayout.widget.DrawerLayout>
Snackbar 出现的时候,悬浮按钮自动向上偏移了Snackbar的同等高度,从而确保不会被遮挡。当Snackbar消失的时候,悬浮按钮会自动向下偏移回到原来的位置
效果图
image-20211212174856505.png
卡片式布局 MaterialCardView
MaterialCardView也是一个FrameLayout,只是额外提供了圆角和阴影等效果,看上去会有立体的感觉
效果图
image-20211212184603785.png
下面就实现上面的效果
添加依赖
implementation "com.github.bumptech.glide:glide:4.9.0"
添加Glide库的依赖。Glide是一个超级强大的开源图片加载库,它不仅可以用于加载本地图片,还可以加载网络图片、GIF图片甚至是本地视频
代码实现
修改activity_meterial.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".meterial_study.MeterialActivity">
<!--DrawerLayout的第一个子控件,用于作为主屏幕中显示的内容-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
......
<!--添加RecyclerView-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
......
</androidx.coordinatorlayout.widget.CoordinatorLayout>
......
</androidx.drawerlayout.widget.DrawerLayout>
Fruit类
data class Fruit(val name: String, val imageId: Int)
新建fruit_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="fitXY" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:textSize="16sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
使用了MaterialCardView来作为子项的最外层布局,从而使得RecyclerView中的每个元素都是在卡片当中的。由于MaterialCardView是一个FrameLayout,因此它没有什么方便的定位方式,这里我们只好在MaterialCardView中再嵌套一个LinearLayout,然后在LinearLayout中放置具体的内容
新建FruitAdapter类
class FruitAdapter(val context: Context, val fruitList: List<Fruit>) :
RecyclerView.Adapter<FruitAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val fruitImage = view.findViewById<ImageView>(R.id.fruit_image)
val fruitName = view.findViewById<TextView>(R.id.fruit_name)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
return ViewHolder((view))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.fruitName.text = fruitList[position].name
Glide.with(context).load(fruitList[position].imageId).into(holder.fruitImage)
}
override fun getItemCount(): Int {
return fruitList.size
}
}
Glide的用法:先调用Glide.with()方法并传入一个Context、Activity或Fragment参数,然后调用load()方法加载图片,可以是一个URL地址,也可以是一个本地路径,或者是一个资源id,最后调用into()方法将图片设置到具体某一个ImageView中就可以了
如果图片像素非常高,不进行压缩就直接展示的话,很容易引起内存溢出。而使用Glide就完全不需要担心这回事,Glide在内部做了许多非常复杂的逻辑操作,其中就包括了图片压缩
修改MeterialActivity中的代码
class MeterialActivity : AppCompatActivity() {
private val fruits = mutableListOf(Fruit("Apple", R.drawable.apple),
Fruit("Orange", R.drawable.orange),
Fruit("Watermelon", R.drawable.watermelon),
Fruit("Pineapple", R.drawable.pineapple),
Fruit("Strawberry", R.drawable.strawberry),
Fruit("Banana", R.drawable.banana)
)
private val fruitList = ArrayList<Fruit>()
private fun initFruits() {
fruitList.clear()
repeat(50) {
val random = (0 until fruits.size).random()
fruitList.add(fruits[random])
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_meterial)
// 调用setSupportActionBar()方法并将Toolbar的实例传入
setSupportActionBar(toolbar)
// 添加DrawerLayout的导航按钮
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(R.drawable.ic_menu)
}
dealNavView()
initFruits()
recyclerView.layoutManager = GridLayoutManager(this, 2)
recyclerView.adapter = FruitAdapter(this, fruitList)
}
......
}
AppBarLayout
上面的效果中,Toolbar被RecyclerView给挡住了,可以使用AppBarLayout解决
由于RecyclerView和Toolbar都是放置在CoordinatorLayout中的,而前面已经说过,CoordinatorLayout就是一个加强版的FrameLayout,那么FrameLayout中的所有控件在不进行明确定位的情况下,默认都会摆放在布局的左上角,从而产生了遮挡的现象,使用定位也可以解决,但AppBarLayout更好一些
AppBarLayout实际上是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装
AppBarLayout使用方法
- 将Toolbar嵌套到AppBarLayout中
- 给RecyclerView指定一个布局行为
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".meterial_study.MeterialActivity">
<!--DrawerLayout的第一个子控件,用于作为主屏幕中显示的内容-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/purple_500"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
......
</androidx.coordinatorlayout.widget.CoordinatorLayout>
......
</androidx.drawerlayout.widget.DrawerLayout>
将Toolbar放置在了AppBarLayout里面,然后在RecyclerView中使用app:layout_behavior属性指定了一个布局行为。其中appbar_scrolling_view_behavior这个字符串也是由Material库提供的
事实上,当RecyclerView滚动的时候就已经将滚动事件通知给AppBarLayout了,当AppBarLayout接收到滚动事件的时候,它内部的子控件其实是可以指定如何去响应这些事件的,通过app:layout_scrollFlags属性就能实现,scroll表示当RecyclerView向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏;enterAlways表示当RecyclerView向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示;snap表示当Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示
下拉刷新SwipeRefreshLayout
把想要实现下拉刷新功能的控件放置到SwipeRefreshLayout中,就可以迅速让这个控件支持下拉刷新
添加依赖
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
代码
修改activity_meterial.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".meterial_study.MeterialActivity">
<!--DrawerLayout的第一个子控件,用于作为主屏幕中显示的内容-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
......
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
......
</androidx.drawerlayout.widget.DrawerLayout>
修改MeterialActivity
// 设置下拉刷新进度条颜色
swipe_refresh.setColorSchemeResources(R.color.purple_200)
// 下拉刷新监听
swipe_refresh.setOnRefreshListener {
Thread.sleep(2000)
runOnUiThread {
initFruits()
fruitAdapter.notifyDataSetChanged()
// 加载完成后,一定要将isRefreshing设置为false,否则刷新进度条一直转
swipe_refresh.isRefreshing = false
}
}
效果图
image-20211212213622107.png
可折叠式标题栏CollapsingToolbarLayout
现在的标题栏是使用Toolbar来编写的,不过它看上去和传统的ActionBar没什么两样,只不过可以响应RecyclerView的滚动事件来进行隐藏和显示。可以借助CollapsingToolbarLayout实现一个可折叠式标题栏的效果
CollapsingToolbarLayout是不能独立存在的,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。而AppBarLayout又必须是CoordinatorLayout的子布局
创建水果详情展示界面
创建一个 FruitActivity 作为水果的详情展示界面
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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"
tools:context=".meterial_study.FruitActivity">
<!--标题栏部分-->
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="250dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentScrim="@color/purple_500"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/fruit_image"
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.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:layout_marginTop="35dp"
app:cardCornerRadius="4dp">
<TextView
android:id="@+id/fruit_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</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/app_bar"
app:layout_anchorGravity="bottom|end"
app:tint="#ffffff" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
app:contentScrim属性用于指定CollapsingToolbarLayout在趋于折叠状态以及折叠之后的背景色,app:layout_scrollFlags的scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动,exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕
app:layout_collapseMode用于指定当前控件在CollapsingToolbarLayout折叠过程中的折叠模式,其中Toolbar指定成pin,表示在折叠的过程中位置始终保持不变,ImageView指定成parallax,表示会在折叠的过程中产生一定的错位偏移,这种模式的视觉效果会非常好
水果内容详情的最外层布局使用了一个NestedScrollView,注意它和AppBarLayout是平级的,ScrollView和NestedScrollView相似,它们的内部都只允许存在一个直接子布局,而NestedScrollView在此基础之上还增加了嵌套响应滚动事件的功能。由于CoordinatorLayout本身已经可以响应滚动事件了,因此我们在它的内部就需要使用NestedScrollView或RecyclerView这样的布局
加入一个悬浮按钮,将锚点设置为AppBarLayout,这样悬浮按钮就会出现在水果标题栏的区域内,接着又使用app:layout_anchorGravity属性将悬浮按钮定位在标题栏区域的右下角
FruitActivity
class FruitActivity : AppCompatActivity() {
companion object {
private const val TAG = "FruitActivity"
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)
// 获取传过来的水果名和图片id
val fruitName = intent.getStringExtra(FRUIT_NAME)
val fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID, 0)
//设置toolbar
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
collapsing_toolbar.title = fruitName
//设置图片和内容
Glide.with(this).load(fruitImageId).into(fruit_image)
fruit_content.text = fruitName.repeat(500)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
android.R.id.home->{
finish()
}
}
return super.onOptionsItemSelected(item)
}
}
修改FruitAdapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
val holder = ViewHolder((view))
holder.itemView.setOnClickListener {
val fruit = fruitList[holder.bindingAdapterPosition]
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
}
效果图
上滑前的界面
image-20211212223240006.png
上滑后的界面
image-20211212223349302.png
系统状态栏空间
借助android:fitsSystemWindows这个属性让背景图能够和系统状态栏融合,将控件的android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里,上面的代码只给ImageView设置这个属性是没有用的,我们必须将ImageView布局结构中的所有父布局都设置上这个属性才可以
同时还必须在程序的主题中将状态栏颜色指定成透明色才行。指定成透明色的方法很简单,在主题中将android:statusBarColor属性的值指定成@android:color/transparent就可以了
<style name="MyTheme" parent="Theme.AndroidStudy">
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
注意,需要重新写一个 theme,继承自默认的 theme,否则没有效果。然后到AndroidManifest.xml文件中应用
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/MyTheme">
效果图
image-20211212224223017.png










网友评论