27.1 Toolbar
在res/values/themes.xml中
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.MaterialTest" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<item name="colorPrimary">@color/design_default_color_primary</item>
<item name="colorPrimaryDark">@color/design_default_color_primary_dark</item>
</style>
<style name="Theme.MaterialTest" parent="Base.Theme.MaterialTest" />
</resources>
Theme.Material3.DayNight.NoActionBar指定了主题,为DayNight.NoActionBar,无ActionBar。
在activity_main.xml中添加Toolbar
<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=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:background="@color/design_default_color_primary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
...
</androidx.constraintlayout.widget.ConstraintLayout>
在MainActvity中
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
setSupportActionBar(viewBinding.toolbar)
}
新建toolbar布局文件,在res下新建menu文件夹,menu中新建Menu resource file文件,命名toolbar.xml。
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">
<item
android:id="@+id/backup"
android:icon="@drawable/back_white"
android:title="Backup"
app:showAsAction="always"
/>
<item
android:id="@+id/delete"
android:icon="@drawable/delete_white"
android:title="Delete"
app:showAsAction="ifRoom"
/>
<item
android:id="@+id/settings"
android:icon="@drawable/setting_white"
android:title="Settings"
app:showAsAction="never"
/>
</menu>
showAsAction来指定按钮显示的位置
- always: 永远显示在Toolbar中,如果屏幕空间不够则不显示
- ifRoom: 屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单中
- never:永远显示在菜单中
注意: Toolbar中action按钮只会显示图标,菜单中的action只会显示文字。
最后再修改MainActvity
class MainActivity : AppCompatActivity() {
private lateinit var viewBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
setSupportActionBar(viewBinding.toolbar)
}
// 导航栏
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
var msg = ""
when (item.itemId) {
R.id.backup -> msg = "Backup"
R.id.delete -> msg = "Delete"
R.id.settings -> msg = "Settings"
}
Toast.makeText(this, "You clicked $msg", Toast.LENGTH_SHORT).show()
return true
}
}
27.2 滑动菜单-DrawerLayout
xml
android:layout_gravity="start" 是必须设置的,告诉drawer从那边出来
- left: 左边
- right: 右边
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:background="@color/design_default_color_primary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</FrameLayout>
<TextView
android:text="This is menu"
android:textSize="30sp"
android:layout_gravity="start"
android:background="#ffffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.drawerlayout.widget.DrawerLayout>
activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
setSupportActionBar(viewBinding.toolbar)
supportActionBar?.let {
it.setDisplayHomeAsUpEnabled(true)
it.setHomeAsUpIndicator(R.drawable.home_white)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
var msg = ""
when (item.itemId) {
R.id.backup -> msg = "Backup"
R.id.delete -> msg = "Delete"
R.id.settings -> msg = "Settings"
android.R.id.home -> viewBinding.drawerLayout.openDrawer(GravityCompat.START)
}
if (msg.isNotEmpty()) {
Toast.makeText(this, "You clicked $msg", Toast.LENGTH_SHORT).show()
}
return true
}
用getSupportActionBar()方法得到了ActionBar的实例。setDisplayHomeAsUpEnabled()方法让ActionBar显示出来返回按钮,setHomeAsUpIndicator()方法来设置一个导航按钮图标。
27.3 NavigationView
menu/nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/navCall"
android:icon="@drawable/call"
android:title="Call"/>
<item
android:id="@+id/navFriends"
android:icon="@drawable/friend"
android:title="Friends"/>
<item
android:id="@+id/navLocation"
android:icon="@drawable/location"
android:title="Location"/>
<item
android:id="@+id/navMail"
android:icon="@drawable/mail"
android:title="Mail"/>
</group>
</menu>
layout/nav_header.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:padding="10dp"
android:background="@color/design_default_color_primary"
xmlns:app="http://schemas.android.com/apk/res-auto">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/iconImage"
android:src="@drawable/img_2"
android:layout_centerInParent="true"
android:layout_width="70dp"
android:layout_height="70dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/mailText"
android:text="123456789@gmail.com"
android:textColor="#ffffff"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/iconImage"
app:layout_constraintStart_toStartOf="parent"
/>
<TextView
android:id="@+id/userText"
android:text="Loong"
android:textColor="#ffffff"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/mailText"
app:layout_constraintStart_toStartOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity.xml使用
<com.google.android.material.navigation.NavigationView
android:id="@+id/navView"
android:layout_gravity="start"
app:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header"
android:background="#ffffff"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
activity
// 默认选中
viewBinding.navView.setCheckedItem(R.id.navCall)
// 点击回调
viewBinding.navView.setNavigationItemSelectedListener {
// 关闭drawerLayout抽屉
viewBinding.drawerLayout.closeDrawers()
// 返回true,表示已处理
true
}
27.4 悬浮按钮-FloatiingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingButton"
android:src="@drawable/home_gray"
android:elevation="18dp"
android:layout_margin="30dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
用app:elevation属性给FloatingActionButton指定一个高度值。高度值越大,投影范围也越大,但是投影效果越淡;高度值越小,投影范围也越小,但是投影效果越浓。
viewBinding.floatingButton.setOnClickListener {
Toast.makeText(this, "FAB clicked", Toast.LENGTH_SHORT).show()
}
其它和普通Button一样。
27.5 Snackbar
Snackbar并不是Toast的替代品,它们有着不同的应用场景。Toast的作用是告诉用户现在发生了什么事情,但用户只能被动接收这个事情,因为没有什么办法能让用户进行选择。而Snackbar则在这方面进行了扩展,它允许在提示中加入一个可交互按钮,当用户点击按钮的时候,可以执行一些额外的逻辑操作。
viewBinding.floatingButton.setOnClickListener {
Snackbar.make(it, "Data deleted", Snackbar.LENGTH_SHORT)
.setAction("Undo") {
Toast.makeText(this, "Data restored", Toast.LENGTH_SHORT).show()
}
.show()
}
27.6 卡片式布局-MaterialCardView
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
app:elevation="5dp">
<TextView
android:id="@+id/infoText"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.card.MaterialCardView>
- app:cardCornerRadius: 卡片圆角弧度
- app:elevation:卡片阴影大小
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruitImage"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="100dp"/>
<TextView
android:id="@+id/fruitName"
android:layout_gravity="center_horizontal"
android:textSize="16sp"
android:layout_margin="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
fruit_item.xml
cell布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruitImage"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="100dp"/>
<TextView
android:id="@+id/fruitName"
android:layout_gravity="center_horizontal"
android:textSize="16sp"
android:layout_margin="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
Model类
class Fruit {
var name: String = ""
var imageUrl: String = ""
constructor(name: String, url: String) {
this.name = name
this.imageUrl = url
}
}
Adapter类
class FruitAdapter(val context: Context, val fruitList: List<Fruit>): RecyclerView.Adapter<FruitAdapter.ViewHolder>() {
inner class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
val fruitName: TextView = view.findViewById(R.id.fruitName)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.fruit_item, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return fruitList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
holder.fruitName.text = fruit.name
Log.d("Adapter", "${fruit.imageUrl}")
Glide.with(context).load(fruit.imageUrl).into(holder.fruitImage)
}
}
Activty使用
class MainActivity : AppCompatActivity() {
private lateinit var viewBinding: ActivityMainBinding
val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
//...
initFruitList()
val layoutManager = GridLayoutManager(this, 2)
val recyclerView = viewBinding.recyclerView
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(this, fruitList)
recyclerView.adapter = adapter
}
fun initFruitList() {
val u1 = "https://img2.baidu.com/it/u=2045021150,2452876524&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"
// ...
val fruits = mutableListOf(
Fruit("Banana", u1)
)
repeat(20) {
val index = (0 until fruits.size).random()
fruitList.add(fruits[index])
}
}
}
27.7 AppBarLayout
可结合CoordinatorLayout使用
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<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:background="@color/design_default_color_primary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_scrollFlags="scroll|enterAlways|snap"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
//...
</androidx.drawerlayout.widget.DrawerLayout>
RecyclerView中使用app:layout_behavior属性指定了一个布局行为。其中appbar_scrolling_view_behavior这个字符串也是由Material库提供的。
在Toolbar中添加了一个app:layout_scrollFlags属性:
-
scroll: 表示当RecyclerView向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏;
-
enterAlways: 表示当RecyclerView向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示;
-
snap: 表示当Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。
27.8 下拉刷新-swiperefreshlayout
swiperefreshlayout
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
在要添加下拉刷新的控件外面,包装一层swiperefreshlayout
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
设置颜色、刷新事件
viewBinding.swipeRefresh.setColorSchemeResources(R.color.black)
viewBinding.swipeRefresh.setOnRefreshListener {
refreshFruits(adapter)
}