片段未附加到主机
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 和一个 MutableLiveData,在 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
让我解释一下整个事情,以防万一。我将 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 和一个 MutableLiveData,在 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