Material Design 一套全新的界面设计语言,包含了视觉、运动、互动效果等特性, 其设计理念和工具都封装到了 Material 库中
Toolbar 不仅继承了 ActionBar 的所有功能, 而且拥有更强的灵活性
ActionBar ActionBar 的配置来源于主题文件: res/values/themes.xml
在这里实现父主题的选取以及各个主题色的配置, 使用 .NoActionBar
即为没有 ActionBar 的主题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <style name ="Theme.23MaterialTest" parent ="Theme.MaterialComponents.Light.NoActionBar" > <item name ="colorPrimaryVariant" > @color/purple_700</item > <item name ="colorOnPrimary" > @color/white</item > <item name ="colorSecondary" > @color/teal_200</item > <item name ="colorSecondaryVariant" > @color/teal_700</item > <item name ="colorOnSecondary" > @color/black</item > <item name ="android:statusBarColor" tools:targetApi ="l" > ?attr/colorPrimaryVariant</item > <item name ="colorPrimary" > @color/colorPrimary</item > <item name ="colorPrimaryDark" > @color/colorPrimaryDark</item > <item name ="colorAccent" > @color/colorAccent</item > </style >
主题色的分布
colorAccent: 不只是用来指定一个按钮的颜色,而更多表达了一种强调的意思,比如一些控件的选中状态也会使用该颜色
colorPrimary: 标题栏颜色
colorPrimaryVariant: 状态栏颜色
windowBackgroud:
navigationBarColor:
1 2 3 4 5 6 7 8 9 10 <item name ="android:textColorPrimary" > @color/colorAccent</item > <item name ="android:windowBackground" > @color/teal_700</item > <item name ="android:navigationBarColor" > @color/purple_200</item > <item name ="colorPrimary" > @color/colorPrimary</item > <item name ="colorPrimaryVariant" > @color/colorPrimaryDark</item >
Quick Start
编写布局文件: 新增 app 命名空间, 使得 Material 能够兼容老系统
Tips: 主程序主题为浅色, 为了和主体颜色区分, 因此 Toolbar 使用深色 (Dark) 主题; 为了避免 Toolbar 中的菜单按钮也继承深色主题导致无法区分, 因此手动指定 Toolbar 中的菜单按钮为浅色 (Light) 主题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <FrameLayout 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.appcompat.widget.Toolbar android:id ="@+id/toolbar" android:layout_width ="match_parent" android:layout_height ="?attr/actionBarSize" android:background ="@color/colorPrimary" android:theme ="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme ="@style/ThemeOverlay.AppCompat.Light" /> </FrameLayout >
MainActivity 中配置 toolbar 实例
toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar)
属性修改
标题栏文字: AndroidManifest 中 activity 中的 label 属性指定; 默认使用 AndroidManifest 中 Application 中的 label
滑动菜单 滑动菜单: 将一些菜单选项隐藏起来,而不是放置在主屏幕上,然后可以通过滑动的方式将菜单显示出来
DrawerLayout DrawerLayout 布局中允许放入两个直接子控件:
主屏幕中显示的内容
滑动菜单中显示的内容
滑动菜单内容必须指定 layout_gravity 属性, 用于指明滑出的方向
drawerLayout.isDrawerOpen(GravityCompat.START): 根据指定的滑出动作判断是否已经滑出
drawerLayout.openDrawer(GravityCompat.START): 使其滑出
drawerLayout.closeDrawer(GravityCompat.START): 使其关闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <androidx.drawerlayout.widget.DrawerLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" android:id ="@+id/drawerLayout" android:layout_width ="match_parent" android:layout_height ="match_parent" > <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/colorPrimary" android:theme ="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme ="@style/ThemeOverlay.AppCompat.Light" /> </FrameLayout > <TextView android:layout_width ="match_parent" android:layout_height ="match_parent" android:layout_gravity ="start" android:background ="#FFF" android:text ="This is menu" android:textSize ="30sp" /> </androidx.drawerlayout.widget.DrawerLayout >
为了更好的触发滑动菜单, 可以在标题栏中添加导航按钮
1 2 3 4 5 6 7 8 9 10 11 supportActionBar?.let { it.setDisplayHomeAsUpEnabled(true ) it.setHomeAsUpIndicator(R.drawable.ic_menu) } override fun onOptionsItemSelected (item: MenuItem ) : Boolean { when (item.itemId) { android.R.id.home -> drawerLayout.openDrawer(GravityCompat.START) } return true }
NavigationView 美化滑动菜单
app/build.gradle 中引入相关依赖
implementation 'com.google.android.material:material:1.1.0' implementation 'de.hdodenhof:circleimageview:3.0.1'
使用 Material 库需要替换默认的 ActionBar 即使用 NoActionBar 主题
menu: ret/menu/nav_menu, 在 NavigationView 中显示具体的菜单项: group 表示一个组,checkableBehavior 指定为 single 表示组中的所有菜单项只能单选
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <menu xmlns:android ="http://schemas.android.com/apk/res/android" > <group android:checkableBehavior ="single" > <item android:id ="@+id/navCall" android:icon ="@drawable/nav_call" android:title ="Call" /> <item android:id ="@+id/navFriends" android:icon ="@drawable/nav_friends" android:title ="Friends" /> <item android:id ="@+id/navLocation" android:icon ="@drawable/nav_location" android:title ="Location" /> <item android:id ="@+id/navMail" android:icon ="@drawable/nav_mail" android:title ="Mail" /> <item android:id ="@+id/navTask" android:icon ="@drawable/nav_task" android:title ="Tasks" /> </group > </menu >
headerLayout: res/layout/nav_header, 在 NavigationView 中显示头部布局: 显示用户的头像,用户名,邮箱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <RelativeLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="180dp" android:padding ="10dp" android:background ="@color/teal_200" > <de.hdodenhof.circleimageview.CircleImageView android:id ="@+id/iconImage" android:layout_width ="70dp" android:layout_height ="70dp" android:src ="@drawable/nav_icon" android:layout_centerInParent ="true" /> <TextView android:id ="@+id/mailText" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_alignParentBottom ="true" android:text ="tonygreendev@gmail.com" android:textColor ="#FFF" android:textSize ="14sp" /> <TextView android:id ="@+id/userText" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_above ="@id/mailText" android:text ="Tony Green" android:textColor ="#FFF" android:textSize ="14sp" /> </RelativeLayout >
完善基本的交互
1 2 3 4 5 navView.setCheckedItem(R.id.navCall) navView.setNavigationItemSelectedListener { drawerLayout.closeDrawers() true }
悬浮维度 仿立体面的操作
加入到布局中
Tips:
layout_gravity 属性与滑动菜单一致, 按照人们的习惯, 如果语言从左往右, 则 start 表示左, end 表示右
elevation: 表示悬浮高度, 高度越高投影效果越明显
1 2 3 4 5 6 7 8 <com.google.android.material.floatingactionbutton.FloatingActionButton android:id ="@+id/fab" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="bottom|end" android:layout_margin ="16dp" android:src ="@drawable/ic_done" android:elevation ="8dp" />
添加点击事件
floatingActionButton.setOnClickListener{ Toast.makeText(this , "FAB clicked" , Toast.LENGTH_SHORT).show() }
Snackbar 交互性提示 在提示中加入了可交互式的按钮, 用户能与提示进行交互
参数需要传入一个 view 对象, 只要是当前界面布局的任意一个 View 都可以,因为 Snackbar 会使用这个 View 自动查找最外层的布局,用于展示提示信息
setAction 设置动作, 进行交互
1 2 3 4 5 6 7 floatingActionButton.setOnClickListener{ view -> Snackbar.make(view, "Data Updated" , Snackbar.LENGTH_SHORT) .setAction("Undo" ) { Toast.makeText(this , "Data backed" , Toast.LENGTH_LONG).show() } .show() }
通过对 View 进行扩展, 简化 Snackbar 的使用
CoordinatorLayout 悬浮维度的布局 加强版的 FrameLayout, 可以监听其所有子控件的各种事件,并自动帮助我们做出最为合理的响应
例如 Snackbar 提示信息将悬浮按钮遮挡住了,那么 CoordinatorLayout 能监听到 Snackbar的弹出事件,会自动将内部的 FloatingActionButton 向上偏移,从而确保不会被 Snackbar 遮挡
但是 Snackbar 所传入的触发 View 必须要是 CoordinatorLayout 的子组件奥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <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/colorPrimary" android:theme ="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme ="@style/ThemeOverlay.AppCompat.Light" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id ="@+id/fab" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="bottom|end" android:layout_margin ="16dp" android:src ="@drawable/ic_done" android:elevation ="8dp" /> </androidx.coordinatorlayout.widget.CoordinatorLayout >
卡片式布局 MaterialCardView 基于 FrameLayout, 常用于 RecyclerView 的数据项布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <com.google.android.material.card.MaterialCardView 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 ="wrap_content" android:layout_margin ="5dp" app:cardCornerRadius ="4dp" > <LinearLayout android:orientation ="vertical" android:layout_width ="match_parent" android:layout_height ="wrap_content" > <ImageView android:id ="@+id/fruitImage" android:layout_width ="match_parent" android:layout_height ="100dp" android:scaleType ="centerCrop" /> <TextView android:id ="@+id/fruitName" 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 >
使用合适的布局去布局多个卡片
1 2 3 4 5 val layoutManager = GridLayoutManager(this , 2 )val recyclerView: RecyclerView = findViewById(R.id.recyclerView)recyclerView.layoutManager = layoutManager val adapter = FruitAdapter(this , fruitList)recyclerView.adapter = adapter
AppBarLayout 解决父布局遮挡 Toolbar 问题
将 Toolbar 嵌入到 AppBarLayout 中
给 RecyclerView 指定一个布局行为 (如果存在父组件, 则布局行为要上移)
1 2 3 4 5 6 <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" />
这个布局属性能够实现与 AppBar 的交互, 如 RecyclerLayout 滚动时, AppBarLayout 能接收到滚动事件并进行响应, 这时候可以通过对 Toolbar 添加 layout_scrollFlags 属性实现控制, 可以混合使用, 如 scroll|enterAlways
scroll: 当 RecyclerView 向上滚动的时候,Toolbar 会跟着一起向上滚动并实现隐藏
enterAlways: 表示当 RecyclerView 向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示
snap: ;表示当 Toolbar 还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示
1 2 3 4 5 6 7 8 9 <androidx.appcompat.widget.Toolbar android:id ="@+id/toolbar" android:layout_width ="match_parent" android:layout_height ="?attr/actionBarSize" android:background ="@color/colorPrimary" android:theme ="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme ="@style/ThemeOverlay.AppCompat.Light" app:layout_scrollFlags ="scroll|enterAlways|snap" />
下拉刷新 SwipeRefreshLayou
把想要实现下拉刷新功能的控件放置到 SwipeRefreshLayout 中,就可以迅速让这个控件支持下拉刷新
1 2 3 4 5 6 7 8 9 10 11 12 13 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id ="@+id/swipeRefresh" 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 >
初始化
1 2 3 4 5 val swipeRefresh: SwipeRefreshLayout by lazy { val sr: SwipeRefreshLayout = findViewById(R.id.swipeRefresh) sr.setColorSchemeResources(R.color.blue) sr }
刷新事件处理
swipeRefresh.setOnRefreshListener { refreshFruits(adapter) }
刷新动画关闭
1 2 3 4 if (todoItemSwipeRefresh.isRefreshing) { todoItemSwipeRefresh.isRefreshing = false }
可折叠式标题栏 CollapsingToolbarLayout 是基于 Toolbar 的布局, 能够让 Toolbar 的效果变得更加丰富, 但是使用其的基础很高, 需要作为 AppBarLayout 的子布局(解决标题栏遮挡问题), 而 AppBarLayout 又是 CoordinateLayout 的子布局(监听子组件实现处理)
QuickStart
布局: CoordinateLayout + AppBarLayout + CollapsingToolbarLayout + Toolbar 实现标题栏布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <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" android:fitsSystemWindows ="true" 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 >
配置浮动按钮 可在合适的位置进行配置,并且会随着折叠消失
1 2 3 4 5 6 7 <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" />