Android 抽屉布局 + 底部Tab + 自定义 ToolBar
Android App 市面上流行的布局往往是如下图所示的
好的接下来就为大家展现代码如何实现其效果!
首先需要导入的是design库,可以是:androidx 的引入,或support库的引入
//androidx 引入design库的方式
implementation 'com.google.android.material:material:1.0.0-rc01'
//或者support 引入design库的方式
//implementation ‘com.android.support:design:28.0.0’
首先MainActivity 的 Layout 布局:
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:id="@+id/drawer_layout"
tools:context=".ui.MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/toolbar"/>
<include layout="@layout/container"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navigation_bottom"
style="@style/Widget.Design.BottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_alignParentBottom="true"
android:background="@color/viewBackground"
app:itemIconTint="@drawable/nav_item_color_selector"
app:itemTextColor="@drawable/nav_item_color_selector"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
app:menu="@menu/navigation_bottom"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:layout_width="wrap_content"
android:layout_gravity="start"
android:id="@+id/nav_view"
app:headerLayout="@layout/nav_header"
app:menu="@menu/menu_drawer"
android:layout_height="match_parent"
/>
</androidx.drawerlayout.widget.DrawerLayout>
最外层用DrawerLayout 给包起来 ,然后在用CoordinatorLayout(Design 库中的一个可伸展的布局,内部嵌套一个toolbar 和 Fragment 需展示的container 页面),然后在该布局中的底部添加一个BottomNavigationView 的底部多Tab的控件(由google 官方自己封装好的一个多Tab选择切换控件),然后是NavigationView(抽屉布局的具体layout)
toolbar.xml 如下:
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" 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="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
外部由AppBarLayout 包含,内部是Toolbar,然后是toolbar上的按钮和标题。
以及Toolbar顶部的按钮自定义style.xml :
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
</style>
<style name="DrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="color">@color/White</item>
<item name="android:textColor">@color/White</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
container.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</FrameLayout>
就是一个帧布局,用来做Fragment 显示内容的载体
底部BottomNavigationView 的menu 布局,navigation_bottom.xml :需将其放入menu 目录下的
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/home" android:icon="@drawable/ic_home_black_24dp" android:title="@string/home"/>
<item android:id="@+id/wechat" android:icon="@drawable/ic_wechat" android:title="@string/wechat"/>
<item android:id="@+id/project" android:icon="@drawable/ic_project" android:title="@string/project"/>
<item android:id="@+id/navigation" android:icon="@drawable/ic_navigation" android:title="@string/navigation"/>
<item android:id="@+id/knowledge_tree" android:icon="@drawable/ic_dashboard_black_24dp" android:title="@string/konwledge_tree"/>
</menu>
展示其中一个item的drawable ,如ic_wechat,xml ,是svg 矢量图
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp"
android:viewportHeight="1024" android:viewportWidth="1024" android:width="24dp">
<path android:fillColor="#FF000000"
android:pathData="M860.61,794.82c62.71,-46.85 98.37,-114.12 98.37,-186.61 0,-124.93 -107.69,-228.43 -246.7,-244.21C694.08,220.94 555.25,113.23 388.67,113.23c-179.53,0 -325.59,126.53 -325.59,282.05 0,83.86 41.8,161.66 115.2,215.37 -4.01,17.58 -18.1,54.56 -33.92,88.1 -5.65,11.97 -2.6,26.24 7.44,34.86 5.48,4.71 12.31,7.1 19.17,7.1 5.7,0 11.42,-1.66 16.41,-5.01 43.1,-28.99 97.12,-61.83 114.85,-68.41 28.04,6.66 57.09,10.04 86.45,10.04 4.74,0 9.49,-0.11 14.23,-0.28 34.39,102.41 143.48,177.46 272.39,177.46 25.08,0 49.91,-2.83 73.89,-8.43 15.94,6.41 60.87,33.81 96.59,57.84 4.98,3.35 10.71,5.01 16.41,5.01 6.86,0 13.69,-2.39 19.17,-7.1 10.04,-8.63 13.08,-22.9 7.43,-34.87C876.13,840.12 864.62,810.35 860.61,794.82zM388.67,618.51c-26.48,0 -52.62,-3.24 -77.7,-9.64 -8.7,-2.22 -20.74,-5.28 -78.95,28.73 8.8,-29.17 10.53,-54.35 -9.79,-67.96 -63.76,-42.72 -100.32,-106.27 -100.32,-174.35 0,-123.09 119.67,-223.22 266.76,-223.22 133.45,0 244.9,81.28 263.93,190.63 -145.85,10.07 -261,116.33 -261,245.52 0,3.44 0.1,6.86 0.27,10.26C390.81,618.49 389.74,618.51 388.67,618.51zM815.87,754.43c-17.15,11.49 -18.16,30.71 -12.75,53.2 -31.21,-17.53 -45.37,-21.32 -53.83,-21.32 -3.47,0 -5.98,0.64 -8.32,1.24 -21.19,5.41 -43.28,8.14 -65.67,8.14 -123.99,0 -224.85,-84.1 -224.85,-187.48 0,-103.37 100.87,-187.47 224.85,-187.47s224.85,84.1 224.85,187.47C900.14,665.25 869.42,718.54 815.87,754.43z"/>
<path android:fillColor="#FF000000"
android:pathData="M246.39,322.56a39.96,36.19 0,1 0,81.78 0,39.96 36.19,0 1,0 -81.78,0Z"/>
<path android:fillColor="#FF000000"
android:pathData="M458.88,322.56a39.96,36.19 0,1 0,81.78 0,39.96 36.19,0 1,0 -81.78,0Z"/>
<path android:fillColor="#FF000000"
android:pathData="M566.16,530.37a32.07,29.04 0,1 0,65.63 0,32.07 29.04,0 1,0 -65.63,0Z"/>
<path android:fillColor="#FF000000"
android:pathData="M729.48,530.37a32.07,29.04 0,1 0,65.63 0,32.07 29.04,0 1,0 -65.63,0Z"/>
</vector>
以及点击底部Item的selector,nav_item_color_selector.xml :
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorPrimary" android:state_checked="true"/>
<item android:color="@color/textColorSecondary" android:state_checked="false"/>
</selector>
最后是抽屉布局中nav_header.xml : 就是上图中抽屉布局上部的图片区域
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="190dp"
android:background="?attr/colorPrimaryDark"
android:gravity="bottom"
android:orientation="vertical"
android:padding="16dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<ImageView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
tools:text="名字"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>
</FrameLayout>
以及其底部的item菜单,menu_drawer.xml ,也是需要放入menu 目录中的:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/favorites" android:title="@string/favorites"></item>
<item android:id="@+id/todo" android:title="@string/todo"></item>
<item android:id="@+id/night_mode" android:title="@string/night_mode"></item>
<item android:id="@+id/setting" android:title="@string/setting"></item>
<item android:id="@+id/logout" android:title="@string/logout"></item>
<item android:id="@+id/about" android:title="@string/about"></item>
</menu>
还有顶部的扫一扫按钮和查询按钮的布局文件,也是一个menu 文件,需放入menu目录下,menu_activity_main.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/scan"
android:icon="@drawable/ic_scan"
android:title="@string/scan"
app:showAsAction="always|collapseActionView">
</item>
<item android:id="@+id/search"
android:icon="@drawable/ic_search"
android:title="@string/search"
app:showAsAction="always|collapseActionView">
</item>
</menu>
好了,讲完了其layout,接下来讲解其MainActivity.kt 其代码逻辑 , 下述代码由kotlin,所写,java的会日后再补上。MainActivity.kt 代码如下:
package com.cx.icxwanandroid.ui
import android.content.Context
import android.content.Intent
import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import com.cx.icxwanandroid.R
import com.cx.icxwanandroid.base.BaseActivity
import com.cx.icxwanandroid.ui.fragment.*
import com.cx.icxwanandroid.utils.ToastUtils
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.bottomnavigation.LabelVisibilityMode.LABEL_VISIBILITY_LABELED
import com.google.android.material.navigation.NavigationView
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.toolbar.*
import java.lang.Exception
class MainActivity : BaseActivity() {
private var mCurrentFragment : Fragment ?= null
private var index : Int = 0
private var fragmentTag : String ?= null
//每个Fragment 的Tag
private val fragmentNames = arrayOf(
HomeFragment::class.java.name,
WeChatFragment::class.java.name,
ProjectFragment::class.java.name,
NavigationFragment::class.java.name,
KnowledgeTreeFragment::class.java.name
)
//底部的tab的Title
private val bottomTitles = arrayOf(
R.string.home , R.string.wechat , R.string.project , R.string.navigation , R.string.konwledge_tree
)
override var layoutId: Int = R.layout.activity_main
override fun initData() {
//设置toolBar
setSupportActionBar(toolbar)
//初始化抽屉布局
initDrawerLayout()
//初始化抽屉布局中的item 的点击事件
initNav()
//设置底部Tab的点击事件
initNavBottom()
//设置底部的切换Tab 导致切换Fragment的逻辑
bottomNav()
}
override fun subscibeUi() {
}
//初始化整个抽屉的布局,并且设置抽屉按钮开启和关闭的点击事件
private fun initDrawerLayout(){
drawer_layout.run {
val toggle = ActionBarDrawerToggle(
this@MainActivity,
this,
toolbar,
R.string.app_name,
R.string.app_name
)
addDrawerListener(toggle)
toggle.syncState()
}
}
//设置抽屉item 的点击事件
private fun initNav(){
val navListener = NavigationView.OnNavigationItemSelectedListener{
when(it.itemId){
R.id.favorites -> {
}
R.id.todo -> {
}
R.id.night_mode -> {
}
R.id.setting -> {
}
R.id.logout -> {
}
R.id.about -> {
}
}
drawer_layout.closeDrawer(GravityCompat.START)
true
}
nav_view.run {
setNavigationItemSelectedListener(navListener)
}
}
//初始化底部Tab的点击事件
private fun initNavBottom(){
val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener {
item ->
when(item.itemId){
R.id.home -> index = 0
R.id.wechat -> index = 1
R.id.project -> index = 2
R.id.navigation -> index = 3
R.id.knowledge_tree -> index = 4
}
bottomNav()
true
}
navigation_bottom.run {
labelVisibilityMode = LABEL_VISIBILITY_LABELED
setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
}
}
private fun bottomNav(){
toolbar.title = getString(
if(index == 0){
R.string.app_name
}else{
bottomTitles[index]
}
)
//设置toolBar 的Title颜色
toolbar.setTitleTextColor(Color.parseColor("#ffffff"))
fragmentTag = fragmentNames[index]
val fragment = getFragmentByTag(fragmentTag!!)
showFragment(mCurrentFragment , fragment , fragmentTag!!)
}
//根据FragmentTag 得到 Fragment
private fun getFragmentByTag(name : String) : Fragment{
var fragment = supportFragmentManager.findFragmentByTag(name)
if(fragment != null){
return fragment
} else {
try {
// 得到Fragment的实例
fragment = Class.forName(name).newInstance() as Fragment
}catch (e : Exception){
fragment = HomeFragment()
}
}
return fragment!!
}
//显示Fragment
private fun showFragment(from : Fragment ? , to : Fragment , tag : String){
val transaction = supportFragmentManager.beginTransaction()
if(from == null){
if(to.isAdded){
transaction.show(to)
}else{
transaction.add(R.id.container , to , tag)
}
}else{
if(to.isAdded){
transaction.hide(from).show(to)
}else{
transaction.hide(from).add(R.id.container , to , tag)
}
}
transaction.commit()
mCurrentFragment = to
}
//初始化顶部的菜单,有扫一扫和查询按钮
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_activity_main , menu)
return super.onCreateOptionsMenu(menu)
}
//顶部扫一扫和查询的点击事件
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.scan -> {
ToastUtils.show("scan")
return true
}
R.id.search -> {
ToastUtils.show("search")
return true
}
}
return super.onOptionsItemSelected(item)
}
}
BaseActivity.kt 代码如下:
package com.cx.icxwanandroid.base
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
abstract class BaseActivity : AppCompatActivity() {
protected abstract var layoutId : Int
// protected var multipleStatusView : MultipleStatusView ?= null
protected abstract fun initData()
protected abstract fun subscibeUi()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layoutId)
initData()
subscibeUi()
}
}
BaseFragment 代码如下:
package com.cx.icxwanandroid.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
abstract class BaseFragment : Fragment(){
protected abstract var layoutId : Int
protected abstract fun initData()
protected abstract fun subscribeUi()
open fun onRetry(){
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(layoutId , container , false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initData()
subscribeUi()
}
}
好啦,写到这里就结束了,基础的页面框架,Android 抽屉布局 + 底部多 Tab切换 + 自定义ToolBar 的UI 效果就已经搭好啦,读者可以参考代码去实现哈!