249

我将DialogFragments用于许多事情:从列表中选择项目,输入文本。

将值(即列表中的字符串或项目)返回给调用活动/片段的最佳方法是什么?

目前,我正在实现调用活动DismissListener并为 DialogFragment 提供对该活动的引用。Dialog 然后调用 Activity 中的OnDimiss方法,Activity 从 DialogFragment 对象中获取结果。非常混乱,它在配置更改(方向更改)上不起作用,因为 DialogFragment 失去了对活动的引用。

谢谢你的帮助。

4

15 回答 15

259

从显示对话框的位置使用myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE),然后在对话框完成后,您可以从中调用getTargetFragment().onActivityResult(getTargetRequestCode(), ...),并onActivityResult()在包含的片段中实现。

这似乎是对 的滥用onActivityResult(),尤其是因为它根本不涉及活动。但是我已经看到了官方谷歌人推荐的,甚至可能在 api 演示中。我认为这g/setTargetFragment()是添加的。

于 2012-11-17T19:01:40.653 回答
143

正如您在此处看到的,有一种非常简单的方法可以做到这一点。

在您DialogFragment添加一个接口监听器,如:

public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
}

然后,添加对该侦听器的引用:

private EditNameDialogListener listener;

这将用于“激活”侦听器方法,并检查父 Activity/Fragment 是否实现此接口(见下文)。

Activity//那个“调用”的地方简单地实现了这个接口FragmentActivityFragmentDialogFragment

在您DialogFragment想要关闭DialogFragment并返回结果的地方,您需要添加的所有内容是:

listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();

mEditText.getText().toString()将传递回调用的内容在哪里Activity

请注意,如果您想返回其他内容,只需更改侦听器采用的参数即可。

最后,您应该检查该接口是否实际由父活动/片段实现:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // Verify that the host activity implements the callback interface
    try {
        // Instantiate the EditNameDialogListener so we can send events to the host
        listener = (EditNameDialogListener) context;
    } catch (ClassCastException e) {
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(context.toString()
                + " must implement EditNameDialogListener");
    }
}

这种技术非常灵活,即使您还不想关闭对话框,也可以使用结果进行回调。

于 2013-02-11T08:34:09.050 回答
52

有一种更简单的方法可以从 DialogFragment 接收结果。

首先,在您的 Activity、Fragment 或 FragmentActivity 中,您需要添加以下信息:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Stuff to do, dependent on requestCode and resultCode
    if(requestCode == 1) { // 1 is an arbitrary number, can be any int
         // This is the return result of your DialogFragment
         if(resultCode == 1) { // 1 is an arbitrary number, can be any int
              // Now do what you need to do after the dialog dismisses.
         }
     }
}

requestCode基本上是您调用的 DialogFragment 的 int 标签,我将在稍后展示它是如何工作的。resultCode 是您从 DialogFragment 发回的代码,告诉您当前等待的 Activity、Fragment 或 FragmentActivity 发生了什么。

下一段代码是对 DialogFragment 的调用。这里有一个例子:

DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);     
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");

通过这三行,您可以声明您的 DialogFragment,设置一个 requestCode(一旦 Dialog 被关闭,它将调用 onActivityResult(...),然后您将显示该对话框。就这么简单。

现在,在您的 DialogFragment 中,您只需在 DialogFragment 之前直接添加一行,dismiss()以便将 resultCode 发送回 onActivityResult()。

getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();

就是这样。请注意,resultCode 定义为int resultCoderesultCode = 1;在这种情况下设置的值。

就是这样,您现在可以将 DialogFragment 的结果发送回您的调用 Activity、Fragment 或 FragmentActivity。

此外,看起来此信息之前已发布,但没有给出足够的示例,所以我想我会提供更多详细信息。

编辑 06.24.2016 对于上面的误导性代码,我深表歉意。但是您肯定无法将结果返回给活动,如下所示:

dialogFrag.setTargetFragment(this, 1);

设定一个目标Fragment而不是Activity。因此,为了做到这一点,您需要使用实现一个InterfaceCommunicator.

在你的DialogFragment设置一个全局变量

public InterfaceCommunicator interfaceCommunicator;

创建一个公共函数来处理它

public interface InterfaceCommunicator {
    void sendRequestCode(int code);
}

然后,当您准备好将代码发送回运行完成ActivityDialogFragment,您只需在您之前添加以下dismiss();DialogFragment

interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.

现在,在您的活动中,您必须做两件事,第一件事是删除不再适用的那一行代码:

dialogFrag.setTargetFragment(this, 1);  

然后实现接口,就大功告成了。您可以通过将以下行添加到implements类最顶部的子句中来做到这一点:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

然后@Override是活动中的功能,

@Override
public void sendRequestCode(int code) {
    // your code here
}

您可以像使用该方法一样使用此接口onActivityResult()方法。除了接口方法是for DialogFragments,另一个是for Fragments

于 2014-08-15T13:58:20.107 回答
22

好吧,回答可能为时已晚,但这是我为从DialogFragment. 与@brandon 的回答非常相似。在这里我DialogFragment从一个片段中调用,只需将此代码放置在您调用对话框的位置。

FragmentManager fragmentManager = getFragmentManager();
            categoryDialog.setTargetFragment(this,1);
            categoryDialog.show(fragmentManager, "dialog");

我要调用categoryDialog的我在哪里,然后在您的实现中将此代码放置在您打算设置数据的位置。的值为1 你可以设置它或使用系统定义。DialogFragmentdialogfragmentresultCode

            Intent intent = new Intent();
            intent.putExtra("listdata", stringData);
            getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
            getDialog().dismiss();

现在是时候回到调用片段并实现这个方法了。如果您想在 if 条件下resultCode检查数据有效性或结果是否成功。requestCode

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);        
        //do what ever you want here, and get the result from intent like below
        String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
    }
于 2016-04-09T08:25:29.733 回答
18

对于仍在阅读此内容的任何人:setTargetFragment()已被弃用。现在建议使用这样的FragmentResultListenerAPI:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("resultKey")
        // Do something with the result...
    }

    ...

    // Somewhere show your dialog
    MyDialogFragment.newInstance().show(parentFragmentManager, "tag")
}

然后在你的MyDialogFragment设置结果:

button.setOnClickListener{
    val result = "some string"
    setFragmentResult("requestKey", bundleOf("resultKey" to result))
    dismiss()
}
于 2020-08-05T09:57:46.987 回答
11

不同的方法,以允许Fragment与其Activity进行通信:

1)在片段中定义一个公共接口并为其创建一个变量

public OnFragmentInteractionListener mCallback;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int id);
}

2)将活动投射到片段中的mCallback变量

try {
    mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
    Log.d(TAG, e.getMessage());
}

3)在您的活动中实现监听器

public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener  {
     //your code here
}

4)覆盖活动中的OnFragmentInteraction

@Override
public void onFragmentInteraction(int id) {
    Log.d(TAG, "received from fragment: " + id);
}

更多信息:https ://developer.android.com/training/basics/fragments/communicating.html

于 2017-07-30T19:00:39.257 回答
10

我发现的一种简单方法如下:实现这是您的 dialogFragment,

  CallingActivity callingActivity = (CallingActivity) getActivity();
  callingActivity.onUserSelectValue("insert selected value here");
  dismiss();

然后在调用 Dialog Fragment 的活动中创建适当的函数,如下所示:

 public void onUserSelectValue(String selectedValue) {

        // TODO add your implementation.
      Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
    }

Toast 是为了证明它有效。为我工作。

于 2015-12-23T22:44:43.653 回答
7

DialogFragment看到没有人建议使用本地广播进行通信,我感到非常惊讶Activity!我发现它比其他建议更简单、更干净。本质上,您注册Activity以收听广播并从您的DialogFragment实例发送本地广播。简单的。有关如何设置的分步指南,请参见此处

于 2015-10-04T18:20:48.957 回答
4

或共享 ViewModel,如下所示:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

https://developer.android.com/topic/libraries/architecture/viewmodel#sharing_data_between_fragments

于 2018-10-29T15:00:04.620 回答
3

在我的情况下,我需要将参数传递给 targetFragment。但我得到了异常“片段已经激活”。所以我在我的 DialogFragment 中声明了一个由 parentFragment 实现的接口。当 parentFragment 启动一个 DialogFragment 时,它将自己设置为 TargetFragment。然后在 DialogFragment 我打电话

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);
于 2017-07-03T08:51:26.597 回答
3

在科特林

    // My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
    
    var listener: InterfaceCommunicator? = null

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        listener = context as InterfaceCommunicator
    }

    interface InterfaceCommunicator {
        fun sendRequest(value: String)
    }   

    override fun onClick(v: View) {
        when (v.id) {
            R.id.buttonOk -> {    
        //You can change value             
                listener?.sendRequest('send data')
                dismiss()
            }
            
        }
    }
}

// 我的活动

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

    override fun sendRequest(value: String) {
    // :)
    Toast.makeText(this, value, Toast.LENGTH_LONG).show()
    }
}
 

我希望它有用,如果你可以改进,请编辑它。我的英文不是很好

于 2018-10-13T22:24:22.970 回答
1

如果你想发送参数并从第二个片段接收结果,你可以使用 Fragment.setArguments 来完成这个任务

static class FirstFragment extends Fragment {
    final Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 101: // receive the result from SecondFragment
                Object result = msg.obj;
                // do something according to the result
                break;
            }
        };
    };

    void onStartSecondFragments() {
        Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
                                                                               // instance
        putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
    }
}

static class SecondFragment extends DialogFragment {
    Message mMsg; // arguments from the caller/FirstFragment

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);
        mMsg = getParcelable(this);
    }

    void onClickOK() {
        mMsg.obj = new Object(); // send the result to the caller/FirstFragment
        mMsg.sendToTarget();
    }
}

static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
    if (f.getArguments() == null) {
        f.setArguments(new Bundle());
    }
    f.getArguments().putParcelable("extra_args", arg);
    return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
    return f.getArguments().getParcelable("extra_args");
}
于 2019-02-26T02:25:40.887 回答
0

在对话片段上

class AbcDialogFragment(private val ondata: (data: String) -> Unit) :  DialogFragment() {}

从片段/活动显示对话框的代码

val abcDialogFragment = AbcDialogFragment(ondata = {data->  })
                
abcDialogFragment.show(requireActivity().supportFragmentManager, "TAG")

在对话框片段中,您可以在对话框片段关闭或任何点击侦听器时调用 onData。

于 2021-04-15T03:04:33.817 回答
0

TL;DR - 使用此AppDialog类将数据传递到 DialogFragment 并从中获取结果。

详细解释:

  • 前提- 片段在配置更改时被销毁并重新创建。查看模型徘徊。使用 Dialog 时,建议将其包装在 DialogFragment 中,这样当用户旋转设备并改变方向时,Dialog 不会意外消失(DialogFragment 会重新创建并重新显示它)。
  • 限制(因此这个问题) - DialogFragment 的工作方式是它需要一个需要重新实例化配置更改的类 - 这意味着不能将构造函数参数传递给子类来传递参数,通常需要通过视图模型进行自定义回调以传回对话框的结果。这通常意味着每个对话框都有一个新的子类。
  • 解决方案- 为了帮助解决这一切,这个自定义 AppDialog 片段来救援 - 参数存储在内存中(类似于视图模型,您可以将其视为将 T 保存在内存中并使用它的微型自定义视图模型在配置更改时重新创建对话框),直到对话框片段被解除。回调的正确方法是通过视图模型。如果显示 AppDialog 的片段,那么您可能已经有一个视图模型,并且您可以从用于创建对话框的 lambda 引用它 - 这意味着对视图模型的额外强引用,直到对话框片段被解除。
  • 示例- 请参阅重构简单 Dialog 以使用此 AppDialog 实用程序类来接收参数并回调 viewModel 以通知结果的示例。

助手类:


class AppDialog<T>: DialogFragment() {
    companion object {

        fun<T> buildDialog(params: T? = null, builder: AppDialogLambda<T>): AppDialog<T> {

            // Setup arguments
            val args = Bundle()
            args.putInt("key", pushDialogArgs(params, builder))

            // Instantiate
            val fragment = AppDialog<T>()
            fragment.arguments = args
            return fragment
        }

        // --------------------
        // Dialog Arguments

        private var lastKey: Int = 0
        private val dialogArgs = mutableMapOf<Int, Pair<Any?, AppDialogLambda<*>>>()

        private fun pushDialogArgs(params: Any?, builder: AppDialogLambda<*>): Int {
            dialogArgs[lastKey] = params to builder
            return lastKey++
        }

        private fun getDialogArgs(key: Int): Pair<Any?, AppDialogLambda<*>> {
            return dialogArgs[key]!!
        }

        private fun deleteDialogArgs(key: Int) {
            dialogArgs.remove(key)
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // Get arguments
        val argKey = requireArguments().getInt("key")
        val (params, builder) = getDialogArgs(argKey)

        // We are getting back our arguments we passed AppDialog.buildDialog and
        // the type is guaranteed to be the same. Silence this warning
        @Suppress("UNCHECKED_CAST")
        return (builder as AppDialogLambda<T>)(this, params as T?)
    }

    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        val argKey = requireArguments().getInt("key")
        deleteDialogArgs(argKey)
    }
}

示例用法():

        val info = mapOf("message" to "${error.description}\n\nPlease check your Internet connection and try again.")
        AppDialog.buildDialog(info) { fragment, params ->
            fragment.isCancelable = false // since we are in a DialogFragment
            AlertDialog.Builder(fragment.context)
                .setTitle("Terms Of Service Failed To Load")
                .setMessage(params!!["message"])
                .setPositiveButton("Retry") { _, _ ->
                    // Update the view model instead of calling UserTOSFragment directly 
                    // as the fragment may be destroyed and recreated
                    // on configuration changes. The viewModel will stay alive.
                    viewModel.onTermsOfServiceReload()
                }
                .setNegativeButton("Cancel") { _, _ ->
                    viewModel.onTermsOfServiceDeclined()
                    fragment.findNavController().popBackStack()
                }.create()
        }.show(parentFragmentManager, "TOS Failed Dialog")

示例用法(之前):不使用 DialogFragment(出于说明目的,不要这样做,这是不好的做法,因为对话框将在配置更改时被破坏),UserTOSFragment.kt 中的代码 - 注意用于直接调用 UserTOSFragment 的代码。 loadContent() 重试。在上面的示例中,这必须重写为调用 viewModel.onTermsOfServiceDeclined() :

        AlertDialog.Builder(context)
            .setTitle("Terms Of Service Failed To Load")
            .setMessage("${error.description}\n\nPlease check your Internet connection and try again.")
            .setPositiveButton("Retry") { _, _ ->
                loadContent()
            }
            .setCancelable(false)
            .setNegativeButton("Cancel") { _, _ ->
                viewModel.onTermsOfServiceDeclined()
                findNavController().popBackStack()
            }
            .show()
于 2021-11-13T21:06:16.230 回答
-3

只是将它作为选项之一(因为还没有人提到它) - 您可以使用像 Otto 这样的事件总线。所以在对话框中你这样做:

bus.post(new AnswerAvailableEvent(42));

并让您的调用者(活动或片段)订阅它:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
于 2017-09-20T16:18:36.923 回答