Android简易音乐重构MVVM Java版-使用Navigation导航组件重构主界面及其他页面跳转(二十)

Posted 雪の星空朝酱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android简易音乐重构MVVM Java版-使用Navigation导航组件重构主界面及其他页面跳转(二十)相关的知识,希望对你有一定的参考价值。

android简易音乐重构MVVM Java版-使用Navigation导航组件重构主界面及其他页面跳转(二十)

关于

  本篇主要重构主界面,使用Navigation实现页面路由跳转,关于navigation介绍参考谷歌开发者平台
简易音乐app仅作为学习用,禁止用于商业及非法用途,如产生法律纠纷与本人无关

效果图

实现

  首先添加navigation导航组件引用:

//navigation
    nav_version = "2.5.0"
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-ui:$nav_version"

  右键点击 res 目录,然后依次选择 New > Android Resource File。此时系统会显示 New Resource File 对话框。然后在 File name 字段中输入名称,例如“nav_graph”,最后从 Resource type 下拉列表中选择 Navigation,然后点击 OK,如下图:

  后续添加页面路由,大致界面如下:

修改Activity_main.xml页面

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/home_drawer_menu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/boundary_gray"
    tools:context=".ui.home.MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <androidx.constraintlayout.widget.Group
            android:id="@+id/group_is_visibility"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:constraint_referenced_ids="bottom_nav,view_top,home_top_left_btn,search,ed_search"
            android:visibility="visible"
            />


        <View
            android:id="@+id/view_top"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_60"
            app:layout_constraintTop_toTopOf="parent"
            android:background="@color/colorPrimary"
            />

        <ImageView
            android:id="@+id/home_top_left_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_home_top_menu"
            app:layout_constraintTop_toTopOf="@id/view_top"
            app:layout_constraintBottom_toBottomOf="@id/view_top"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="@dimen/dp_12" />

        <ImageView
            android:id="@+id/search"
            android:layout_width="@dimen/dp_22"
            android:layout_height="@dimen/dp_25"
            android:layout_marginEnd="@dimen/dp_16"
            app:layout_constraintEnd_toEndOf="parent"
            android:src="@drawable/music_mike"
            app:layout_constraintTop_toTopOf="@id/view_top"
            app:layout_constraintBottom_toBottomOf="@id/view_top" />

        <EditText
            android:id="@+id/ed_search"
            android:layout_width="@dimen/dp_0"
            android:layout_height="@dimen/dp_30"
            android:layout_marginStart="@dimen/dp_16"
            android:layout_marginEnd="@dimen/dp_16"
            app:layout_constraintStart_toEndOf="@id/home_top_left_btn"
            app:layout_constraintEnd_toStartOf="@id/search"
            app:layout_constraintTop_toTopOf="@id/view_top"
            android:alpha="0.5"
            android:textColor="@color/white"
            android:paddingStart="@dimen/dp_8"
            android:paddingEnd="@dimen/dp_8"
            android:paddingTop="@dimen/dp_5"
            android:paddingBottom="@dimen/dp_5"
            app:layout_constraintBottom_toBottomOf="@id/view_top"
            android:background="@drawable/bg_edit_search_gray" />

          <!--添加homeactivity为宿主容器-->
        <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:defaultNavHost="true"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_0"
            app:layout_constraintTop_toBottomOf="@id/view_top"
            app:layout_constraintBottom_toTopOf="@id/bottom_nav"
            />

        <include layout="@layout/item_song_bottom_bar"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_0"
            android:id="@+id/song_bar"
            app:layout_constraintBottom_toTopOf="@id/bottom_nav"/>

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_nav"
            android:layout_width="match_parent"
            android:background="?android:attr/windowBackground"
            android:layout_height="@dimen/dp_60"
            app:menu="@menu/bottom_nav"
            app:itemRippleColor="@color/white"
            app:labelVisibilityMode="labeled"
            app:layout_constraintBottom_toBottomOf="parent"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
    <fragment
        android:name="com.tobery.personalmusic.ui.home.menu.DrawerMenuFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="left"
        tools:ignore="RtlHardcoded" />
</androidx.drawerlayout.widget.DrawerLayout>

修改MainActivity.java

private BottomNavigationView navigationBarView;
@SuppressLint("NonConstantResourceId")
    private void initView() 
        navigationBarView = binding.bottomNav;
        NavHostFragment navHostFragment =(NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
        NavController navController = Navigation.findNavController(this,R.id.nav_host_fragment);
        assert navHostFragment != null;
        //自定义navigator保存fragment实例
        FragmentNavigator navigator = new KeepCurrentStateFragment(
                this,
                navHostFragment.getChildFragmentManager(),
                R.id.nav_host_fragment
        );
        navController.getNavigatorProvider().addNavigator(navigator);
        navController.setGraph(R.navigation.nav_graph,getIntent().getExtras());
        NavigationUI.setupWithNavController(navigationBarView,navController);
        //取消item长按点击事件
        BottomNavigationMenuView bottomNavigationMenuView = (BottomNavigationMenuView) navigationBarView.getChildAt(0);
        int size = bottomNavigationMenuView.getChildCount();
        for (int index = 0; index < size;index++)
            bottomNavigationMenuView.getChildAt(index).setOnLongClickListener(new View.OnLongClickListener() 
                @Override
                public boolean onLongClick(View v) 
                    return true;
                
            );
        
        //监听导航事件
        navController.addOnDestinationChangedListener((navController1, navDestination, arguments) -> 
            boolean showAppBar = true;
            if (arguments != null)
                showAppBar = arguments.getBoolean("ShowAppBar",true);
            
            if (showAppBar)
                binding.groupIsVisibility.setVisibility(View.VISIBLE);
            else //隐藏首页顶部导航栏
                binding.groupIsVisibility.setVisibility(View.GONE);
            
        );
        setDrawMenu();
    

新建一个libCommon的library(一些kotlin写的工具放在这里)


  自定义navigatorKeepCurrentStateFragment

@Navigator.Name("keep_state_fragment")
class KeepCurrentStateFragment(
    private val mContext: Context,
    private val mFragmentManager: FragmentManager,
    private val mContainerId: Int
) : FragmentNavigator(mContext, mFragmentManager, mContainerId) 

    @Nullable
    override fun navigate(
        destination: Destination,
        @Nullable args: Bundle?,
        @Nullable navOptions: NavOptions?,
        @Nullable navigatorExtras: Navigator.Extras?
    ): NavDestination? 
        if (mFragmentManager.isStateSaved) 
            return null
        

        var className = destination.className
        if (className[0] == '.') 
            className = mContext.packageName + className
        
        var frag: Fragment? = mFragmentManager.findFragmentByTag(className)
        if (null == frag) 
            frag = mFragmentManager.fragmentFactory.instantiate(mContext.classLoader, className)
        
        frag.arguments = args
        val ft: FragmentTransaction = mFragmentManager.beginTransaction()
        var enterAnim = navOptions?.enterAnim ?: -1
        var exitAnim = navOptions?.exitAnim ?: -1
        var popEnterAnim = navOptions?.popEnterAnim ?: -1
        var popExitAnim = navOptions?.popExitAnim ?: -1
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) 
            enterAnim = if (enterAnim != -1) enterAnim else 0
            exitAnim = if (exitAnim != -1) exitAnim else 0
            popEnterAnim = if (popEnterAnim != -1) popEnterAnim else 0
            popExitAnim = if (popExitAnim != -1) popExitAnim else 0
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
        

        val fragments: List<Fragment> = mFragmentManager.fragments
        for (fragment in fragments) 
            ft.setMaxLifecycle(frag, Lifecycle.State.STARTED)
            ft.hide(fragment)
        
        if (!frag.isAdded) 
            ft.add(mContainerId, frag, className)
        
        ft.setMaxLifecycle(frag, Lifecycle.State.RESUMED)
        ft.show(frag)
        ft.setPrimaryNavigationFragment(frag)
        @IdRes val destId = destination.id

        val mBackStack: ArrayDeque<Int>? = try 
            val field: Field = FragmentNavigator::class.java.getDeclaredField("mBackStack")
            field.isAccessible = true
            field.get(this) as? ArrayDeque<Int>
         catch (e: Exception) 
            e.printStackTrace()
            return null
        
        val initialNavigation = mBackStack?.isEmpty() == true
        val isSingleTopReplacement = (navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack?.peekLast() == destId)
        val isAdded: Boolean = when 
            initialNavigation -> 
                true
            
            isSingleTopReplacement -> 
                if (mBackStack?.size ?: 0 > 1) 
                    mFragmentManager.popBackStack(
                        generateBackStackName(mBackStack?.size ?: 0, mBackStack?.peekLast() ?: 0),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE
                    )
                    ft.addToBackStack(generateBackStackName(mBackStack?.size ?: 0, destId))
                
                false
            
            else -> 
                ft.addToBackStack(generateBackStackName(mBackStack?.size ?: 0 + 1, destId))
                true
            
        
        if (navigatorExtras is Extras) 
            for ((key, value) in navigatorExtras.sharedElements) 
                ft.addSharedElement(key, value)
            
        
        ft.setReorderingAllowed(true)
        ft.commit()
        return if (isAdded) 
            mBackStack?.add(destId)
            destination
         else 
            null
        
    

    private fun generateBackStackName(backStackIndex: Int, destId: Int): String 
        return "$backStackIndex-$destId"
    

修改nav_graph.xml

注意nav_graph.xml里面的底部菜单的fragment的id要和menu/bottom_nav里面定义的fragment的id一致才行,不然navigation无法和BottomNavigationView联动

<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/nav_graph"
    app:startDestination="@id/navigation_discover">

    <keep_state_fragment
        android:id="@+id/navigation_discover"
        android:name="com.tobery.personalmusic.ui.home.discover.DiscoverFragment"
        tools:layout="@layout/fragment_discover"
        android:label="discover">
        <argument Android简易音乐重构MVVM Java版-新增启动动画

Android简易音乐重构MVVM Java版-新增推荐菜单及侧边栏展示

Android简易音乐重构MVVM Java版-BottomNavigationView+viewpager主界面结构

Android简易音乐重构MVVM Java版-新增推荐雷达歌单详情列表界面(十八)

Android简易音乐重构MVVM Java版-新增推荐雷达歌单详情列表界面(十八)

Android简易音乐重构MVVM Java版-新增歌曲播放界面+状态栏黑科技(十七)