KunMinX 专注 “业务架构”,致力消除敏捷开发过程中 “不可预期问题”。
kunminx / smooth-navigation Goto Github PK
View Code? Open in Web Editor NEW提供流畅的 Jetpack Navigation 转场体验。并解决 GitHub 上 Navigation Add Hide 修改版普遍存在的缺陷。
提供流畅的 Jetpack Navigation 转场体验。并解决 GitHub 上 Navigation Add Hide 修改版普遍存在的缺陷。
KunMinX 专注 “业务架构”,致力消除敏捷开发过程中 “不可预期问题”。
fragment之间相互重叠,而且每次切换还会重新初始化fragment
api 'com.kunminx.arch:smooth-navigation:3.9.0-beta1'
api("androidx.navigation:navigation-fragment-ktx:$nav_version") {
exclude group: 'androidx.navigation', module: "navigation-fragment"
}
Duplicate class androidx.navigation.fragment.DialogFragmentNavigator found in modules jetified-smooth-navigation-3.9.0-beta1-runtime (com.kunminx.arch:smooth-navigation:3.9.0-beta1) and navigation-fragment-2.3.5-runtime (androidx.navigation:navigation-fragment:2.3.5)
mavigation args跨模块传参很头疼。如何更好的封装?
在看了这篇文章之后,里面有一个写法,目标是和标题一样,行为应该是切换了navigation
和startDestination
。
<?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"
android:id="@+id/main_navigation"
app:startDestination="@id/home_fragment">
<action
android:id="@+id/action_login"
app:destination="@+id/login_navigation"
app:popUpTo="@id/main_navigation"
app:popUpToInclusive="true"
app:launchSingleTop="true"/>
<fragment
android:id="@+id/home_fragment"
android:name="com.example.HomeFragment" />
<navigation
android:id="@+id/login_navigation"
app:startDestination="@id/login_fragment">
<fragment
android:id="@+id/login_fragment"
android:name="com.example.LoginFragment" />
</navigation>
</navigation>
结果发生了问题,对大佬的严格深有感悟,受制于自身水平,实在没办法探索。
2022-01-18 12:22:03.157 E/AndroidRuntime: FATAL EXCEPTION: main
Process: , PID: 16247
java.lang.RuntimeException: Unable to start activity ComponentInfo{/*******.module.main.mine.bankcard.BankCardManagerActivity}: java.lang.UnsupportedOperationException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2877)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2938)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1652)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6441)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:939)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:829)
Caused by: java.lang.UnsupportedOperationException
at java.util.AbstractList.remove(AbstractList.java:161)
at androidx.navigation.fragment.FragmentNavigator.popBackStack(FragmentNavigator.java:109)
at androidx.navigation.NavController.popBackStackInternal(NavController.java:323)
at androidx.navigation.NavController.navigate(NavController.java:1059)
at androidx.navigation.NavController.navigate(NavController.java:944)
at androidx.navigation.NavController.navigate(NavController.java:877)
at androidx.navigation.NavController.navigate(NavController.java:863)
at androidx.navigation.NavController.navigate(NavController.java:851)
at **************.module.main.mine.bankcard.BankCardManagerActivity.initView(BankCardManagerActivity.kt:30)
at com.wsdydeni.baselib.base.BaseActivity.onCreate(BaseActivity.kt:46)
at android.app.Activity.performCreate(Activity.java:6782)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1123)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2825)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2938)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1652)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6441)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:939)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:829)
具体是 FragmentNavigator.java:109
boolean isSingleTopReplacement = navOptions != null && !initialNavigation && navOptions.shouldLaunchSingleTop() && (Integer)this.mBackStack.peekLast() == destId;
launchSingleTop模式跳转页面时,若启动页面时附带了新的arguments,有什么好的方案来处理因arguments变更而需执行的逻辑么?例如页面为文章详情页,启动页面时需附带文章id参数。【一般我会在onCreate时处理数据初始化的逻辑。对文章详情页的case来说就是接收argument中的文章id参数,交给VM来发起文章数据加载。】
测试场景如下:
当前栈顶:AFragment
NavAction: 由AFragment启动AFragment,附带新的arguments,launchSingleTop = true
此时满足栈顶复用条件,"复用"当前栈顶的AFragment示例(这里复用打个引号,原版Navigation实际上未复用实例)
---------方案对比 start------------
-----原版Navigation 行为-------
因创建了新实例,因此会执行Fragment onCreate -> onResume一系列生命周期方法,因此可随onCreate使用新的argument完成初始化逻辑。
-----SmoothNavigation行为-------
因生命周期未变更,除setArguments方法外的任何一个回调方法均不会被执行。
---------方案对比 end------------
在使用SmoothNavigation的情况下,除复写setArguments并处理argument变更逻辑外,有无其他的解决方案?
2021-04-25 22:42:14.737 19593-19593/com.kunminx.smoothnavigation E/AndroidRuntime: FATAL EXCEPTION: main Process: com.kunminx.smoothnavigation, PID: 19593 java.lang.IllegalArgumentException: Navigation action/destination com.kunminx.smoothnavigation:id/action_listFragment_to_detailFragment cannot be found from the current destination Destination(com.kunminx.smoothnavigation:id/detailFragment) label=DetailFragment class=com.kunminx.puremusic.ui.DetailFragment at androidx.navigation.NavController.navigate(NavController.java:938) at androidx.navigation.NavController.navigate(NavController.java:875) at androidx.navigation.NavController.navigate(NavController.java:861) at androidx.navigation.NavController.navigate(NavController.java:849) at com.kunminx.puremusic.ui.ListFragment.lambda$onCreateView$0$ListFragment(ListFragment.java:60) at com.kunminx.puremusic.ui.-$$Lambda$ListFragment$4BN03aTrC9t10SJQdwnHqZnZ3TQ.onItemClick(Unknown Source:4) at com.kunminx.puremusic.ui.base.adapter.BaseBindingAdapter.lambda$onCreateViewHolder$1$BaseBindingAdapter(BaseBindingAdapter.java:75) at com.kunminx.puremusic.ui.base.adapter.-$$Lambda$BaseBindingAdapter$8LeqcfLWURSbEo3pVSDDYsfV3jg.onClick(Unknown Source:4) at android.view.View.performClick(View.java:7184) at android.view.View.performClickInternal(View.java:7161) at android.view.View.access$3500(View.java:818) at android.view.View$PerformClick.run(View.java:27677) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7562) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
在体验demo的时候点击列表,触发了这个异常。
红米8a,Android10.
没有修改过demo的任何代码。
我点击+添加动态,然后来回添加返回滑动。点击到某一条的时候就遇到这样情况了。
fragment之间跳转画面之后不会走onPause,onStop回调,回到画面不会走onResume等方法~
能否在readme.md 文件中指出
最新是 Navigation 2.4.2,由于使用 kotlin 重写,并且改动不少,不好把 2.3.x 的改动方式平移到 2.4.x 上。
另外现在 navigation-fragment 中包含 navigation-fragment-ktx,所以不能通过 exclude 解决依赖重复问题,只能拷贝代码到项目上。
2.4.x和2.3.x 版本混用不知道会不会有坑?
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.appqueen.quizzmaster, PID: 19760
java.lang.IllegalArgumentException: Navigation action/destination com.appqueen.quizzmaster:id/action_mainFragment_to_quizzListFragment cannot be found from the current destination Destination(com.appqueen.quizzmaster:id/quizzListFragment) label=fragment_quizz_list class=com.appqueen.quizzmaster.view.poslogin.quizzlist.QuizzListFragment
at androidx.navigation.NavController.navigate(NavController.kt:1540)
at androidx.navigation.NavController.navigate(NavController.kt:1472)
at androidx.navigation.NavController.navigate(NavController.kt:1454)
at androidx.navigation.NavController.navigate(NavController.kt:1437)
at com.appqueen.quizzmaster.view.shared.main.MainFragment.onClick(MainFragment.java:257)
at android.view.View.performClick(View.java:7357)
at android.widget.TextView.performClick(TextView.java:14263)
at android.view.View.performClickInternal(View.java:7323)
at android.view.View.access$3200(View.java:849)
at android.view.View$PerformClick.run(View.java:27895)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:216)
at android.app.ActivityThread.main(ActivityThread.java:7266)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
我的解决方案.. 抛砖引玉
fun NavController.safeNavigate(action: NavDirections) {
try {
this.navigate(action)
} catch (e:Exception){
}
}
任何缺乏实证依据和因果逻辑的泛泛而谈,都可能对其他使用者造成困扰。
在发表个人见解前,请先确保自己认真阅读过源码。这是对自己、对作者、对其他读者最起码的尊重。
由FragmentA跳转到FragmentB,FragmentA生命周期为何没有任何变化?
override fun onAttach(context: Context) {
super.onAttach(context)
Log.e("生命周期","onAttach")
}
override fun onStart() {
super.onStart()
Log.e("生命周期","onStart")
}
override fun onResume() {
super.onResume()
Log.e("生命周期","onResume")
}
override fun onPause() {
super.onPause()
Log.e("生命周期","onPause")
}
override fun onStop() {
super.onStop()
Log.e("生命周期","onStop")
}
override fun onDetach() {
super.onDetach()
Log.e("生命周期","onDetach")
}
我在代码里面初始化navGraph
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment_identity) as NavHostFragment
val navController = NavHostFragment.findNavController(navHostFragment)
navController.setGraph(
if(authStatus == "1") R.navigation.nav_identity
else R.navigation.nav_identity_verify
)
设置了之后,在某个二级页面返回,白屏了
Navigation.findNavController(requireActivity(),R.id.nav_host_fragment_identity).navigateUp()
第一次设置navGrpah的日志
2022-01-21 16:12:17.797 D/Nav: --isSingleTopReplacement --- false
2022-01-21 16:12:17.797 D/Nav: --mBackStack.size:0 getFragments().size():0
2022-01-21 16:12:17.797 D/Nav: --Replace --- InitiateVerifyFragment{6b983ed} (35176420-6950-4140-97fe-c1951b65d7a9)
跳转二级页面的日志
2022-01-21 16:12:21.625 D/Nav: --isSingleTopReplacement --- false
2022-01-21 16:12:21.625 D/Nav: --mBackStack.size:1 getFragments().size():1
2022-01-21 16:12:21.625 D/Nav: --Add --- UploadVerifyFragment{9c80c27} (b504e152-121d-4bc7-84cf-efa6358bf1ca)
2022-01-21 16:12:21.626 D/Nav: --Hide --- InitiateVerifyFragment{ddb6b77} (55ca39c0-09e1-4355-b0dc-c311cb6af423 id=0
返回的时候没有日志,只隐藏了二级页面,一级页面也没显示,导致白屏了。
launchSingleTop属性跳转页面在不同的手机上有不同的 表现,某些手机会闪一下,某些机型会直接显示上一级的 页面
原因为hide隐藏的其实就是DFragment,而add添加的fragment其实也是Dfragment因此在同一个FragmentTransation中隐藏和添加同一个Fragment会有问题的,if (mBackStack.size() > 0 && mFragmentManager.getFragments().size() > 0) {
Log.d("Nav"," --Hide --Add");
ft.hide(mFragmentManager.getFragments().get(mBackStack.size() - 1));
ft.add(mContainerId, frag);
}
我修改了FragmentNavigator的navigate方法相当是launchsingletop的情况下,不将新创建的fragmet入栈:
public NavDestination navigate(@nonnull Destination destination, @nullable Bundle args,
@nullable NavOptions navOptions, @nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager, className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
Log.d("testnav", " --isSingleTopReplacement --- " + isSingleTopReplacement);
//TODO Tip 1: Solve the unexpected display of popUpToInclusive under the add hide solution
//TODO Tip 2: Increase fault tolerance to deal with incorrectly-timed jumps in the case of nested sub-fragments
Log.d("testnav", " --mBackStack.size:" + mBackStack.size()
+ " getFragments().size():" + mFragmentManager.getFragments().size());
if (mBackStack.size() > 0 && mFragmentManager.getFragments().size() > 0) {
Log.d("testnav", " --Add --- " + frag);
Fragment hideFrag = mFragmentManager.getFragments().get(mBackStack.size() - 1);
Log.d("testnav", " --Hide --- " + hideFrag);
if (isSingleTopReplacement) {
hideFrag.setArguments(args);
ft.setMaxLifecycle(hideFrag, Lifecycle.State.RESUMED);
} else {
ft.hide(hideFrag);
ft.add(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
}
} else {
Log.d("testnav", " --Replace --- " + frag);
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
}
// ft.setPrimaryNavigationFragment(frag);
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
// if (mBackStack.size() > 1) {
// // If the Fragment to be replaced is on the FragmentManager's
// // back stack, a simple replace() isn't enough so we
// // remove it from the back stack and put our replacement
// // on the back stack in its place
// mFragmentManager.popBackStack(
// generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
// FragmentManager.POP_BACK_STACK_INCLUSIVE);
// ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
// }
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras && !isSingleTopReplacement) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
2022年7月23日12:16:46的master代码。
pixel 3 Android 10,情况如标题。
java.lang.IllegalArgumentException: Navigation action/destination com.kunminx.smoothnavigation:id/action_listFragment_to_detailFragment cannot be found from the current destination Destination(com.kunminx.smoothnavigation:id/detailFragment) label=DetailFragment class=com.kunminx.puremusic.ui.DetailFragment
fragment设定app:launchSingleTop="true"时,会出现一闪而过的问题,请问下有啥优化方案么
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.