我将 DialogFragments 用于许多事情:从列表中选择项目,输入文本。
将值(即列表中的字符串或项目)返回给调用活动/片段的最佳方法是什么?
目前我正在使调用活动实现 DismissListener
并为 DialogFragment 提供对该活动的引用。 Dialog 然后调用 Activity 中的 OnDimiss
方法,Activity 从 DialogFragment 对象中获取结果。非常混乱,它在配置更改(方向更改)上不起作用,因为 DialogFragment 失去了对活动的引用。
谢谢你的帮助。
Activity
和 DialogFragment
都可能被重新创建。使用传递给 onAttach(Activity activity)
的 Activity
是正确且推荐的方法。
从您显示对话框的位置使用 myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)
,然后当您的对话框完成时,您可以从中调用 getTargetFragment().onActivityResult(getTargetRequestCode(), ...)
,并在包含的片段中实现 onActivityResult()
。
这似乎是对 onActivityResult()
的滥用,尤其是因为它根本不涉及活动。但是我已经看到了官方谷歌人推荐的,甚至可能在 api 演示中。我认为这是添加 g/setTargetFragment()
的目的。
正如您所看到的 here,有一种非常简单的方法可以做到这一点。
在您的 DialogFragment
中添加一个接口侦听器,例如:
public interface EditNameDialogListener {
void onFinishEditDialog(String inputText);
}
然后,添加对该侦听器的引用:
private EditNameDialogListener listener;
这将用于“激活”侦听器方法,并检查父 Activity/Fragment 是否实现此接口(见下文)。
在“调用”DialogFragment
的 Activity
/FragmentActivity
/Fragment
中,只需实现此接口即可。
在您的 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");
}
}
这种技术非常灵活,即使您还不想关闭对话框,也可以使用结果进行回调。
Activity
和 FragmentActivity
,但如果调用者是 Fragment
?
Fragment
,它的工作方式相同。
Fragment
,那么您可以执行以下操作: 1. 将片段作为引用传递(可能不是一个好主意,因为您可能会导致内存泄漏)。 2. 使用 FragmentManager
并调用 findFragmentById
或 findFragmentByTag
它将获取您的活动中存在的片段。我希望它有所帮助。祝你有美好的一天!
setTargetFragment()
和 getTargetFragment()
很神奇。
有一种更简单的方法可以从 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 中,您只需在 dismiss()
之前直接添加一行,以便将 resultCode 发送回 onActivityResult()。
getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();
而已。请注意,resultCode 定义为 int resultCode
,在本例中我设置为 resultCode = 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);
}
然后,当您准备好在 DialogFragment
运行完毕后将代码发送回 Activity
时,您只需在 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()
方法一样使用此接口方法。除了接口方法用于DialogFragments
,另一个用于Fragments
。
dialogFrag.setTargetFragment(this, 1)
,但此方法接收 Fragment 作为第一个参数,因此无法强制转换。我对吗 ?
@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
好吧,回答可能为时已晚,但这是我为从 DialogFragment
获取结果所做的工作。与@brandon 的回答非常相似。在这里,我从片段中调用 DialogFragment
,只需将此代码放在您调用对话框的位置。
FragmentManager fragmentManager = getFragmentManager();
categoryDialog.setTargetFragment(this,1);
categoryDialog.show(fragmentManager, "dialog");
其中 categoryDialog
是我要调用的 DialogFragment
,然后在您的 dialogfragment
实现中将此代码放置在您要设置数据的位置。 resultCode
的值为 1,您可以设置它或使用系统定义。
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();
}
对于仍在阅读本文的任何人:setTargetFragment()
已被弃用。现在建议像这样使用 FragmentResultListener
API:
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()
}
parentFragmentManager
很重要。很容易意外发送 childFragmentManager
不会触发 setFragmentResultListener lambda
不同的方法,以允许 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
public void onAttach
并在那里进行活动投射
我发现的一种简单方法如下:实现这是您的 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 是为了证明它有效。为我工作。
Interface
而不是与具体类硬耦合。
看到没有人建议使用本地广播进行 DialogFragment
到 Activity
通信,我感到非常惊讶!我发现它比其他建议更简单、更干净。本质上,您注册您的 Activity
以收听广播并从您的 DialogFragment
实例发送本地广播。简单的。有关如何进行全部设置的分步指南,请参阅 here。
或共享 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
在我的情况下,我需要将参数传递给 targetFragment。但我得到了异常“片段已经激活”。所以我在我的 DialogFragment 中声明了一个由 parentFragment 实现的接口。当 parentFragment 启动一个 DialogFragment 时,它将自己设置为 TargetFragment。然后在 DialogFragment 我打电话
((Interface)getTargetFragment()).onSomething(selectedListPosition);
在科特林
// 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()
}
}
我希望它有用,如果你可以改进,请编辑它。我的英文不是很好
如果你想发送参数并从第二个片段接收结果,你可以使用 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");
}
在对话片段上
class AbcDialogFragment(private val ondata: (data: String) -> Unit) : DialogFragment() {}
从片段/活动显示对话框的代码
val abcDialogFragment = AbcDialogFragment(ondata = {data-> })
abcDialogFragment.show(requireActivity().supportFragmentManager, "TAG")
在对话框片段中,您可以在对话框片段关闭或任何点击侦听器时调用 onData。
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()
只是将它作为选项之一(因为还没有人提到它) - 您可以使用像 Otto 这样的事件总线。所以在对话框中你这样做:
bus.post(new AnswerAvailableEvent(42));
并让您的调用者(活动或片段)订阅它:
@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
// TODO: React to the event somehow!
}
setTargetFragment
现在已弃用,但替代品FragmentManager.setFragmentResultListener
(在 Pass data between fragments 中描述仍处于 alpha 阶段。