前言

  • 本文是根据上海应用技术大学余艳芳老师的移动应用课程写的上课笔记。
  • 部分素材和代码没有给出(例如drawable中的文件代码),请读者自己补全或者删除,或者可以到本人github下载完整素材和代码。
  • 完整的项目在本人的GitHub仓库中,以下是相关链接:https://github.com/ufovsmba/TodayApplication

Menu

  • Android中提供的菜单有如下几种:

    • 选项菜单OptionsMenu
    • 上下文菜单ContextMenu
    • 弹出菜单PopupMenu

Options Menu

Options Menu选项菜单

image-20220404195941766

  • 使用onCreateOptionsMenu()回调方法对菜单进行初始化
  • 使用onPrepareOptionsMenu()方法动态改变选项菜单的内容

  • onOptionsItemSelected()方法用于响应菜单项的点击事件

  • 创建子菜单的步骤:
    • 重写Activity类的onCreateOptionsMenu()方法
    • 调用Menu的addSubMenu()方法添加子菜单
    • 调用SubMenu的add()方法为子菜单添加菜单项
    • 重写Activity类的onOptionsItemSelected ()方法

测试样例代码

xml文件添加menu效果图

直接通过Java代码添加menu效果图

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.gallifrey.todayapplication.menu;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.widget.Toast;

import com.gallifrey.todayapplication.R;

public class OptionsMenuActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_options_menu);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

//用java代码方式添加菜单
menu.add("options1");//默认groupId:0,order:0
menu.add(0,1,1,"options2");//四个参数分别为 groupId(分组Id),itemId(组内itemId),order(出现顺序),title
menu.add(0,2,2,"options3");
menu.add(1,2,4,"options4");
menu.add(1,1,3,"options5");
// menu.removeGroup(0); //分组方便管理
SubMenu f=menu.addSubMenu("sub_options");
f.add("sub1");
SubMenu f2=f.addSubMenu("sub1-2");
f2.add("sub2");


return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
//用xml文件添加菜单,动态添加菜单
menu.clear();//清楚之前在Java代码中添加的menu的内容,注释这两行代码可以看到Java中设置menu的效果
getMenuInflater().inflate(R.menu.options_menu,menu);
return super.onPrepareOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
String str="";
switch (item.getItemId()){
case R.id.menu_open:
str="menu_open";
break;
case R.id.menu_close:
str="menu_close";
break;
default:
str="NULL";
break;
}
Toast.makeText(this,str+" Click",Toast.LENGTH_LONG).show();

return super.onOptionsItemSelected(item);
}
}
  • 在res文件夹下创建menu目录,然后建立options_meu.xml文件
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
<?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:title="File"
android:icon="@mipmap/file"
app:showAsAction="ifRoom">
<menu>
<item android:id="@+id/menu_open"
android:icon="@mipmap/open"
android:title="open"/>
<item android:id="@+id/menu_close"
android:icon="@mipmap/close"
android:title="close"/>
<item android:icon="@mipmap/edit"
android:title="Edit">
<menu>
<item android:id="@+id/menu_cut"
android:icon="@mipmap/cut"
android:title="Cut"/>
<item android:id="@+id/menu_copy"
android:icon="@mipmap/copy"
android:title="Copy"/>
</menu>
</item>
</menu>
</item>
</menu>

ContextMenu

ContextMenu上下文菜单

  • 上下文菜单是通过调用ContextMenu接口中的方法来实现

image-20220404202328692

  • onCreateContextMenu()方法来生成ContextMenu对象
1
onCreateContextMenu(ContextMenu menu, View v,ContextMenu.ContextMenuInfo menuInfo)
  • 创建上下文菜单的步骤:
    • 通过registerForContextMenu()方法为ContextMenu分配一个View对象
    • 通过onCreateContextMenu()创建一个上下文对象

使用XML资源生成菜单项的步骤:

  • 在res目录中创建menu子目录
  • 在menu子目录中创建一个Menu Resource file(XML文件)
  • 使用XML文件的资源ID,在Activity中将XML文件中所定义的菜单元素添加到menu对象中
  • 判断资源ID,实现相应事件处理

测试样例代码

  • 上下文菜单基于View,长按显示
  • 上下文菜单中,一级菜单不显示icon图标,二级菜单才显示icon图标。(一级菜单设置了图标也没有用)

image-20220405144126837

image-20220405144016596

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.gallifrey.todayapplication.menu;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.gallifrey.todayapplication.R;

public class ContextMenuActivity extends AppCompatActivity {
private TextView mTvContext1,mTvContext2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_context_menu);
mTvContext1=findViewById(R.id.tv_context_menu1);
mTvContext2=findViewById(R.id.tv_context_menu2);

registerForContextMenu(mTvContext1);
registerForContextMenu(mTvContext2);
}


@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if(v.equals(mTvContext1)){
//java方式创建contextMenu
menu.add("context java 1");
SubMenu f=menu.addSubMenu("context java 2");
f.add("context java 21");
}else{
//xml方式创建contextMenu
getMenuInflater().inflate(R.menu.context_menu,menu);
}
}


@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
String str=" ";
switch (item.getItemId()){
case R.id.context_menu_cut:
str="context_menu_cut";
break;
case R.id.context_menu_paste:
str="context_menu_paste";
break;
default:
return super.onContextItemSelected(item);
}
Toast.makeText(this,str,Toast.LENGTH_LONG).show();
return true;
}
}
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
<?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=".menu.ContextMenuActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/tv_context_menu1"
android:text="Context1"
android:gravity="center"
android:textSize="30sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tv_context_menu1"
android:id="@+id/tv_context_menu2"
android:text="Context2"
android:layout_marginTop="39dp"
android:gravity="center"
android:textSize="30sp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
  • 在res文件夹下创建menu目录,然后建立context_meu.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:icon="@mipmap/file"
android:title="File">
</item>
<item android:icon="@mipmap/edit"
android:title="Edit">
<menu>
<item android:id="@+id/context_menu_cut"
android:title="context_cut"
android:icon="@mipmap/cut"/>
<item android:id="@+id/context_menu_paste"
android:title="context_paste"
android:icon="@mipmap/paste"/>
</menu>
</item>
</menu>

PopupMenu

PopupMenu弹出菜单

使用PopupMenu的步骤

  1. 创建PopupMenu的实例
  2. 填充实例
  3. 设置监听
  4. 展示弹出菜单

测试样例代码

  • 一级菜单不显示icon图标,二级菜单才显示icon图标。(一级菜单设置了图标也没有用)

image-20220405152041828

image-20220405152059993

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
43
44
45
46
47
48
49
50
51
52
53
package com.gallifrey.todayapplication.menu;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.Toast;

import com.gallifrey.todayapplication.R;

public class PopupMenuActivity extends AppCompatActivity {
private Button mBtnPopup0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_popup_menu);

mBtnPopup0 =findViewById(R.id.btn_popup);
mBtnPopup0.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
PopupMenu popupMenu=new PopupMenu(PopupMenuActivity.this,view);
popupMenu.getMenuInflater().inflate(R.menu.popupo_menu,popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
String str="";
switch (menuItem.getItemId()){
case R.id.popup_file:
str="popup_file";
break;
case R.id.popup_save:
str="popup_save";
break;
case R.id.popup_search:
str="popup_search";
break;
default:
return false;
}
Toast.makeText(PopupMenuActivity.this,str,Toast.LENGTH_LONG).show();
return true;
}
});
popupMenu.show();
}
});

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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=".menu.PopupMenuActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/btn_popup"
android:textSize="30sp"
android:text="弹出菜单"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
  • 在res文件夹下创建menu目录,然后建立popup_meu.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/popup_file"
android:icon="@mipmap/file"
android:title="File"/>
<item android:title="Edit"
android:icon="@mipmap/edit">
<menu>
<item android:id="@+id/popup_search"
android:title="Search"
android:icon="@mipmap/search"/>
<item android:id="@+id/popup_save"
android:title="Save"
android:icon="@mipmap/save"/>
</menu>
</item>
</menu>

Toolbar

Toolbar操作栏

  • Material Design风格的导航组件
  • 取代Actionbar(Toolbar可以出现屏幕任意位置,允许开发者更多定制,Actionbar只能出现在屏幕最上方且开发有限)
  • 为开发者预留许多可定制修改的余地:
    • 支持添加一个或多个的自定义组件
    • 设置App的Logo图标
    • 支持设置标题和子标题
    • 设置导航栏图标
    • 支持Action Menu
方法 功能描述
setTitle(int resId) 设置标题
setSubtitle(int resId) 设置子标题
setTitleTextColor(int color) 设置标题字体颜色
setSubtitleTextColor(int color) 设置子标题字体颜色
setNavigationIcon(Drawable icon) 设置导航栏的图标
setLogo(Drawable drawable) 设置Toolbar的Logo图标

Toolbar的简单应用

Toolbar的使用步骤:

  • 在styles.xml文件中对原主题进行修改(使用Toolbar,必须设置不支持Actionbar)
1
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar/>
  • 添加Toolbar组件
1
2
3
4
5
<androidx.appcompat.widget.Toolbar
android:id="@+id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" >
  • 在Java代码中设置Toolbar

Toolbar的综合应用

ToolBar可以包含导航按钮、应用的Logo、标题和子标题、若干个自定义View以及动作菜单等元素

ps:Toolbar的setTitle()方法需要在setSupportActionBar()方法之前调用,否则无效

测试样例代码

  • 在themes目录themes.xml里面设置一个NoActionBar的style(使用Toolbar,必须设置不支持ActionBar)
  • 在清单文件AndroidManifest.xml中,修改使用toolbar的activitty的themes(如果在application中设置会影响所有activity!)
1
2
3
4
5
6
7
8
9
10
11
12
13
<style name="Theme.TodayApplication.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
1
2
3
4
5
6
7
8
9
10
11
12
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TodayApplication">
<activity
android:theme="@style/Theme.TodayApplication.NoActionBar"
android:name=".toolbar.ToolbarActivity"
android:exported="false" />

设置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
<?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=".toolbar.ToolbarActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
app:layout_constraintTop_toTopOf="parent"
android:background="?colorPrimary"
app:title=""
app:titleTextColor="#f00"
app:titleTextAppearance="@style/ToolbarTextStyle"
app:navigationIcon="@mipmap/back"
app:popupTheme="@style/ToolbarPopupTheme"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="italic|bold"
android:textSize="30sp"
android:drawableLeft="@mipmap/edit"
android:drawableTint="@color/white"
android:text="新标题"
android:textColor="#FF5722"
android:layout_gravity="center"
/>

</androidx.appcompat.widget.Toolbar>
</androidx.constraintlayout.widget.ConstraintLayout>
  • 设置toolbar中菜单的布局文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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/toolbar_search"
android:title="Search"
android:icon="@mipmap/search"
android:iconTint="@color/white"
app:showAsAction="ifRoom"/>
<item android:title="Setting"
android:icon="@mipmap/setting"
app:showAsAction="always">
<menu>
<item android:id="@+id/toolbar_menu_cut"
android:icon="@mipmap/cut"
android:title="Cut"/>
<item android:id="@+id/toolbar_menu_copy"
android:icon="@mipmap/copy"
android:title="Copy"/>
</menu>
</item>
</menu>
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.gallifrey.todayapplication.toolbar;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.os.Bundle;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

import com.gallifrey.todayapplication.R;

public class ToolbarActivity extends AppCompatActivity {
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toolbar);

mToolbar=findViewById(R.id.toolbar);
//Java代码中设置标题优先级比xml中设置高
//要设置空标题,必须要在java代码中设置
//setTitle()必须要在setSupportActionBar()前面才能生效
mToolbar.setTitle("一级标题");

setSupportActionBar(mToolbar);

//监听必须在setSupportActionBar()下面才能生效
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbae_options_menu,menu);
return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
String str="";
switch (item.getItemId()){
case R.id.toolbar_search:
str="toolbar_search";
break;
case R.id.toolbar_menu_cut:
str="toolbar_menu_cut";
break;
case R.id.toolbar_menu_copy:
str="toolbar_menu_copy";
break;
default:
return super.onOptionsItemSelected(item);
}
Toast.makeText(this,str,Toast.LENGTH_LONG).show();
return true;
}
}
  • 在style.xml设置弹出时在不掩盖锚点
1
2
3
4
5
6
7
8
9
10
11
<style name="ToolbarTextStyle">
<item name="android:textSize">30sp</item>
<item name="android:textStyle">bold</item>
</style>

<style name="ToolbarPopupTheme" parent="ThemeOverlay.AppCompat.DayNight">
<item name="actionOverflowMenuStyle">@style/OverFlowMenuStyle</item>
</style>
<style name="OverFlowMenuStyle" parent="Widget.AppCompat.PopupMenu.Overflow">
<item name="overlapAnchor">false</item>
</style>

image-20220405171009654