Android Studio中如何构建带图标的侧边栏及下拉子项?
嘿,我刚好之前折腾过类似的侧边栏需求,给你两个实用的解决思路,应该能完美匹配你要的「带图标+下拉子项」效果!
方案一:用官方NavigationView(适配Android Studio模板布局)
Android Studio默认的侧边栏模板大多用的是NavigationView,它本身就支持下拉子项+图标,不用额外造轮子,步骤超简单:
- 创建侧边栏菜单文件
在res/menu目录下新建nav_menu.xml,通过<item>嵌套<menu>实现下拉子项,每个项都可以用android:icon设置图标:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 带下拉子项的主菜单 --> <item android:id="@+id/nav_main_1" android:title="我的项目" android:icon="@drawable/ic_folder"> <menu> <item android:id="@+id/nav_sub_1" android:title="未完成" android:icon="@drawable/ic_unfinished"/> <item android:id="@+id/nav_sub_2" android:title="已完成" android:icon="@drawable/ic_finished"/> </menu> </item> <!-- 普通单级菜单 --> <item android:id="@+id/nav_main_2" android:title="设置" android:icon="@drawable/ic_setting"/> </menu>
- 在布局中引用菜单
找到模板里的NavigationView,添加app:menu属性关联刚才的菜单文件:
<com.google.android.material.navigation.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/nav_menu"/>
- 处理点击事件
NavigationView会自动处理主项的展开/折叠,你只需要监听点击逻辑:
NavigationView navView = findViewById(R.id.nav_view); navView.setNavigationItemSelectedListener(item -> { switch (item.getItemId()) { case R.id.nav_main_1: // 主菜单点击逻辑(可选,比如手动控制展开状态) break; case R.id.nav_sub_1: // 子项1的业务逻辑 break; case R.id.nav_sub_2: // 子项2的业务逻辑 break; } // 点击后关闭侧边栏 DrawerLayout drawer = findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; });
这个方案的优势是代码少、符合Material Design规范,完全适配模板布局,不用自己写展开折叠逻辑。
方案二:自定义ExpandableListView(高度定制需求)
如果觉得NavigationView的样式不够灵活,比如要调整图标大小、添加额外控件,就用ExpandableListView自定义布局:
- 准备主项和子项的布局文件
- 主项布局
list_group_item.xml(包含图标和标题):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:orientation="horizontal" android:gravity="center_vertical"> <ImageView android:id="@+id/group_icon" android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_group"/> <TextView android:id="@+id/group_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:textSize="16sp"/> </LinearLayout>
- 子项布局
list_child_item.xml(同样带图标):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="12dp" android:paddingStart="32dp" android:orientation="horizontal" android:gravity="center_vertical"> <ImageView android:id="@+id/child_icon" android:layout_width="20dp" android:layout_height="20dp" android:src="@drawable/ic_child"/> <TextView android:id="@+id/child_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:textSize="14sp"/> </LinearLayout>
- 自定义适配器加载布局
继承BaseExpandableListAdapter,在getGroupView和getChildView中绑定图标和文字:
public class CustomExpandAdapter extends BaseExpandableListAdapter { private Context mContext; private List<String> mGroupTitles; private Map<String, List<ChildItem>> mChildData; // 子项数据模型:存图标ID和标题 public static class ChildItem { int iconRes; String title; public ChildItem(int iconRes, String title) { this.iconRes = iconRes; this.title = title; } } public CustomExpandAdapter(Context context, List<String> groupTitles, Map<String, List<ChildItem>> childData) { mContext = context; mGroupTitles = groupTitles; mChildData = childData; } @Override public int getGroupCount() { return mGroupTitles.size(); } @Override public int getChildrenCount(int groupPosition) { return mChildData.get(mGroupTitles.get(groupPosition)).size(); } @Override public Object getGroup(int groupPosition) { return mGroupTitles.get(groupPosition); } @Override public Object getChild(int groupPosition, int childPosition) { return mChildData.get(mGroupTitles.get(groupPosition)).get(childPosition); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { String groupTitle = (String) getGroup(groupPosition); if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.list_group_item, parent, false); } TextView tvTitle = convertView.findViewById(R.id.group_title); ImageView ivIcon = convertView.findViewById(R.id.group_icon); tvTitle.setText(groupTitle); // 可选:根据展开状态切换图标(比如展开用向下箭头,折叠用向右箭头) ivIcon.setImageResource(isExpanded ? R.drawable.ic_expand : R.drawable.ic_collapse); return convertView; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { ChildItem childItem = (ChildItem) getChild(groupPosition, childPosition); if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.list_child_item, parent, false); } TextView tvTitle = convertView.findViewById(R.id.child_title); ImageView ivIcon = convertView.findViewById(R.id.child_icon); tvTitle.setText(childItem.title); ivIcon.setImageResource(childItem.iconRes); return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } }
- 在Activity中初始化列表
ExpandableListView expandListView = findViewById(R.id.expand_list); // 准备测试数据 List<String> groupTitles = new ArrayList<>(); groupTitles.add("我的收藏"); groupTitles.add("最近浏览"); Map<String, List<CustomExpandAdapter.ChildItem>> childData = new HashMap<>(); List<CustomExpandAdapter.ChildItem> collectList = new ArrayList<>(); collectList.add(new CustomExpandAdapter.ChildItem(R.drawable.ic_article, "技术文章")); collectList.add(new CustomExpandAdapter.ChildItem(R.drawable.ic_video, "教学视频")); childData.put(groupTitles.get(0), collectList); // 设置适配器 CustomExpandAdapter adapter = new CustomExpandAdapter(this, groupTitles, childData); expandListView.setAdapter(adapter); // 监听子项点击 expandListView.setOnChildClickListener((parent, v, groupPos, childPos, id) -> { CustomExpandAdapter.ChildItem item = (CustomExpandAdapter.ChildItem) adapter.getChild(groupPos, childPos); // 子项点击逻辑 return true; });
这个方案适合需要高度自定义UI的场景,比如要给图标加圆角、添加 badge 标记等。
总结
- 如果只是基础需求,优先用方案一的
NavigationView,省心省力还符合官方规范; - 如果要做特殊样式,再用方案二的自定义
ExpandableListView。
内容的提问来源于stack exchange,提问作者Ridlo
相关产品推荐
相关产品推荐

