从DialogFragment回调片段

ete*_*att 154 android callback android-fragments android-dialogfragment

问题:如何创建从DialogFragment到另一个Fragment的回调.就我而言,涉及的活动应该完全不知道DialogFragment.

考虑一下我

public class MyFragment extends Fragment implements OnClickListener
Run Code Online (Sandbox Code Playgroud)

然后在某些时候我做到

DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
dialogFrag.show(getFragmentManager, null);
Run Code Online (Sandbox Code Playgroud)

MyDialogFragment的样子

protected OnClickListener listener;
public static DialogFragment newInstance(OnClickListener listener) {
    DialogFragment fragment = new DialogFragment();
    fragment.listener = listener;
    return fragment;
}
Run Code Online (Sandbox Code Playgroud)

但是,如果DialogFragment在其生命周期中暂停和恢复,则无法保证听众会出现.片段中唯一的保证是通过setArguments和getArguments通过Bundle传递的.

如果它应该是监听器,有一种方法可以引用该活动:

public Dialog onCreateDialog(Bundle bundle) {
    OnClickListener listener = (OnClickListener) getActivity();
    ....
    return new AlertDialog.Builder(getActivity())
        ........
        .setAdapter(adapter, listener)
        .create();
}
Run Code Online (Sandbox Code Playgroud)

但我不希望活动监听事件,我需要一个片段.实际上,它可以是任何实现OnClickListener的Java对象.

考虑通过DialogFragment呈现AlertDialog的Fragment的具体示例.它有Yes/No按钮.如何将这些按钮发送回创建它的Fragment?

Pio*_*rew 186

涉及的活动完全不知道DialogFragment.

片段类:

public class MyFragment extends Fragment {
int mStackLevel = 0;
public static final int DIALOG_FRAGMENT = 1;

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

    if (savedInstanceState != null) {
        mStackLevel = savedInstanceState.getInt("level");
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("level", mStackLevel);
}

void showDialog(int type) {

    mStackLevel++;

    FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
    Fragment prev = getActivity().getFragmentManager().findFragmentByTag("dialog");
    if (prev != null) {
        ft.remove(prev);
    }
    ft.addToBackStack(null);

    switch (type) {

        case DIALOG_FRAGMENT:

            DialogFragment dialogFrag = MyDialogFragment.newInstance(123);
            dialogFrag.setTargetFragment(this, DIALOG_FRAGMENT);
            dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");

            break;
    }
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch(requestCode) {
            case DIALOG_FRAGMENT:

                if (resultCode == Activity.RESULT_OK) {
                    // After Ok code.
                } else if (resultCode == Activity.RESULT_CANCELED){
                    // After Cancel code.
                }

                break;
        }
    }
}

}
Run Code Online (Sandbox Code Playgroud)

DialogFragment类:

public class MyDialogFragment extends DialogFragment {

public static MyDialogFragment newInstance(int num){

    MyDialogFragment dialogFragment = new MyDialogFragment();
    Bundle bundle = new Bundle();
    bundle.putInt("num", num);
    dialogFragment.setArguments(bundle);

    return dialogFragment;

}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    return new AlertDialog.Builder(getActivity())
            .setTitle(R.string.ERROR)
            .setIcon(android.R.drawable.ic_dialog_alert)
            .setPositiveButton(R.string.ok_button,
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getActivity().getIntent());
                        }
                    }
            )
            .setNegativeButton(R.string.cancel_button, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, getActivity().getIntent());
                }
            })
            .create();
}
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为这里的关键是`setTargetFragment`和`getTargetFragment`.使用`onActivityResult`有点不清楚.在Fragment调用者中声明自己的特定方法可能会更好,并使用它,而不是重新使用onActivityResult.但那时它的所有语义. (98认同)
  • 这会在配置更改 - 轮换中存活吗? (6认同)
  • @eternalmatt,你的异议是完全合理的,但我认为onActivityResult()的值是它保证存在于任何Fragment上,所以任何Fragment都可以用作父类.如果您创建自己的接口并让父Fragment实现它,那么子项只能与实现该接口的父项一起使用.如果您以后更广泛地使用孩子,将孩子与该界面耦合可能会再次困扰您.使用"内置"onActivityResult()接口不需要额外的耦合,因此它可以让您更灵活. (6认同)
  • 用过这个.注意:堆栈级别不需要在旋转或睡眠中存活.我的片段实现了DialogResultHandler#handleDialogResult(我创建的接口),而不是onActivityResult.@myCode,显示一个对话框选择的值被添加到Intent,然后在你的onActivityResult中读取将是非常有帮助的.初学者不清楚意图. (3认同)
  • 堆栈级变量未使用? (2认同)

Ogu*_*can 73

TargetFragment解决方案似乎不是对话框片段的最佳选择,因为它可能会IllegalStateException在应用程序被销毁并重新创建后创建.在这种情况下FragmentManager找不到目标片段,你会得到一个这样IllegalStateException的消息:

"关键android:target_state:index 1"不再存在片段

它似乎Fragment#setTargetFragment()不是为了孩子和父母片段之间的沟通,而是为了兄弟片段之间的沟通.

所以另一种方法是使用ChildFragmentManager父片段创建这样的对话框片段,而不是使用以下活动FragmentManager:

dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment");
Run Code Online (Sandbox Code Playgroud)

通过使用一个接口,在onCreate方法中DialogFragment你可以得到父片段:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    try {
        callback = (Callback) getParentFragment();
    } catch (ClassCastException e) {
        throw new ClassCastException("Calling fragment must implement Callback interface");
    }
}
Run Code Online (Sandbox Code Playgroud)

剩下的就是在这些步骤之后调用你的回调方法.

有关该问题的详细信息,请访问以下链接:https: //code.google.com/p/android/issues/detail?id = 54520

  • 这也适用于 api 23 中引入的 onAttach(Context context)。 (2认同)
  • 这应该是公认的答案。当前接受的答案是有问题的,并且片段不应该这样使用。 (2认同)
  • @AhmadFadli这里的问题是为片段之间的通信获取正确的上下文(父级)。如果您要使用对话框片段作为活动的子元素,那么应该不会有任何混淆。Activity的FragmentManager和获取回调的getActivity()足够了。 (2认同)

Rui*_*Rui 34

也许有点晚了,但可能会帮助其他人像我一样有同样的问题.

您可以在显示之前使用setTargetFragmenton Dialog,并在对话框中调用getTargetFragment以获取参考.


Ger*_*eto 33

推荐的方法是使用新的Fragment Result API

通过使用它,您不需要重写 onAttach(context)setTargetFragment()后者现已弃用


1 - 在父Fragment上添加结果监听器onCreate

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    childFragmentManager.setFragmentResultListener("requestKey", this) { key, bundle ->
        val result = bundle.getString("bundleKey")
    }

}
Run Code Online (Sandbox Code Playgroud)

2- 在Fragment 上,设置结果(例如,在按钮单击侦听器上):

button.setOnClickListener {
    val result = "resultSample"

    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
Run Code Online (Sandbox Code Playgroud)

有关文档的更多信息:https ://developer.android.com/guide/fragments/communicate#fragment-result

希望能帮助到你!

  • 哇,多么好的答案啊!我想知道这个答案需要多长时间才能获得票数,我是一名活跃的首席 Android 工程师,不知何故没有听到这个 api 的风声,我不得不说这可能是现在 - 2022 年 -正确的方法,应该是这个问题的公认答案!干杯杰拉尔多:-) (5认同)

Vij*_*ede 31

我按照这个简单的步骤来做这些事情.

  1. DialogFragmentCallbackInterface某些方法一样创建界面callBackMethod(Object data).你打算传递数据.
  2. 现在你可以DialogFragmentCallbackInterface在你的片段中实现接口了MyFragment implements DialogFragmentCallbackInterface
  3. DialogFragment创建时将您的调用片段设置MyFragment为创建DialogFragment使用myDialogFragment.setTargetFragment(this, 0)检查setTargetFragment(Fragment fragment,int requestCode)的目标片段

    MyDialogFragment dialogFrag = new MyDialogFragment();
    dialogFrag.setTargetFragment(this, 1); 
    
    Run Code Online (Sandbox Code Playgroud)
  4. DialogFragment通过调用getTargetFragment()并将其转换为您的目标片段对象DialogFragmentCallbackInterface.现在您可以使用此接口将数据发送到您的片段.

    DialogFragmentCallbackInterface callback = 
               (DialogFragmentCallbackInterface) getTargetFragment();
    callback.callBackMethod(Object data);
    
    Run Code Online (Sandbox Code Playgroud)

    这一切都完成了!只需确保您已在片段中实现此接口.

  • 这应该是最好的答案.很棒的答案. (3认同)

Edw*_*rey 19

与其他碎片通信指南说的片段应该通过相关的活动交流.

通常,您会希望一个片段与另一个片段进行通信,例如根据用户事件更改内容.所有Fragment-to-Fragment通信都是通过相关的Activity完成的.两个碎片永远不应该直接通信.

  • 我知道这是旧的,但是如果有其他人来到这里,我觉得当一个片段"拥有"用于确定DialogFragment的创建和管理的逻辑时,该文档中所述的案例不适用.当活动甚至不确定为什么创建Dialog或者在什么条件下它应该被解雇时,从片段到活动创建一堆连接有点奇怪.除此之外,DialogFragment非常简单,仅用于通知用户并可能获得响应. (15认同)
  • 我认为随着片段的使用被扩展,不使用直接片段通信的最初想法就会破坏.例如,在导航抽屉中,活动的每个直接子片段大致充当活动.因此,通过活动进行对话碎片等片段的通信会损害IMO的可读性/灵活性.事实上,似乎没有任何好的方法来封装dialogfragment以允许它以可重用的方式处理活动和片段. (3认同)

Jam*_*ken 12

您应该interface在片段类中定义一个并在其父活动中实现该接口.详细信息请参见http://developer.android.com/guide/components/fragments.html#EventCallbacks.代码看起来类似于:

分段:

public static class FragmentA extends DialogFragment {

    OnArticleSelectedListener mListener;

    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

活动:

public class MyActivity extends Activity implements OnArticleSelectedListener{

    ...
    @Override
    public void onArticleSelected(Uri articleUri){

    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 我会考虑你想做什么坏练习.Android指南建议所有片段到片段的通信都通过活动进行(根据http://developer.android.com/training/basics/fragments/communicating.html).如果你真的希望在`MyFragment`中处理所有这些,你可能想切换到使用常规的`AlertDialog` (2认同)

Sup*_*Yao 7

根据官方文档:

片段#setTargetFragment

此片段的可选目标。例如,如果此片段由另一个片段启动,并且在完成后想要将结果返回给第一个片段,则可以使用此方法。此处设置的目标通过 FragmentManager#putFragment 跨实例保留。

片段#getTargetFragment

返回由 setTargetFragment(Fragment, int) 设置的目标片段。

所以你可以这样做:

// In your fragment

public class MyFragment extends Fragment implements OnClickListener {
    private void showDialog() {
        DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
        // Add this
        dialogFrag.setTargetFragment(this, 0);
        dialogFrag.show(getFragmentManager, null);
    }
    ...
}

// then

public class MyialogFragment extends DialogFragment {
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // Then get it
        Fragment fragment = getTargetFragment();
        if (fragment instanceof OnClickListener) {
            listener = (OnClickListener) fragment;
        } else {
            throw new RuntimeException("you must implement OnClickListener");
        }
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)


use*_*603 5

为片段设置侦听器的正确方法是在附加片段时对其进行设置。我遇到的问题是从未调用过 onAttachFragment()。经过一番调查,我意识到我一直在使用getFragmentManager而不是getChildFragmentManager

这是我如何做到的:

MyDialogFragment dialogFragment = MyDialogFragment.newInstance("title", "body");
dialogFragment.show(getChildFragmentManager(), "SOME_DIALOG");
Run Code Online (Sandbox Code Playgroud)

将其附加到 onAttachFragment 中:

@Override
public void onAttachFragment(Fragment childFragment) {
    super.onAttachFragment(childFragment);

    if (childFragment instanceof MyDialogFragment) {
        MyDialogFragment dialog = (MyDialogFragment) childFragment;
        dialog.setListener(new MyDialogFragment.Listener() {
            @Override
            public void buttonClicked() {

            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)


Gil*_*oot 5

更新:请注意,如果有人感兴趣,我可以分享使用视图模型更简单的方法。

Kotlin 伙计们,我们走了!

所以我们的问题是我们创建了一个活动,MainActivity在那个活动上我们创建了一个片段,FragmentA现在我们想在FragmentA调用它的基础上创建一个对话框片段FragmentB。我们如何FragmentBFragmentA不经过的情况下从back to得到结果MainActivity

笔记:

  1. FragmentA是 的子片段MainActivity。要管理在 中创建的片段,FragmentA我们将使用childFragmentManagerwhich 来做到这一点!
  2. FragmentA是 的父片段FragmentBFragmentA要从内部访问,FragmentB我们将使用parenFragment.

话虽如此,在里面FragmentA

class FragmentA : Fragment(), UpdateNameListener {
    override fun onSave(name: String) {
        toast("Running save with $name")
    }

    // call this function somewhere in a clickListener perhaps
    private fun startUpdateNameDialog() {
        FragmentB().show(childFragmentManager, "started name dialog")
    }
}
Run Code Online (Sandbox Code Playgroud)

这是对话片段FragmentB

class FragmentB : DialogFragment() {

    private lateinit var listener: UpdateNameListener

    override fun onAttach(context: Context) {
        super.onAttach(context)
        try {
            listener = parentFragment as UpdateNameListener
        } catch (e: ClassCastException) {
            throw ClassCastException("$context must implement UpdateNameListener")
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            val binding = UpdateNameDialogFragmentBinding.inflate(LayoutInflater.from(context))
            binding.btnSave.setOnClickListener {
                val name = binding.name.text.toString()
                listener.onSave(name)
                dismiss()
            }
            builder.setView(binding.root)
            return builder.create()
        } ?: throw IllegalStateException("Activity can not be null")
    }
}
Run Code Online (Sandbox Code Playgroud)

这是连接两者的界面。

interface UpdateNameListener {
    fun onSave(name: String)
}
Run Code Online (Sandbox Code Playgroud)

就是这样。