ToolBar
Toolbar的强大之处在于,它不仅继承了 ActionBar的所有功能,而且灵活性很高,可以配合其他控件来完成一些Material Design的效果。
任何一个新建的项目,默认都是会显示ActionBar。
可以打开AndroidManifest看一下:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MaterialDesign"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
可以看到,这里使用android:theme属性指定了一个MaterialDesign的主题,它是在res/values/themes里定义的:
xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.MaterialDesign" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.MaterialDesign" parent="Base.Theme.MaterialDesign" />
</resources>
我们现在要使用ToolBar,所以需要指定一个不带ActionBar的主题,我们可以使用Theme.Material3.Light.NoActionBar
xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.MaterialDesign" parent="Theme.Material3.Light.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.MaterialDesign" parent="Base.Theme.MaterialDesign" />
</resources>
为什么这里要指定一个xmlns:app的命名空间呢?这是由于Material Design 是在Android 5.0系统中才出现的,而很多的Material属性在5.0之前的系统中并不存在,那么为了能够兼容之前的老系统,我们就不能使用android:attribute这样的写法了,而是应该使用app:attribute。
为了能让Toolbar单独使用深色主题,这里我们使用android:theme属性,将Toolbar的主题指定成了ThemeOverlay.AppCompat.Dark.ActionBar。
使用了app:popupTheme属性单独将弹出的菜单项指定成了淡色主题。之所以使用app:popupTheme,是因为popupTheme这个属性是在Android 5.0系统中新增的,我们使用app:popupTheme的话就可以兼容Android 5.0以下的系统了。
下来修改MainActivity中的代码:
java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
}
先得到ToolBar的实例,然后调用setSupportActionBar()方法并将ToolBar实例传入,就可以了。
我们可以修改标题上的文字:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="你好"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MaterialDesign"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
通过修改**android:label="你好"**去实现
当然还可以添加一些别的东西使得ToolBar更丰富。
滑动菜单
DrawerLayout
所谓的滑动菜单就是将一些菜单选项隐藏起来,而不是放置在主屏幕上,然后可以通过滑动的方式将菜单显示出来。这种方式既节省了屏幕空间,又实现了非常好的动画效果,是 Material Design中推荐的做法。
我们借助DrawerLayout可以更简单地实现滑动菜单
DrawerLayout是一个布局,在布局中允许放入两个直接子控件,第一个子控件是主屏幕中显示的内容,第二个子控件是滑动菜单中显示的内容。
我们可以对activity_main进行修改:
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
xmlns:app="http://schemas.android.com/apk/res-auto"
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="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</FrameLayout>
<!-- 侧滑菜单 -->
<LinearLayout
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:orientation="vertical"
android:background="#FFF">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is menu"
android:textSize="30sp"
android:gravity="center"/>
</LinearLayout>
</androidx.drawerlayout.widget.DrawerLayout>
就实现了简单的滑动菜单。
但是现在的滑动菜单并没有什么提示信息,我们可以给ToolBar左边加入一个导航按钮,点击按钮也会将菜单展示出来:
java
public class MainActivity extends AppCompatActivity {
private DrawerLayout mDrawLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mDrawLayout = (DrawerLayout) findViewById(R.id.main);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
mDrawLayout.openDrawer(GravityCompat.START);
}
return super.onOptionsItemSelected(item);
}
}
先得到DrawerLayout和ActionBar实例,接着使用setDisplayHomeAsUpEnabled()让按钮显示出来,接着使用setHomeAsUpIndicator()设置一个导航按钮图标。
在onOptionsItemSelected()方法中对HomeAsUp按钮的点击事件进行处理,HomeAsUp的id永远都是android.R.id.home,然后调用openDrawer()将滑动菜单展示出来。
NavigationView
首先需要引入库:
xml
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
implementation("com.android.support:design:28.0.0")
implementation("de.hdodenhof:circleimageview:2.1.0")
}
下来创建res->menu->nav_menu_xml,修改代码:
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/nav_call"
android:icon="@drawable/profile"
android:title="Call"/>
<item
android:id="@+id/nav_friends"
android:icon="@drawable/people"
android:title="Friends"/>
<item
android:id="@+id/nav_location"
android:icon="@drawable/location"
android:title="Location"/>
<item
android:id="@+id/nav_mail"
android:icon="@drawable/events"
android:title="Mail"/>
<item
android:id="@+id/nav_task"
android:icon="@drawable/settings"
android:title="Tasks"/>
</group>
</menu>
下来在layout文件夹下创建nav_header_xml:
xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:padding="10dp"
android:background="?attr/colorPrimary">
<TextView
android:id="@+id/mail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="xiaoduyyy@gmail.com"
android:textColor="#FFF"
android:textSize="14sp"/>
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/mail"
android:text="Xiaodu"
android:textColor="#FFF"
android:textSize="14sp"/>
</RelativeLayout>
修改activity_main中的代码:
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
xmlns:app="http://schemas.android.com/apk/res-auto"
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="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</FrameLayout>
<!-- 侧滑菜单 -->
<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"
app:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header"/>
</androidx.drawerlayout.widget.DrawerLayout>
我们将之前的TextView换成了NavigationView,用app:menu和app:headerLayout将刚才准备好的menu和headerLayout设置进去。
下来处理菜单的点击事件:
java
public class MainActivity extends AppCompatActivity {
private DrawerLayout mDrawLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mDrawLayout = (DrawerLayout) findViewById(R.id.main);
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
navigationView.setCheckedItem(R.id.nav_call);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
mDrawLayout.closeDrawers();
return true;
}
});
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
mDrawLayout.openDrawer(GravityCompat.START);
}
return super.onOptionsItemSelected(item);
}
}
悬浮按钮和可交互提示
FloatingActionButton
这个控件可以帮助我们较轻松地实现悬浮按钮的效果。
首先准备一个图标ic_done.png放在drawable下,修改activity_main中的代码:
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
xmlns:app="http://schemas.android.com/apk/res-auto"
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="?attr/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"/>
</FrameLayout>
<!-- 侧滑菜单 -->
<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"
app:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header"/>
</androidx.drawerlayout.widget.DrawerLayout>
还可以设置悬浮高度:
xml
<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"
app:elevation="8dp"/>
高度值越大,投影范围越大,投影效果越淡
下来我们处理一下它的点击事件:
java
public class MainActivity extends AppCompatActivity {
private DrawerLayout mDrawLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mDrawLayout = (DrawerLayout) findViewById(R.id.main);
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
navigationView.setCheckedItem(R.id.nav_call);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
mDrawLayout.closeDrawers();
return true;
}
});
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "FAB clicked", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
mDrawLayout.openDrawer(GravityCompat.START);
}
return super.onOptionsItemSelected(item);
}
}
Snackbar
Snackbar也是提示工具,不过和Toast不同,Toast主要告诉用户当前发生了什么事,而Snackbar进行了扩展,允许提示中加入一个可交互按钮,可以执行额外的操作。
修改MainActivity中的代码:
java
public class MainActivity extends AppCompatActivity {
private DrawerLayout mDrawLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mDrawLayout = (DrawerLayout) findViewById(R.id.main);
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
navigationView.setCheckedItem(R.id.nav_call);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
mDrawLayout.closeDrawers();
return true;
}
});
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "Data deleted", Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
}
}).show();
}
});
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
mDrawLayout.openDrawer(GravityCompat.START);
}
return super.onOptionsItemSelected(item);
}
}
调用了Snackbar的make()方法创建一个Snackbar对象,make()方法第一个参数传入一个View,Snackbar会使用这个View自动查找最外层布局,用于展示Snackbar。第二个参数是Snackbar中显示的内容,第三个参数是时长。
调用了setAction()设置一个动作,我们这里只设置弹出提示,这最后调用show()让Snackbar显示出来。
但是这个Snackbar将悬浮按钮遮挡住了,我们可以借助CoordinatorLayout去解决问题。
CoordinatorLayout
CoordinatorLayout可以说是加强版的FrameLayout,它可以监听所有子控件的各种事件,自动做出最合理的影响。
我们把activity_main中的FrameLayout替换成CoordinatorLayout就可以了:
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 主内容区域 -->
<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="?attr/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"
app:elevation="8dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!-- 侧滑菜单 -->
<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"
app:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header"/>
</androidx.drawerlayout.widget.DrawerLayout>
已经到底啦!!