Giter Site home page Giter Site logo

kunminx / smooth-navigation Goto Github PK

View Code? Open in Web Editor NEW
252.0 4.0 24.0 414 KB

提供流畅的 Jetpack Navigation 转场体验。并解决 GitHub 上 Navigation Add Hide 修改版普遍存在的缺陷。

Java 100.00%
jetpack navigation navigation-architecture-component navigation-component fragment fragmentation

smooth-navigation's Introduction

Smooth-Navigation

中文说明

提供安全可靠 Navigation 操作,解决 GitHub 上 "Navigation Add Hide 修改版" 普遍存在 "popUpToInclusive 导致 Fragment 不符预期加载" 等问题。

使用方式: 于 gradle 添加 com.kunminx.arch:smooth-navigation 依赖,并将原有 androidx.navigation:navigation-fragment 或修改版依赖移除(否则编译过程中会遭原有依赖覆盖)。

Maven 依赖

  • 鉴于 Jcenter 关闭,我们已将仓库迁移至 Maven Central,请自行在根目录 build.gradle 添加 mavenCentral()
implementation 'com.kunminx.arch:smooth-navigation:4.0.0'

如使用 kotlin 拓展,在上述基础上,添加如下依赖即可:

implementation('androidx.navigation:navigation-fragment-ktx:2.3.2') {
    exclude group: 'androidx.navigation', module: "navigation-fragment"
}

English Guide

Provides safe and reliable Navigation operations, and solves the common problem of "popUpToInclusive causing Fragment to load unexpectedly" in the open source "Navigation Add Hide modified version" on GitHub.

How to use: Add com.kunminx.arch:smooth-navigation dependency in gradle, and remove the original androidx.navigation:navigation-fragment or modified version dependency (otherwise the original Dependent coverage).

Maven dependency

  • The following implementation is renamed. We have changed from archi to arch, so you should pay attention to the modification,
  • Due to the closure of JCenter, we have migrated the warehouse to Maven Central, so you can add mavenCentral() in the root directory build.gradle.
implementation 'com.kunminx.arch:smooth-navigation:4.0.0'

If you use kotlin extension, then on the basis of the above, add the following dependencies:

implementation('androidx.navigation:navigation-fragment-ktx:2.3.2') {
    exclude group: 'androidx.navigation', module: "navigation-fragment"
}

Thanks

感谢 @孙致远、@别睡太晚、@雅俗共赏 等小伙伴对 popUpToInclusive 及嵌套 child 等场景下容错问题反馈和测试 Demo 提供。

感谢小伙伴 @ hele-jeremy 对 launchSingleTop 场景下容错问题反馈和优化代码提供。

License

Copyright 2019-present KunMinX

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

smooth-navigation's People

Contributors

kunminx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

smooth-navigation's Issues

代码设置NavGraph,二级页面返回后白屏

我在代码里面初始化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

返回的时候没有日志,只隐藏了二级页面,一级页面也没显示,导致白屏了。

【 提问须知 】

如有 bug,请另外 new 一个 issue ⚠️⚠️⚠️

本项目开 issue 规范:

  1. 有任何 bug 都欢迎及时开 issue,我看到后予以处理。
  2. 如有使用上的疑问,请先认真阅读 Readme 和源码 sample,在没有找到答案后,另外开 issue。
  3. 如开 issue 是为了发表个人见解,请务必 客观、具体、严谨;严禁草率、乱入、带节奏:

务必注明观点所对应的场景,并附上完整可复现的代码,

不然缺乏一致的前提依据来有效交流!

任何缺乏实证依据和因果逻辑的泛泛而谈,都可能对其他使用者造成困扰。

在发表个人见解前,请先确保自己认真阅读过源码。这是对自己、对作者、对其他读者最起码的尊重。

Navigation 2.4+ Kotlin 重写版本

最新是 Navigation 2.4.2,由于使用 kotlin 重写,并且改动不少,不好把 2.3.x 的改动方式平移到 2.4.x 上。
另外现在 navigation-fragment 中包含 navigation-fragment-ktx,所以不能通过 exclude 解决依赖重复问题,只能拷贝代码到项目上。
2.4.x和2.3.x 版本混用不知道会不会有坑?

action跳转,双击崩溃

我的解决方案.. 抛砖引玉

fun NavController.safeNavigate(action: NavDirections) {
    try {
        this.navigate(action)
    } catch (e:Exception){
    }
}

清空back stack后跳转

在看了这篇文章之后,里面有一个写法,目标是和标题一样,行为应该是切换了navigationstartDestination

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

Hi there so i have this issue, and i cant resolve it, please help :)

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)

launchSingleTop属性跳转页面问题



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;
}
}

navArgs()方法不能用

删掉官方的androidx.navigation:navigation-fragment后,获取参数的扩展不能用了,请问咋整
Dingtalk_20201015173626

Duplicate class androidx.navigation.fragment.DialogFragmentNavigator found in modules

    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)

java.lang.IllegalArgumentException: Navigation action

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的任何代码。
我点击+添加动态,然后来回添加返回滑动。点击到某一条的时候就遇到这样情况了。

连续快速点击item跳转闪退

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

生命周期问题

由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")
    }

launchSingleTop模式跳转页面时,若启动页面时附带了新的arguments,如何处理arguments变更逻辑?

launchSingleTop模式跳转页面时,若启动页面时附带了新的arguments,有什么好的方案来处理因arguments变更而需执行的逻辑么?例如页面为文章详情页,启动页面时需附带文章id参数。【一般我会在onCreate时处理数据初始化的逻辑。对文章详情页的case来说就是接收argument中的文章id参数,交给VM来发起文章数据加载。】

测试场景如下:
当前栈顶:AFragment
NavAction: 由AFragment启动AFragment,附带新的arguments,launchSingleTop = true
此时满足栈顶复用条件,"复用"当前栈顶的AFragment示例(这里复用打个引号,原版Navigation实际上未复用实例)

---------方案对比 start------------
-----原版Navigation 行为-------

  1. 弹出栈顶AFragment实例
  2. 添加新的AFragment实例,arguments赋值为新的arguments

因创建了新实例,因此会执行Fragment onCreate -> onResume一系列生命周期方法,因此可随onCreate使用新的argument完成初始化逻辑。
-----SmoothNavigation行为-------

  1. 保持栈顶AFragment实例不变
  2. 对该实例调用setArguments方法

因生命周期未变更,除setArguments方法外的任何一个回调方法均不会被执行。
---------方案对比 end------------

在使用SmoothNavigation的情况下,除复写setArguments并处理argument变更逻辑外,有无其他的解决方案?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.