Android学习笔记 Day1——利用BottomNavigationView实现底边栏

前言

先说一下心路历程,底边栏显然是很基础的一个功能,作为初学者的我在了解之前想当然地认为此功能很好实现。为此本人查阅了大量的资料,其他大佬的简书和官方API都看过。网上很多大佬写的教程往往因不够详细而导致在编译的阶段存在异常,许多问题悬而未决,本人在实现过程中大多都虎头蛇尾。但其中不乏有一些Android 4.x时代的实现方法,虽然比较简单,但是过于老旧。而直接阅读API进行coding在Build阶段或多或少地存在一些问题(还是自己太菜)。为此本人直接扒了扒AS自动生成的底边栏实现方式的代码。

添加Gradle依赖

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.navigation:navigation-fragment:2.2.2'
    implementation 'androidx.navigation:navigation-ui:2.2.2'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

本人在学习期间也看过很多大佬写过的博客,有好多忽略掉了Gradle依赖这一步。作为一个初学者,本人也为此卡了很长一段时间。此处添加的依赖,缺一不可。

添加Fragment的布局文件

fragment_home.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.HomeFragment">

    <TextView
        android:text="Notifications Fragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

此处主仅以主页Fragment的布局为例。

定义菜单布局

<?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>

此处的相应<item>的android:id应当与Navigation资源文件中对应的android:id相同,否则点击tab则无法切换相应的Fragment,原因暂时未知,欢迎各位大佬帮忙解答。

定义HomeFragment

HomeFragment.java

public class HomeFragment extends Fragment {
    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_home, container, false);
        return root;
    }
}

同上,仅以Home的Fragment为例。

添加Navigation布局文件

mobile_navigation.xml

<?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.navstudying.ui.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

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

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="com.example.navstudying.ui.NotificationFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notification" />
</navigation>

此处定义了desitination。值得注意的是,android:name属性值为destination的包路径,android:layout属性值为destination对应Fragment的布局文件文件名。

android:name 设置自定义Fragment
android:layout 设置Fragment布局文件

定义Activity布局文件

activity_main.xml

<?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">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        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>

底边栏由menu布局来定义。

app:menu 设置底边栏样式

Activity布局中添加了两个组件,BottomNavigationView和fragment。在fragment标签中应指定NavHostFragment。

android:name="androidx.navigation.fragment.NavHostFragment"

定义MainActivity

public class MainActivity extends AppCompatActivity {

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

        BottomNavigationView navView = findViewById(R.id.nav_view);
        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);
    }
}

先获取BottomNavigationView

BottomNavigationView navView = findViewById(R.id.nav_view);

随后利用AppBarConfiguration类构造底边栏,AppBarConfiguration.Builder中的参数是各个destination的id(desitination还没太搞懂,不过已经在学习了,下一个博客准备扒一扒api吃透它)。

AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
                .build();

参考API

Builder

public Builder (int... topLevelDestinationIds)
Create a new Builder with a specific set of top level destinations. The Up button will not be displayed when on these destinations.

扒了扒API,此处也可以直接将menu布局文件id作为参数,亲测有效。

Builder

public Builder (Menu topLevelMenu)
Create a new Builder using a Menu containing all top level destinations. It is expected that the menu item id of each item corresponds with a destination in your navigation graph. The Up button will not be displayed when on these destinations.

获取NavController

NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);

根据API得知,第一个参数是包含底边栏的Activity,第二个参数是View id。通过该函数我们可以获取与该View所绑定的NavController。此处被绑定的View为NavHostFragment。

findNavController

public static NavController findNavController (Activity activity, int viewId)
Find a NavController given the id of a View and its containing Activity. This is a convenience wrapper around findNavController(View).
This method will locate the NavController associated with this view. This is automatically populated for the id of a NavHost and its children

调用方法实现

NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);

参考API配置参数即可

setupActionBarWithNavController

public static void setupActionBarWithNavController (AppCompatActivity activity,
NavController navController,
AppBarConfiguration configuration)
Sets up the ActionBar returned by AppCompatActivity.getSupportActionBar() for use with a NavController.
By calling this method, the title in the action bar will automatically be updated when the destination changes (assuming there is a valid label).
The AppBarConfiguration you provide controls how the Navigation button is displayed. Call navigateUp(NavController, AppBarConfiguration) to handle the Up button.

setupActionBarWithNavController

public static void setupActionBarWithNavController (AppCompatActivity activity,
NavController navController,
AppBarConfiguration configuration)
Sets up the ActionBar returned by AppCompatActivity.getSupportActionBar() for use with a NavController.
By calling this method, the title in the action bar will automatically be updated when the destination changes (assuming there is a valid label).
The AppBarConfiguration you provide controls how the Navigation button is displayed. Call navigateUp(NavController, AppBarConfiguration) to handle the Up button.
Destinations that implement FloatingWindow will be ignored.

对运行逻辑的总结

实际上BottomNavigagtionView所实现的底边栏其本质上是利用了Toolbar的高度可定制性来实现的。首先获取BottomNavigationView组件,随后利用AppBarConfiguration配置AppBar(顾名思义)。获取NavController并配置参数,利用NavigationUI作为入口直接调用setupActionBarWithNavController和NavigationUI.setupWithNavController实现。

实现效果图

Screenshot_1592811321.png

demo地址

https://github.com/Cerstance/NavStudying

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容