从零自学Andriod(二):BottomNavigationView的应用与问题
上一篇介绍了Res资源管理目录的大概结构,相信大部分人还是会搞不明白这其中的关系。没事,接下来我们就要结合实际代码来理解了。在上一篇我创建了带有底部导航菜单的项目,可以看到运行出来的效果已经有个App的基本框架了,起到关键作用的就是底部的导航菜单了。实现导航菜单的方式有很多种,我这只说说App里自带的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
创建好了之后是这样,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菜单在没有选中的时候换了个图片~
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还有很多其他的属性值可以修改,有需要的可以查找资料。最后看一下我修改后的效果,这篇就到这里啦。
本文地址:https://blog.csdn.net/freebazzi/article/details/108710319
上一篇: EL表达式介绍
下一篇: 魏延去世后,蜀汉除姜维外还有哪些大将呢?