片段未附加到主机

Fragment not attached to host

让我解释一下整个事情,以防万一。我将 BottomNavigationView 与 Jetpack 的移动导航图等结合使用。我重新实现了方法:

NavigationUI.setupWithNavController(...)

在导航设置期间,我没有使用此方法,而是使用了一个非常相似的方法:

customSetupWithNavController(...)

我所做的唯一更改是更改片段之间的默认淡入淡出过渡,并开始使用更自然(在我看来)的侧滑动画,就像在 onNavDestinationSelected 中这样:

if(origin == 0){
        builder.setEnterAnim(R.anim.slide_in_from_right)
                .setExitAnim(R.anim.slide_out_to_left)
                .setPopEnterAnim(R.anim.slide_in_from_left)
                .setPopExitAnim(R.anim.slide_out_to_right);
    }else if(origin == 1){
        builder.setEnterAnim(R.anim.slide_in_from_left)
                .setExitAnim(R.anim.slide_out_to_right)
                .setPopEnterAnim(R.anim.slide_in_from_right)
                .setPopExitAnim(R.anim.slide_out_to_left);
    }

其中origin代表传入的Fragment来自的方向。

它带来了一个问题:我的2个片段需要一个FAB来向回收器添加元素,侧滑过渡突然变得丑陋。所以我在 MainActivity 的 Layout 中添加了一个 FAB,并且只有在调用这 2 个片段时才会显示 FAB 的逻辑。

我找不到将单击事件从 Activity 传递到片段的好方法,因为我无法实例化片段,因为导航处理整个过程。

所以,我所做的是创建一个 ViewHolder,因为我知道它可以在生命周期变化中存活下来。这个 ViewHolder 包含一个 int 和一个 Mu​​tableLiveData,在 MainActivity 逻辑中我传递了所选元素的当前所选 id BottomNavigationView 到 int,只有当 MainActivity 的 FAB 被点击时,实时 Boolean 才设置为 true。因此,在 Fragments onViewCreated() 中,我向此 Boolean 添加了观察者,当值设置为 true 时,id 传递给了 ViewHolder与当前片段的id匹配,Boolean设置回false,可以做一些事情,就像这样:

eventsNotificationHandler.getClickEvent().observe(requireActivity(), new Observer<Boolean>() {
        @Override
        public void onChanged(Boolean aBoolean) {
            if(aBoolean && eventsNotificationHandler.getPositionId() == R.id.nav_contacts){
                eventsNotificationHandler.setClickEvent(false);
                //do something here
            }
        }
    });

这个 notificationHandler 是 ViewHolder。

到目前为止一切顺利,此时我可以:

1- 在 BottomNavigationView 的片段之间自由导航,FAB 只显示需要的片段。

2- 随时在观察器中使用 Log.d(...),然后看到调试消息就好了。

3- Toast 消息,任何时候我想要,ONLY 如果上下文参数是在 Observer 之外初始化的,像这样:

Toast.makeText(previouslyDefinedContext, "SOME TEXT", Toast.LENGTH_SHORT).show();

我不会的:

1- 随时从观察者内部启动一个 Activity,使用与以前相同的想法,ONLY 在观察者之前和外部初始化上下文 I能够启动意图,就像这样:

eventsNotificationHandler.getClickEvent().observe(requireActivity(), new Observer<Boolean>() {
        @Override
        public void onChanged(Boolean aBoolean) {
            if(aBoolean && eventsNotificationHandler.getPositionId() == R.id.nav_contacts){
                eventsNotificationHandler.setClickEvent(false);
                Intent newContact = new Intent(previouslyDefinedContext, NewContactActivity.class);
                startActivity(newContact);
                requireActivity().overridePendingTransition(R.anim.slide_in_from_right,R.anim.slide_out_to_left);
            }
        }
    });

但在这种特殊情况下,我可以根据需要多次启动新的 Activity,但只有 如果我直接导​​航到观察者所在的这个特定片段是在应用程序打开后定义的,如果我决定先通过其他一些片段导航,然后转到该片段尝试启动 Activity,应用程序就会崩溃

我注意到当我从 Observer 内部调用 requireContext() 时会发生这种确切的行为,它工作但随后停止工作。

应用崩溃:

E/AndroidRuntime: FATAL EXCEPTION: main Process: cu.arrowtech.bpawreckproject, PID: 18019 java.lang.IllegalStateException: Fragment FragmentContacts{883259b} (9f127bdb-127d-4366-b90b-c8900a5a771e)} not attached to Activity

我想要的:

1- 如果可能的话,通过在 MainActivity 中按下 FAB,从 Fragment 中启动新 Activity 的正确方法。

2- 如果可能的解决方案意味着更改我已有的逻辑,这是一种切换片段的好方法。

3- 继续使用 Jetpack 和导航图。

我可以通过在每个 Fragment 中使用 2 个独立的 FAB 来做我想做的事情,但是过渡并不漂亮。

我乐于接受建议,即使这意味着要改变逻辑。我几乎可以肯定,这一定是一种更好的方法来完成我正在做的事情,而不是为此目的使用 ViewHolder。

我想得到类似于Google支付的东西,似乎用于添加支付方式、通行证和转账的按钮是同一个按钮,但它适应各种情况。

经过一些研究,我确实找到了一种方法来保持片段之间的流畅过渡 AND 移动导航组件 AND MainActivity 中的单个 FAB布局。

我所做的是使用接口而不是 ViewModel(我一直知道这种方法是错误的):

public interface SharedViewsInterface {
    void onFabClicked();
}

所以在我的MainActivity中我把那个接口定义为null,并且根据情况创建了一个方法来定义它。我的 MainActivity 看起来像:

public class MainActivity extends AppCompatActivity {
    
    //UI elements
    private FloatingActionButton main_fab;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

        //Same dode to get the custom animations
        //.......
    
        //Main and only Fab configuration
        main_fab = findViewById(R.id.main_fab);
        main_fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(sharedViewsInterface !=null){
                    sharedViewsInterface.onFabClicked();
                }
            }
        });    
    }


    //Other auxiliary methods
    public void setInterface(SharedViewsInterface sharedViewsInterface){
        this.sharedViewsInterface = sharedViewsInterface;
    }
}

通过这样做,我可以从每个片段通过这样做在 onCreate 中实现接口:

((MainActivity) requireActivity()).setInterface(new SharedViewsInterface() {
        @Override
        public void onFabClicked() {
            Intent newContact = new Intent(requireContext(), NewContactActivity.class);
            startActivity(newContact);
            requireActivity().overridePendingTransition(R.anim.slide_in_from_right_short,R.anim.slide_out_to_left_medium);
        }
});

这很有效,因为 FAB 仅在具有接口实现的片段可见时显示,请参见我的示例 gif