Android ActionBar 深度定制:实现仿微信顶部导航与菜单效果
在Android应用开发中,顶部导航栏(ActionBar)是用户交互的核心区域之一。许多主流应用(如微信)在ActionBar中集成了搜索框、动态展开的菜单以及自定义的溢出(Overflow)按钮。本文将详细探讨如何通过定制Android原生的ActionBar和OptionsMenu,实现类似微信的顶部导航与菜单效果。
一、 定义菜单资源
首先,我们需要在 res/menu 目录下创建菜单布局文件。这里我们将搜索框配置为可折叠的ActionView,并将其他功能项放入溢出菜单中。
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_search"
android:actionViewClass="android.widget.SearchView"
android:icon="@drawable/ic_search"
android:showAsAction="ifRoom|collapseActionView"
android:title="@string/title_search" />
<item
android:id="@+id/action_add_friend"
android:icon="@drawable/ic_add_friend"
android:title="@string/title_add_friend" />
<item
android:id="@+id/action_group_chat"
android:icon="@drawable/ic_group_chat"
android:title="@string/title_group_chat" />
<item
android:id="@+id/action_scan"
android:icon="@drawable/ic_scan"
android:title="@string/title_scan" />
<item
android:id="@+id/action_payment"
android:icon="@drawable/ic_payment"
android:title="@string/title_payment" />
</menu>
二、 配置字符串资源
为了支持多语言和便于维护,将所有UI文本提取到 res/values/strings.xml 中。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">CustomActionBarDemo</string>
<string name="title_search">搜索</string>
<string name="title_add_friend">添加朋友</string>
<string name="title_group_chat">发起群聊</string>
<string name="title_scan">扫一扫</string>
<string name="title_payment">收付款</string>
</resources>
三、 定制主题与溢出按钮图标
系统默认的Overflow图标(通常是三个点)可能不符合应用的整体设计规范。我们可以通过修改应用主题来替换它。在 res/values/styles.xml 中定义自定义样式:
<resources>
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<item name="android:actionOverflowButtonStyle">@style/CustomOverflowButton</item>
</style>
<style name="CustomOverflowButton" parent="android:Widget.Holo.ActionButton.Overflow">
<item name="android:src">@drawable/ic_custom_overflow</item>
</style>
</resources>
确保在 AndroidManifest.xml 的 <application> 或 <activity> 标签中应用该主题:android:theme="@style/AppTheme"。
四、 核心逻辑与反射机制应用
在早期的Android设备或某些定制ROM中,如果设备存在物理菜单键,系统会自动隐藏ActionBar上的Overflow图标。此外,默认情况下,展开的溢出菜单只显示文本而不显示图标。为了解决这两个问题,我们需要借助Java反射机制来强制显示图标。
以下是重构后的Activity核心代码:
package com.example.actionbardemo;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewConfiguration;
import android.view.Window;
import android.widget.Toast;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class WeChatStyleActionBarActivity extends Activity {
private static final String TAG = "ActionBarDemo";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 隐藏ActionBar左侧的默认应用图标
if (getActionBar() != null) {
getActionBar().setDisplayShowHomeEnabled(false);
}
// 强制显示Overflow图标
forceShowOverflowIcon();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
String message = "";
if (itemId == R.id.action_search) {
message = "触发搜索功能";
} else if (itemId == R.id.action_add_friend) {
message = "添加朋友";
} else if (itemId == R.id.action_group_chat) {
message = "发起群聊";
} else if (itemId == R.id.action_scan) {
message = "打开扫一扫";
} else if (itemId == R.id.action_payment) {
message = "打开收付款";
}
if (!message.isEmpty()) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* 通过反射强制显示Overflow菜单图标,解决物理菜单键导致的图标隐藏问题
*/
private void forceShowOverflowIcon() {
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if (menuKeyField != null) {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
Log.e(TAG, "强制显示Overflow图标失败", e);
}
}
/**
* 菜单展开时触发,通过反射让MenuItem显示图标
*/
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {
if ("MenuBuilder".equals(menu.getClass().getSimpleName())) {
try {
Method setOptionalIconsVisible = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
setOptionalIconsVisible.setAccessible(true);
setOptionalIconsVisible.invoke(menu, true);
} catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
Log.e(TAG, "设置菜单图标可见性失败", e);
}
}
}
return super.onMenuOpened(featureId, menu);
}
}
五、 代码解析
- SearchView 集成:在XML中通过
android:actionViewClass属性直接绑定SearchView,配合collapseActionView实现点击展开、再次点击收起的交互效果。 - 物理菜单键兼容:
forceShowOverflowIcon()方法修改了ViewConfiguration中的sHasPermanentMenuKey静态变量,欺骗系统认为当前设备没有物理菜单键,从而始终在ActionBar右侧渲染Overflow按钮。 - 菜单图标渲染:Android系统为了保持Holo/Material设计的一致性,默认在Overflow下拉列表中隐藏图标。
onMenuOpened()拦截了菜单打开的事件,利用反射调用MenuBuilder的隐藏方法setOptionalIconsVisible,强制渲染图标。
