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:name="ShowAppBar"
android:defaultValue="true"/>
<!--添加指向daily页面的动作-->
<action
android:id="@+id/action_navigation_discover_to_navigation_daily"
app:destination="@id/navigation_daily" />
</keep_state_fragment>
<keep_state_fragment
android:id="@+id/navigation_podcast"
android:name="com.tobery.personalmusic.ui.home.podcast.PodcastFragment"
tools:layout="@layout/fragment_podcast"
android:label="podcast"/>
<keep_state_fragment
android:id="@+id/navigation_mine"
android:name="com.tobery.personalmusic.ui.home.mine.MineFragment"
tools:layout="@layout/fragment_mine"
android:label="mine">
</keep_state_fragment>
<keep_state_fragment
android:id="@+id/navigation_follow"
android:name="com.tobery.personalmusic.ui.home.follow.FollowFragment"
tools:layout="@layout/fragment_follow"
android:label="follow"/>
<keep_state_fragment
android:id="@+id/navigation_daily"
android:label="daily"
android:name="com.tobery.personalmusic.ui.daily.DailySongsFragment"
tools:layout="@layout/fragment_daily_songs">
<argument android:name="ShowAppBar"
android:defaultValue="false"/>
</keep_state_fragment>
<keep_state_fragment
android:id="@+id/navigation_play_list"
android:label="playList"
android:name="com.tobery.personalmusic.ui.daily.PlaySongsListFragment"
tools:layout="@layout/fragment_play_songs_list">
<argument android:name="ShowAppBar"
android:defaultValue="false"/>
</keep_state_fragment>
<activity
android:id="@+id/CurrentSongActivity"
android:name="com.tobery.personalmusic.ui.song.CurrentSongPlayActivity"
android:label="CurrentSongActivity"/>
</navigation>
新增navigation导航跳转页面
修改DiscoverFragment.java
:
binding.imgRecommend.setOnClickListener(view -> {
if (ClickUtil.enableClick()){
//跳转到日推页面
Navigation.findNavController(view).navigate(R.id.navigation_daily);
//startActivity(new Intent(getActivity(), DailySongsActivity.class));
}
});