【问题标题】:Show dialog above view pager that has nested fragments在具有嵌套片段的视图寻呼机上方显示对话框
【发布时间】:2015-10-23 21:03:05
【问题描述】:

我设置了一个非常简单的测试项目https://github.com/ArtworkAD/ViewPagerDialogTest 来评估以下情况:主要活动有一个视图寻呼机,它使用支持片段管理器托管单个片段:

public class MainActivity extends AppCompatActivity {

    // ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
        // ...
        tabLayout.setupWithViewPager(viewPager);
    }

    @Override
    protected void onResume() {
        super.onResume();

        MainActivity.CustomDialog dialog = (MainActivity.CustomDialog) getSupportFragmentManager().findFragmentByTag(MainActivity.CustomDialog.TAG);

        if (dialog == null) {
            new MainActivity.CustomDialog().show(getSupportFragmentManager().beginTransaction(), MainActivity.CustomDialog.TAG);
        }
    }
    // ...
}

当活动恢复时,主活动内会显示一个对话框片段。

视图分页器内的单个片段定义如下:

public class RootFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.root_fragment, container, false);
        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
        }
        return root;
    }
}

这个根片段允许我们在“root_frame”上堆叠其他片段。所以我们堆叠一个又一个:

public class FirstLevelFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        setRetainInstance(true);
        View root = inflater.inflate(R.layout.first_level_fragment, container, false);
        root.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SecondLevelFragment f = (SecondLevelFragment) getActivity().getSupportFragmentManager().findFragmentByTag("NESTED");
                if (f == null) {
                    getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
                }
            }
        });
        return root;
    }

    public static class SecondLevelFragment extends Fragment {

        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            setRetainInstance(true);
            return inflater.inflate(R.layout.second_level_fragment, container, false);
        }
    }
}

这很好用!堆叠思想取自https://stackoverflow.com/a/21453571/401025。但是,当显示对话框并且用户转到第二级片段并旋转屏幕时,我得到以下异常:

E/AndroidRuntime: java.lang.RuntimeException: 无法启动活动 组件信息{de.azzoft.viewpagerdialogtest/de.azzoft.viewpagerdialogtest.MainActivity}: java.lang.IllegalArgumentException:没有为 id 0x7f0c0083 找到视图 (de.azzoft.viewpagerdialogtest:id/root_frame) 用于片段 SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}

E/AndroidRuntime: 引起: java.lang.IllegalArgumentException: 否 找到 id 0x7f0c0083 的视图 (de.azzoft.viewpagerdialogtest:id/root_frame) 用于片段 SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}

完整的堆栈跟踪:https://github.com/ArtworkAD/ViewPagerDialogTest/blob/master/README.md

如果没有出现对话框,一切都会很好。您可以通过下载测试项目来测试它。

看起来实际上是一个片段的对话框在添加到活动时会弄乱片段层次结构。任何想法如何解决这个问题?

保留第二个片段很重要。

【问题讨论】:

  • 我正在努力修复它
  • 你可以试试this solution
  • @Yvette 应该保留“SecondLevelFragment”。嵌套可能是错误的说法,更好的是:添加在“FirstLevelFragment”之上。但实际上如果你看一下代码你就会明白。

标签: android android-fragments android-viewpager android-support-library


【解决方案1】:
No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment

Activity 在旋转时重新创建时,Activity FragmentManger 会尝试将 SecondLevelFragment 添加到 R.id.root_frame 中。但是 root_frame 视图不在 Activity 布局中,它在 FirstLevelFragment 布局中。这就是应用崩溃的原因。

您必须进行两项更改才能解决此问题。

使用getChildFragmentManagerFirstLevelFragment 添加到RootFragment

getChildFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();

使用FragmentManager添加SecondLevelFragment

getFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();

最后从FirstLevelFragmentSecondLevelFragment 中删除setRetainInstance,因为嵌套片段不需要设置保留。

如果您需要在后按时弹回SecondLevelFragment,则需要将后按事件传递给 RootFragment 并从返回堆栈中弹出。

覆盖活动的后按

@Override
public void onBackPressed() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.viewpager);
    if(fragment instanceof RootFragment){
        boolean handled = ((RootFragment)fragment).onBackPressed();
        if(handled){
            return;
        }
    }
    super.onBackPressed();
}

并处理RootFragment 的后按

public boolean onBackPressed() {
    int count = getChildFragmentManager().getBackStackEntryCount();
    if(count > 0){
        getChildFragmentManager().popBackStackImmediate();
        return true;
    }
    return false;
} 

我向您的存储库创建了一个拉取请求。请检查 https://github.com/ArtworkAD/ViewPagerDialogTest/pull/1

如果有任何问题,请告诉我。

【讨论】:

  • 它修复了异常,但是第二级片段不能再从返回堆栈中弹出。
  • 可以从RootFragment弹回第二级片段。请检查更新的答案以处理后按。
  • 首先看起来这是解决方案,但现在我注意到子片段管理器无法保留其片段。我认为这是一个错误?
  • 是的,这是一个已知的错误。请查看此博客ideaventure.blogspot.com.au/2014/10/… 以了解解决方法。有一张谷歌票,但还没有修复。 code.google.com/p/android/issues/detail?id=74222
【解决方案2】:

如果您覆盖 onDismiss 则解决崩溃。尽情享受吧。

 @Override
 protected void onResume() {
        super.onResume();

        DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TAG);

        if(dialog == null){
            CustomDialog.newInstance().show(getSupportFragmentManager(), TAG);

        }


    }


   public static class CustomDialog extends DialogFragment {


        public static CustomDialog newInstance() {
            CustomDialog d = new CustomDialog();
            return d;
        }

        @Override
        public void onDismiss(DialogInterface dialog) {
//            super.onDismiss(dialog);
            Toast.makeText(getActivity(), "onDismiss", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onCancel(DialogInterface dialog) {
//            super.onCancel(dialog);

            Toast.makeText(getActivity(), "onCancel", Toast.LENGTH_LONG).show();

        }

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

            setRetainInstance(true);

        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            builder.setTitle("Dialog");
            builder.setMessage("This is a message!");

            builder.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();

                }
            });

            builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();

                }
            });

            return builder.show();
        }
    }

【讨论】:

  • 在生产中我现在按照你说的做,但是对话框片段优于普通的警报对话框。
  • 这是一个错误,因此您不能使用对话框片段。我建议你使用这个github.com/afollestad/material-dialogs 进行对话
  • 为什么这样有效?我的意思是我检查了一下,问题就解决了,但是为什么呢?
  • 或者我测试了它并意识到像你一样覆盖 onDismiss 不会正确地从后台堆栈中删除对话框片段。
  • 根,每次旋转后的第一个和第二个片段仍在后台堆栈中。我确实像其他片段一样强制对话框片段仍在后台堆栈中。
【解决方案3】:

如果您想保持Fragments 的状态,您应该使用FragmentStatePagerAdapter

来自文档:

PagerAdapter 的实现,它使用一个 Fragment 来管理每个 页。此类还处理片段的保存和恢复 状态。

如果您使用它,您还可以删除 setRetainInstance(true) 调用。

【讨论】:

    【解决方案4】:

    嗯,我已经下载了你的测试应用,看来我已经解决了这个问题。

    在您的 FirstLevelFragment 类中,注释以下行

     //if (nestedNestedFragment == null) {
         getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
     //}
    

    SecondLevelFragment中评论setRetainInstance(true);

    【讨论】:

    • 我认为当我按照您的建议进行操作时,第二个 Fragment 将不再保留?
    • 好的,我认为目的是显示对话框。不用担心会进一步调查
    【解决方案5】:

    我认为您在活动的onCreate() 中错过了setContentView()。看你的 Fragment 不能在没有 View 层次结构的情况下添加。您的片段由活动托管。所以需要先给activity设置内容。

    希望这会有所帮助, 谢谢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多