欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

从零自学Andriod(二):BottomNavigationView的应用与问题

程序员文章站 2022-07-05 21:37:27
上一篇介绍了Res资源管理目录的大概结构,相信大部分人还是会搞不明白这其中的关系。没事,接下来我们就要结合实际代码来理解了。在上一篇我创建了带有底部导航菜单的项目,可以看到运行出来的效果已经有个App的基本框架了,起到关键作用的就是底部的导航菜单了。实现导航菜单的方式有很多种,我这只说说这自带的BottomNavigationView。直接看代码,打开Java>com.example.myapp>MainActivity,有一个被重写的onCreate方法。在方法中可以看到上一篇讲过的读...

上一篇介绍了Res资源管理目录的大概结构,相信大部分人还是会搞不明白这其中的关系。没事,接下来我们就要结合实际代码来理解了。在上一篇我创建了带有底部导航菜单的项目,可以看到运行出来的效果已经有个App的基本框架了,起到关键作用的就是底部的导航菜单了。实现导航菜单的方式有很多种,我这只说说App里自带的BottomNavigationView。

从零自学Andriod(二):BottomNavigationView的应用与问题

一、BottomNavigationView

直接看代码,打开Java>com.example.myapp>MainActivity,这里要注意一下,使用BottomNavigationView需要import相关依赖,如果选择了创建带BottomNavigationView的项目,那么这个也会默认import。

import com.google.android.material.bottomnavigation.BottomNavigationView;

有一个被重写的onCreate方法。在方法中可以看到上一篇讲过的读取资源内容的方式:R.layout.activity_main和R.id.nav_view以及其他的R.id.xxx。

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//设置页面布局
        BottomNavigationView navView = findViewById(R.id.nav_view); //找到导航菜单组件
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
        NavigationUI.setupWithNavController(navView, navController);
    }

super.onCreate(savedInstanceState);就不说了大家都懂,看第二句:

setContentView(R.layout.activity_main);

我的理解是将res/layout/activity_main.html这个布局文件设置为整个app的主布局。这个文件下有两个子节点,代表底部的导航菜单(BottomNavigationView)和中间可显示的内容(fragment)。这些看属性名就挺好理解的,给节点设置id就可以通过R.id.xx的方式获取,而其他的也能看懂就是控件位置大小之类的。

<?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"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

在BottomNavigationView中比较重要的属性是app:menu="@menu/bottom_nav_menu",这句话是将底部导航与菜单项关联起来。菜单项的xml文件中写了3个item,它们分别就是文章开头那张图上的3个菜单按钮home、dashboard和notifications。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home" />

    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/ic_dashboard_black_24dp"
        android:title="@string/title_dashboard" />

    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_notifications_black_24dp"
        android:title="@string/title_notifications" />

</menu>
android:icon = 菜单的icon,指向了一个图片型的资源文件
android:title = 菜单的标题,指向了一个string型的资源文件

这两个资源文件就不细说了,打开就能看明白。

到这一步导航菜单的UI其实就已经出来了,但要实现导航的功能,就要回到activity_main.xml的第二个节点fragment。我对这个fragment的理解是容器,类似于html的frameset,替换容器的内容从而实现导航的效果。而将内容页面与布局关联起来的属性是app:navGraph="@navigation/mobile_navigation" ,其中通过app:startDestination="@+id/navigation_home"来设置打开App时默认显示的fragment,每个fragment中的andriod:name都指向其Java实现,存放在/Java/ui/的目录下。

<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <fragment
        android:id="@+id/navigation_home"
        android:name="com.example.myapp.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/navigation_dashboard"
        android:name="com.example.myapp.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="com.example.myapp.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />
</navigation>

以home举例,HomeFragment继承了fragment类,并在onCreateView中关联继承自ViewModel的HomeViewModel。然后给fragment_home布局中的文本标签TextView赋值。

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        homeViewModel =
                ViewModelProviders.of(this).get(HomeViewModel.class);
        View root = inflater.inflate(R.layout.fragment_home, container, false);
        final TextView textView = root.findViewById(R.id.text_home);
        homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                textView.setText(s);
            }
        });
        return root;
    }

 赋值的内容是HomeViewModel的构造函数中setValue的值。

    public HomeViewModel() {
        mText = new MutableLiveData<>();
        mText.setValue("This is home fragment");
    }

不知道为什么,我刚新建的一个项目会提示ViewModelProviders已弃用,之前建的项目就不会。查找了一下资料,我们需要将ViewModelProviders.of()改成new ViewModelProvider()的形式。

homeViewModel = new ViewModelProvider(this).get(HomeViewModel.class);

到这里BottomNavigationView组件的基本使用方法已经解读完了,第一次看可能会觉得有点绕,只要把这几个xml的关系梳理梳理就清楚了。

二、NavigationUI

回头看MainActivity时,会发现有几句代码好像没有说到,我的理解是NavigationUI将页面切换与App Bar关联起来了,也就是用来实现切换菜单时App Bar的标题会同时跟着变动的效果。后续查了一下资料,果不其然NavigationUI是用来管理页面与App Bar的。

        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
        NavigationUI.setupWithNavController(navView, navController);

三、应用与问题解决

App虽然已经为我们生成好了主要的功能,但想要变成自己的东西还是需要一番调整的,在改动的过程中我遇到了一些问题并将它们记录了下来:

Q:当航菜单有3个项以上时,默认不显示未选中项的文字。

A1:在引用导航控件的xml(activity_main.xml)文件里增加属性:app:labelVisibilityMode="labeled"

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu"
        app:labelVisibilityMode="labeled"/>

A2:在activity的构造函数中(onCreate)设置:

navView.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED);

这需要

import com.google.android.material.bottomnavigation.LabelVisibilityMode;

Q:NavigationUI与override的OnNavigationItemSelectedListener监听冲突。

A:原本想实现点击菜单更换图片的效果,就重写了菜单的OnNavigationItemSelectedListener。却发现事件监听一直没有触发,控制台信息没有输出,断点也进不去。尝试了多种方式未果,怀疑问题出在NavigationUI上,遂将NavigationUI那几句代码注释,果然成功了。于是跳到 NavigationUI.setupWithNavController(navView, navController)这个方法去看,果然里面是重写了监听的。如果真的有重写的需要,就在setupWithNavController里面修改吧。

    public static void setupWithNavController(
            @NonNull final BottomNavigationView bottomNavigationView,
            @NonNull final NavController navController) {
        bottomNavigationView.setOnNavigationItemSelectedListener(
                new BottomNavigationView.OnNavigationItemSelectedListener() {
                    @Override
                    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                        return onNavDestinationSelected(item, navController);
                    }
                });
        final WeakReference<BottomNavigationView> weakReference =
                new WeakReference<>(bottomNavigationView);
        navController.addOnDestinationChangedListener(
                new NavController.OnDestinationChangedListener() {
                    @Override
                    public void onDestinationChanged(@NonNull NavController controller,
                            @NonNull NavDestination destination, @Nullable Bundle arguments) {
                        BottomNavigationView view = weakReference.get();
                        if (view == null) {
                            navController.removeOnDestinationChangedListener(this);
                            return;
                        }
                        Menu menu = view.getMenu();
                        for (int h = 0, size = menu.size(); h < size; h++) {
                            MenuItem item = menu.getItem(h);
                            if (matchDestination(destination, item.getItemId())) {
                                item.setChecked(true);
                            }
                        }
                    }
                });
    }

Q3:如何实现点击菜单更换图片的效果。

A:上一条说了监听已经被NavigationUI重写,有没有办法在不修改setupWithNavController方法的情况下实现这个效果呢?答案是肯定的,这涉及到另一个概念选择器(selector)。不得不说,自从发现有selector后,我可太喜欢了,selector为一些动效的实现省了很多事。

具体怎么使用的呢,首先在res/drawable/下新建一个selector,我给它起名home_selector

从零自学Andriod(二):BottomNavigationView的应用与问题

从零自学Andriod(二):BottomNavigationView的应用与问题

创建好了之后是这样,selector是由若干个item子节点组成的。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

</selector>

接下来我给它加上两行代码

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_home_black_24dp" android:state_checked="true" />
    <item android:drawable="@drawable/ic_dashboard_black_24dp" android:state_checkable="false" />
</selector>

 android:state_checked="true" 与 android:state_checked="false" 很好理解:选中和未选中状态。

所以就是,选中状态显示图片@drawable/ic_home_black_24dp,未选中显示图片@drawable/ic_dashboard_black_24dp

然后将之前提到的res/menu/bottom_nav_menu.xml中的android:icon改成创建的这个选择器就OK。

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/home_selecor"
        android:title="@string/title_home" />

跑起来看看效果,home菜单在没有选中的时候换了个图片~

从零自学Andriod(二):BottomNavigationView的应用与问题从零自学Andriod(二):BottomNavigationView的应用与问题

selector还有其他很多判断用法,网上都有统计,有兴趣的可以找找看。 

Q4:为什么我换了新的Icon,显示的还是默认颜色?

A:需要在MainActivity的onCreate方法中加上navView.setItemIconTintList(null); 

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BottomNavigationView navView = findViewById(R.id.nav_view);
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        navView.setItemIconTintList(null);  //去掉不显示图片默认颜色
        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_parlour, R.id.navigation_study, R.id.navigation_kitchen,R.id.navigation_balcony,R.id.navigation_bedroom )
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
        NavigationUI.setupWithNavController(navView, navController);
    }

Q5:如果修改菜单文字颜色 

A:这也是我整了半天都没改成功的效果,查了半天资料才发现,不能直接修改菜单的文本颜色,要在App主题那边修改。找到res/values/style.xml,可以看到在style下里面有3个节点分别指向了3个颜色值(颜色值存放在res/values/color.xml中)。

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

这三个颜色值分别表示 

        <!-- 应用的主要色调,actionBar默认使用该颜色,Toolbar导航栏的底色 -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <!-- 应用的主要暗色调,statusBarColor 默认使用该颜色 -->
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <!-- 一般控件的选中效果默认采用该颜色,如 CheckBox,RadioButton,SwitchCompat,ProcessBar等-->
        <item name="colorAccent">@color/colorAccent</item>

所以想要修改菜单标题颜色就将相应的颜色值替换掉,但要注意同时也会将其他用到这个颜色值的地方一并修改。style还有很多其他的属性值可以修改,有需要的可以查找资料。最后看一下我修改后的效果,这篇就到这里啦。

从零自学Andriod(二):BottomNavigationView的应用与问题

本文地址:https://blog.csdn.net/freebazzi/article/details/108710319