【Android】DrawerLayout+NavigationView实现侧滑菜单页面

news2024/9/20 18:50:14

【Android】DrawerLayout+NavigationView实现侧滑菜单页面

在 Android 开发中,侧滑菜单是一个非常常见的用户界面模式,它能够在屏幕的一侧显示一个导航菜单,允许用户通过滑动手势或点击按钮来访问不同的应用功能。本文将介绍如何使用 DrawerLayoutNavigationView 来实现这一功能。

效果展示

先来看看这两个组件的结合可以实现什么效果吧!

be5e2d4c9f20291e1dd5 -middle-original

基本概念

1. DrawerLayout

DrawerLayout 是 Android 中一种强大的布局容器,它为开发者提供了在主屏幕一侧或两侧放置可滑动视图的能力,这种滑动视图通常被称为抽屉(Drawer)。抽屉通常用于放置导航菜单,但也可以用于其他类型的内容,如工具面板或设置菜单。

主要特性和功能
  • 多层视图结构: DrawerLayout 通常包含两个或多个子视图,其中一个是主要内容区域(通常是应用的主要 UI),另一个或多个是抽屉内容。抽屉视图可以位于屏幕的左侧、右侧或两侧。
  • 抽屉操作: 抽屉可以通过手势(例如从屏幕边缘滑动)或编程方式打开和关闭。开发者可以通过监听抽屉状态的变化(如打开、关闭、拖动)来执行特定的操作。
  • 可自定义的抽屉: 开发者可以完全自定义抽屉的内容和外观,包括其宽度、背景颜色、内容布局等。
  • 兼容性和响应性: DrawerLayout 设计时考虑到了不同的屏幕尺寸和方向,因此可以适应不同的设备和屏幕状态,如平板设备和横屏模式。

2. NavigationView

NavigationView 是一个专门用于在 DrawerLayout 中实现导航菜单的视图组件。它简化了侧边菜单的创建,并提供了丰富的功能和自定义选项。

主要特性和功能
  • 菜单项配置: NavigationView 使用菜单资源文件(XML 格式)来定义其内容。通过菜单资源文件,开发者可以轻松地配置菜单项的 ID、标题、图标等属性,并可以将菜单项分组,支持单选和多选行为。
  • 菜单头部视图: NavigationView 可以包含一个头部视图,通常用于显示用户信息或应用的标识。头部视图的布局可以完全自定义,支持包括头像、用户名等元素。
  • 易于集成: NavigationView 可以轻松地与 DrawerLayout 结合使用。通过设置 android:layout_gravity="start"android:layout_gravity="end" 属性,开发者可以将导航视图放置在抽屉的左侧或右侧。
  • 样式和主题支持: NavigationView 支持 Material Design 风格,并提供了多种自定义选项,如背景颜色、选中项的颜色等。开发者可以通过自定义样式和主题来改变其外观,使其与应用的整体风格保持一致。
  • 事件处理: NavigationView 提供了一个简单的回调接口 NavigationView.OnNavigationItemSelectedListener,用于处理菜单项的选择事件。开发者可以在这个接口中定义菜单项点击时的行为,如切换 Fragment、启动新 Activity 等。

实现步骤

1. 布局文件

首先,我们需要在布局文件中定义 DrawerLayoutNavigationView 的结构:

<?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="match_parent"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/tool_bar"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <androidx.drawerlayout.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:openDrawer="start"
        tools:context=".MainActivity">

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/fg_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <com.google.android.material.navigation.NavigationView
            android:id="@+id/nv_view"
            android:layout_width="300dp"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:focusable="true"
            app:headerLayout="@layout/drawer_header_layout"
            app:menu="@menu/menu_botton"/>
    </androidx.drawerlayout.widget.DrawerLayout>
</LinearLayout>

在这个布局文件中,DrawerLayout 包含一个 FragmentContainerView 作为主内容区域,以及一个 NavigationView 作为侧滑菜单。

image-20240731192900347

NavigationView的这两项是需要我们自己去配置的,分别对应上下两部分:

image-20240731192949814

有一点需要注意和强调一下Fragment的容器布局一定要放在上面,否则会被菜单栏盖过导致无法修改Fragment。

2. 配置菜单资源文件与头部资源文件

我们使用一个菜单资源文件来定义 NavigationView 中的菜单项:

<?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/menu_home"
            android:title="Home"/>
        <item
            android:id="@+id/menu_find"
            android:title="Find"/>
        <item
            android:id="@+id/menu_mine"
            android:title="Mine"/>
    </group>
</menu>

在这个菜单文件中,我们定义了三个菜单项:HomeFindMine

image-20240731193102792

头部资源文件如下:

<?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:orientation="vertical"
    android:gravity="center">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="#CD1010"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="user"
        android:textSize="30dp"/>

</LinearLayout>

image-20240731193136290

大概长这样(如上)。

3. Fragment 实现

每个菜单项对应一个 Fragment,这里是一个简单的 FragmentExample 的实现:

public class FragmentExample extends Fragment {
    private TextView mTextView;

    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    private String mParam1;
    private String mParam2;

    public FragmentExample() {
        // Required empty public constructor
    }

    public static FragmentExample newInstance(String param1, String param2) {
        FragmentExample fragment = new FragmentExample();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_example, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mTextView = view.findViewById(R.id.tv_frag);
        mTextView.setText(mParam1);
    }
}

这个 Fragment 类通过 newInstance 方法接受参数,并在 onViewCreated 方法中设置文本视图的内容。

4. 主活动代码

在主活动中,我们需要设置 DrawerLayoutNavigationView,并处理导航项的点击事件:

public class MainActivity extends AppCompatActivity {
    private DrawerLayout mDrawerLayout;
    private NavigationView mNavigationView;
    private Toolbar mToolbar;
    private ActionBarDrawerToggle mDrawerToggle;

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

        mDrawerLayout = findViewById(R.id.drawer_layout);
        mNavigationView = findViewById(R.id.nv_view);
        mToolbar = findViewById(R.id.tool_bar);

        setSupportActionBar(mToolbar);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.drawer_open, R.string.drawer_close);
        mDrawerLayout.addDrawerListener(mDrawerToggle);
        mDrawerToggle.syncState();

        setHomePage();

        mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
                int itemId = menuItem.getItemId();
                if (itemId == R.id.menu_home) {
                    FragmentExample homeFragment = FragmentExample.newInstance("home","");
                    getSupportFragmentManager().beginTransaction()
                            .replace(R.id.fg_container, homeFragment)
                            .commit();
                } else if (itemId == R.id.menu_find) {
                    FragmentExample findFragment = FragmentExample.newInstance("find","");
                    getSupportFragmentManager().beginTransaction()
                            .replace(R.id.fg_container, findFragment)
                            .commit();
                } else if (itemId == R.id.menu_mine) {
                    FragmentExample mineFragment = FragmentExample.newInstance("mine","");
                    getSupportFragmentManager().beginTransaction()
                            .replace(R.id.fg_container, mineFragment)
                            .commit();
                }

                if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                    mDrawerLayout.close();
                }

                return true;
            }
        });
    }

    private void setHomePage() {
        FragmentExample homeFragment = FragmentExample.newInstance("home","");
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.fg_container, homeFragment)
                .commit();
        mNavigationView.setCheckedItem(R.id.menu_home);
    }
}

在这个代码中,我们初始化了 DrawerLayoutNavigationViewToolbar,并设置了一个 ActionBarDrawerToggle 来处理菜单的开关。我们还设置了一个 NavigationItemSelectedListener 来处理菜单项的点击事件,根据选中的菜单项替换 FragmentContainerView 中的内容。

1. 类与成员变量

public class MainActivity extends AppCompatActivity {
    private DrawerLayout mDrawerLayout;
    private NavigationView mNavigationView;
    private Toolbar mToolbar;
    private ActionBarDrawerToggle mDrawerToggle;
}
  • 成员变量 mDrawerLayoutmNavigationViewmToolbar 分别代表抽屉布局、导航视图和工具栏。
  • mDrawerToggle 是一个 ActionBarDrawerToggle 对象,用于同步抽屉的开闭状态与工具栏的图标变化。

2. onCreate 方法

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

    mDrawerLayout = findViewById(R.id.drawer_layout);
    mNavigationView = findViewById(R.id.nv_view);
    mToolbar = findViewById(R.id.tool_bar);

    setSupportActionBar(mToolbar);
    mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.drawer_open, R.string.drawer_close);
    mDrawerLayout.addDrawerListener(mDrawerToggle);
    mDrawerToggle.syncState();

    setHomePage();

    mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
            int itemId = menuItem.getItemId();
            if (itemId == R.id.menu_home) {
                FragmentExample homeFragment = FragmentExample.newInstance("home","");
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.fg_container, homeFragment)
                        .commit();
            } else if (itemId == R.id.menu_find) {
                FragmentExample findFragment = FragmentExample.newInstance("find","");
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.fg_container, findFragment)
                        .commit();
            } else if (itemId == R.id.menu_mine) {
                FragmentExample mineFragment = FragmentExample.newInstance("mine","");
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.fg_container, mineFragment)
                        .commit();
            }

            if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                mDrawerLayout.close();
            }

            return true;
        }
    });
}
主要功能和实现步骤:
  1. 视图绑定:
    • mDrawerLayout = findViewById(R.id.drawer_layout); 通过 ID 获取 DrawerLayout 的引用。
    • mNavigationView = findViewById(R.id.nv_view); 获取 NavigationView 的引用。
    • mToolbar = findViewById(R.id.tool_bar); 获取 Toolbar 的引用。
  2. 设置工具栏:
    • setSupportActionBar(mToolbar); 设置 Toolbar 作为活动的操作栏。
  3. ActionBarDrawerToggle:
    • mDrawerToggle = new ActionBarDrawerToggle(...) 创建 ActionBarDrawerToggle 对象,用于控制 DrawerLayout 的开闭以及工具栏图标的同步。
    • mDrawerLayout.addDrawerListener(mDrawerToggle); 添加 mDrawerToggle 作为 DrawerLayout 的监听器。
    • mDrawerToggle.syncState(); 同步 DrawerLayout 的状态与 ActionBar
  4. 设置首页:
    • 调用 setHomePage() 方法,将首页设置为默认页面。
  5. 处理导航项选择事件:
    • 使用 mNavigationView.setNavigationItemSelectedListener(...) 设置导航项的选择监听器。
    • 根据选中的菜单项 ID (itemId),加载相应的 Fragment 到 FragmentContainerViewR.id.fg_container)。
    • 关闭抽屉:如果抽屉是打开状态,则在选择后关闭它。

3. setHomePage 方法

private void setHomePage() {
    FragmentExample homeFragment = FragmentExample.newInstance("home","");
    getSupportFragmentManager().beginTransaction()
            .replace(R.id.fg_container, homeFragment)
            .commit();
    mNavigationView.setCheckedItem(R.id.menu_home);
}
功能和实现:
  • 创建一个 FragmentExample 实例作为首页的 Fragment,并传入参数 “home”。
  • 使用 FragmentManager 进行 Fragment 的事务操作,replace 方法用新创建的 Fragment 替换 R.id.fg_container 中的内容。
  • 调用 commit() 提交事务。
  • 设置 mNavigationView 的当前选中项为 R.id.menu_home,表示用户当前在首页。

注意事项

因为我们创建出的项目默认存在toolbar,我们需要取消默认的toolbar。

想实现这个也很简单,在theme中重新创建一个即可。

image-20240731194118520

    <style name="MyNoActionBar" parent="Base.Theme.DrawerLayoutTest">
        <item name="android:windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

android:windowActionBar: 这个属性决定了是否显示 ActionBar(即工具栏)。

false: 设置为 false 表示在这个主题下,ActionBar 将被隐藏。这通常用于不需要显示 ActionBar 的界面,例如使用自定义工具栏或没有工具栏的界面。

windowNoTitle: 这个属性决定了窗口是否显示标题。

true: 设置为 true 表示窗口将没有标题。通常与 ActionBar 的显示状态结合使用,用于去掉默认的标题栏或 ActionBar。

结语

通过以上步骤,我们实现了一个完整的侧滑菜单界面。DrawerLayoutNavigationView 提供了强大的功能,允许我们轻松地实现复杂的导航布局。

本文参考:

DrawerLayout | Android Developers

不需要显示 ActionBar 的界面,例如使用自定义工具栏或没有工具栏的界面。

windowNoTitle: 这个属性决定了窗口是否显示标题。

true: 设置为 true 表示窗口将没有标题。通常与 ActionBar 的显示状态结合使用,用于去掉默认的标题栏或 ActionBar。

结语

通过以上步骤,我们实现了一个完整的侧滑菜单界面。DrawerLayoutNavigationView 提供了强大的功能,允许我们轻松地实现复杂的导航布局。

本文参考:

DrawerLayout | Android Developers

NavigationView | Android Developers

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1965295.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网页UI设计工具全攻略:九大精选

如果担心不知道如何进行网站 UI 设计、设计网站和编辑网页技术程序&#xff0c;很多人会选择快速方便的 Wix 建设。然而&#xff0c;如果你想建立一个最合适的网站&#xff0c;使用一个功能强大、资源丰富的网站 UI 设计工具仍然是您的最佳选择。网站设计中的 UI 设计不同于一般…

你是否知道Vue的data两种不同定义区别呢?

在做vue项目的时候&#xff0c;虽然vue3出来了一段时间了&#xff0c;vue2已经官方宣布不再维护了&#xff0c;然而我们有些旧项目原来是用的vue2的&#xff0c;那么用了那么久的vue2&#xff0c;不知道你是否有注意到&#xff0c;vue2我们往往会在根文件定义了一个对象形式的d…

类似redmine的项目管理系统有哪些?10款软件测评

国内外主流的10款类似redmine项目管理系统对比&#xff1a;PingCode、Worktile、TAPD、OpenProj、禅道&#xff08;ZenTao&#xff09;、Teambition、JIRA、Asana、Basecamp、Wrike。 在项目管理领域&#xff0c;选择一个既能满足需求又易于操作的工具是每个团队都面临的挑战。…

利用SOLIDWORKS CAD 2024新功能 提高团队工作效率

随着科技的不断发展&#xff0c;CAD&#xff08;计算机辅助设计&#xff09;软件在各行业中的应用越来越广泛&#xff0c;尤其在机械、汽车、航空航天、电子设备等领域。SOLIDWORKS作为一款功能强大的CAD软件&#xff0c;一直在不断更新和优化&#xff0c;以适应不断变化的市场…

【区块链】控制台的配置、操作及常用命令②

常用命令-账户管理 常用命令-区块信息 在控制台中编译部署智能合约 启动节点 在fisco目录下 bash nodes/127.0.0.1/start_all.sh启动控制台 cd ~/fisco/console && bash start.sh部署合约 deploy HelloWorldtransaction hash: 交易的哈希值 contract address&#x…

plugin ‘ROS2‘: loading...error CoppeliaSim和ROS2插件问题

问题 装了24年最新版本ROS2 Jazzy但是仿真软件打开出bug&#xff0c;怎么办&#xff1f; 等支持的出来&#xff0c;完全可以。但是&#xff0c;如果需要用&#xff0c;那调整一下即可。 CoppeliaSim&#xff08;V-Rep&#xff09;和ROS2的使用说明_coppeliasim编译-CSDN博客…

【网络】HTTP协议——应用层协议、URL、HTTP协议格式、HTTP的方法、HTTP的状态码、HTTP常见Header

文章目录 Linux网络1. 应用层2. HTTP协议2.1 URL2.2 urlencode和urdecode2.3 HTTP协议格式2.4 HTTP的方法2.5 HTTP的状态码2.6 HTTP常见Header Linux网络 1. 应用层 应用层是 OSI 七层模型或 TCP/IP 四层模型中的最高层&#xff0c;它直接为用户的应用程序提供服务。 应用层的…

MySQL数据库-SQL编程

一、触发器 1.触发器简介 触发器&#xff08;trigger&#xff09;是一个特殊的存储过程&#xff0c;它的执行不是由程序调用&#xff0c;也不是手工启动&#xff0c;而是由事件来触发&#xff0c;比如当对一个表进行操作&#xff08; insert&#xff0c;delete&#xff0c; u…

【C++标准库】模拟实现string类

模拟实现string类 一.命名空间与类成员变量二.构造函数1.无参&#xff08;默认&#xff09;构造2.有参构造3.兼容无参和有参构造4.拷贝构造1.传统写法2.现代写法 三.析构函数四.string类对象的容量操作1.size2.capacity3.clear4.empty5.reserve6.resize 五.string类对象的访问及…

傅里叶变换与FFT应用

一、傅里叶变换 1.1 变换 我们先给例子&#xff0c;假设在直角坐标系上有A(2,1),B(1,2);数和图之间存在的关系&#xff0c;称作变换&#xff1b;在图上我们想找对角线C&#xff0c;通过计算我们就知道C(3,3)&#xff1b;我们知道&#xff0c;在坐标系上有单位向量&#xff0c;…

Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 之 [ 选择并训练模型 ] [ 模型微调 ] | 3/3(含分析过程)

Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 之 [ 选择并训练模型 ] [ 模型微调 ] | 3/3&#xff08;含分析过程&#xff09; 目录 Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 之 [ 选择并训练模型 ] [ 模型微调 ] | 3/3&#xff08;含分析…

Vue使用flex将图片并排居中且左对齐

先看效果&#xff1a; 在看代码 <template><div class"outer"><div class"inner"><div classeach_image v-for"(item,index) in image_list" :key"index"><img class"image_class" src"./…

培训孵化公司必备ERP的系统,跟卖和铺货以及订单物流发货打包

培训孵化必备的 ERP 系统&#xff0c;贴牌定制独立部署&#xff0c;跟卖铺货&#xff0c;物流对接。 说说新手与培训孵化学员如何使用 ERP&#xff01; 1. ERP 系统对于新手来说非常简单且容易操作&#xff0c;上面的跟卖功能很全面。比如铺货方面&#xff0c;可以采集 1688、…

发现SOLIDWORKS设计活页夹

您有没有遇到过将模型文件转交给同事时丢失附件的文档信息的&#xff1f;您有没有遇到过您的业务同事使用您的模型时仍然搞不清模型和业务项目之间的关系&#xff1f; 在纸制图纸的“旧时代”中&#xff0c;会有一整套信息&#xff08;文档或者表格&#xff09;与模型和图纸一…

电路原理--基础电路工具

1.正弦信号激励下的动态电路分析法-----频域相量法 课本第六章269页。 2.阻抗 3.滤波器简单理解 先介绍下滤波&#xff0c;芯片和元器件在相互工作的时候&#xff0c;会相互影响&#xff0c;在线路上产生寄生电阻或者寄生电容&#xff0c;这种现象叫耦合&#xff0c;耦合会带…

2024 巴黎奥运会:科技点亮体育盛会

一、引言 巴黎奥运会作为全球瞩目的体育盛事&#xff0c;承载着深厚的历史与文化底蕴。它不仅是运动员们展现卓越竞技能力的舞台&#xff0c;也是科技成果大放异彩的平台。科技在巴黎奥运会中的地位举足轻重&#xff0c;为赛事的各个方面带来了革新与突破。 从赛事的筹备到运…

Threejs中导入GLTF模型克隆后合并

很多场景中会需要同一个模型很多次&#xff0c;但是如果多次加载同一个模型会占用很高的带宽&#xff0c;导致加载很慢&#xff0c;因此就需要使用clone&#xff0c;也就是加载一个模型后&#xff0c;其他需要使用的地方使用clone的方式复制出多个同样的模型&#xff0c;再改变…

静态路由与默认路由和实验以及ARP工作原理

目录 1.静态路由和默认路由 1.1 静态路由 1.2 默认路由 1.3 主要区别总结 2.实验 2.1 实验 2.1.1 实验top 2.1.2 实验要求 2.2 实验配置 2.2.1 ip信息配置 2.2.2 配置静态 2.2.3配置默认 2.3 实验结果查看 3.为什么第一个ping会显示丢包&#xff1f; 3.1 ARP 工…

15.3 zookeeper实现分布式锁

1. 简介 2. 代码演示 2.1 客户端连接类 package com.ruoyi.common.zookeeper;import com.ruoyi.common.exception.UtilException; import