方向更改时的 DialogFragment 回调

Posted

技术标签:

【中文标题】方向更改时的 DialogFragment 回调【英文标题】:DialogFragment callback on orientation change 【发布时间】:2012-10-01 02:18:03 【问题描述】:

我正在迁移我目前使用Activity.showDialog(DIALOG_ID); 的对话框,以使用android reference 中讨论的DialogFragment 系统。

在我的开发过程中使用回调将某些事件传递回打开对话框的活动/片段时出现了一个问题:

这是一个简单对话框的示例代码:

public class DialogTest extends DialogFragment 

public interface DialogTestListener 
    public void onDialogPositiveClick(DialogFragment dialog);


// Use this instance of the interface to deliver action events
static DialogTestListener mListener;

public static DialogTest newInstance(Activity activity, int titleId, int messageId) 
    udateListener(activity);
    DialogTest frag = new DialogTest();
    Bundle args = new Bundle();
    args.putInt("titleId", titleId);
    args.putInt("messageId", messageId);
    frag.setArguments(args);
    return frag;


public static void udateListener(Activity activity) 
    try 
        // Instantiate the NoticeDialogListener so we can send events with it
        mListener = (DialogTestListener) activity;
     catch (ClassCastException e) 
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(activity.toString() + " must implement DialogTestListener");
    



@Override
public Dialog onCreateDialog(Bundle savedInstanceState) 
    int titleId = getArguments().getInt("titleId");
    int messageId = getArguments().getInt("messageId");

    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // dialog title
    builder.setTitle(titleId);
    // dialog message
    builder.setMessage(messageId);

    // dialog negative button
    builder.setNegativeButton("No", new OnClickListener() 
               public void onClick(DialogInterface dialog, int id) );
    // dialog positive button
    builder.setPositiveButton("Yes", new OnClickListener() 
        public void onClick(DialogInterface dialog, int id) 
            mListener.onDialogPositiveClick(DialogTest.this);
        );

    // create the Dialog object and return it
    return builder.create();

下面是一些调用它的活动代码:

public class SomeActivity extends FragmentActivity implements DialogTestListener 
private EditText mUserName;
@Override
public void onCreate(Bundle savedInstanceState) 
    // setup ui
    super.onCreate(savedInstanceState);
    setContentView(R.layout.ui_user_edit);
    // name input
    mUserName = (EditText) findViewById(R.id.userEdit_editTextName);


@Override
public void onDialogPositiveClick(DialogFragment dialog) 
    Log.d(TAG, this.toString());
    mUserName.setText(mUserName.getText() + "1");


private void showDialog() 
    DialogTest test = DialogTest.newInstance(SomeActivity.this, R.string.someTitleText, R.string.someMessageText);
    test.show(getSupportFragmentManager(), "testDialog");

代码几乎就是您所看到的参考。问题是,一旦您进行方向更改,当显示一个对话框时,它就会停止按预期工作-->由于活动生命周期,活动和对话框都被重建,对话框现在没有正确的引用新的重建活动。

我在我的活动 onResume 方法中添加了以下代码:

    @Override
protected void onResume() 
    super.onResume();
    DialogTest.udateListener(this);

这样做,我得到了预期的行为,当方向改变发生时,对话框将事件发送回新的重建活动。

我的问题是: 处理在方向更改期间由 FragmentActivity 打开的 DialogFragment 之间的回调的“最佳做法”是什么?

最好的问候

【问题讨论】:

听起来你已经陷入了许多 Fragment 陷阱。我遇到了和你一样的问题,我在阅读这篇优秀的文章时能够解决它:code.hootsuite.com/orientation-changes-on-android。 【参考方案1】:

有更好的解决方案,而不是使用静态方法和变量,因为它只适用于对话框的一个实例。最好将回调存储为非静态成员

private DialogTestListener mListener;
public void setListener (DialogTestListener listener)
  mListener = listener;

那么你应该使用像这样mDialogFragment.show(getSupportFragmentManager(), DIALOG_TAG);这样的标签来显示你的对话框

然后在你的活动的onResume 方法中你可以重置你的监听器

protected void onResume() 
   super.onResume();
   mDialogFragment = (CMFilterDialogFrg) getSupportFragmentManager().findFragmentByTag(DIALOG_TAG);
   if(mDialogFragment  != null)
       mDialogFragment.setListener(yourListener)
   

【讨论】:

谢谢 - 这个方法对我有用!我第一次使用带有回调的对话框片段。我为此挣扎了一段时间-谢天谢地,我看到了这篇文章。我自己就快到了,但这篇文章帮助我们找到了解决方案 +1! Jason Long 如果您从 Activity 调用对话框,则提供最佳解决方案,如果您从 Fragment 调用,则 cuasodayleo 提供最佳解决方案,如果您需要面对这两种情况,这是最佳解决方案。 非常感谢..这很容易实现和直接 这不会在方向更改后继续存在。 getSupportFragmentManager().findFragmentByTag(DIALOG_TAG); 将为空【参考方案2】:

是的,这是我自己一直陷入的常见陷阱。首先让我说您在onResume() 中调用DialogTest.udateListener() 的解决方案似乎完全适合我。

另一种方法是使用ResultReceiver,它可以序列化为Parcelable

public class DialogTest extends DialogFragment 

public static DialogTest newInstance(ResultReceiver receiver, int titleId, int messageId) 
    DialogTest frag = new DialogTest();
    Bundle args = new Bundle();
    args.putParcelable("receiver", receiver);
    args.putInt("titleId", titleId);
    args.putInt("messageId", messageId);
    frag.setArguments(args);
    return frag;


@Override
public Dialog onCreateDialog(Bundle savedInstanceState) 
    int titleId = getArguments().getInt("titleId");
    int messageId = getArguments().getInt("messageId");
    ResultReceiver receiver = getArguments().getParcelable("receiver");

    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // dialog title
    builder.setTitle(titleId);
    // dialog message
    builder.setMessage(messageId);

    // dialog negative button
    builder.setNegativeButton("No", new OnClickListener() 
        public void onClick(DialogInterface dialog, int id) 
            receiver.sendResult(Activity.RESULT_CANCEL, null);
        );
    // dialog positive button
    builder.setPositiveButton("Yes", new OnClickListener() 
        public void onClick(DialogInterface dialog, int id) 
            receiver.sendResult(Activity.RESULT_OK, null);
        );

    // create the Dialog object and return it
    return builder.create();

然后您可以像这样处理 Receiver 中的所有内容:

protected void onReceiveResult(int resultCode, Bundle resultData) 
    if (getActivity() != null)
        // Handle result
    

查看ResultReceiver doesn't survire to screen rotation 了解更多详情。所以最后你可能仍然需要用你的Activity 重新连接ResultReceiver。唯一的区别是您将ActivityDialogFragment 解耦。

【讨论】:

感谢您的意见!现在我将使用onResume() 解决方案,一旦我需要一些 ActivityDialogFragment 解耦,我将看看 ResultReceiver。 查看 Fragment#setRetainInstance(boolean) 并通读文档。这实际上是我们真正在寻找的解决方案。虽然 Activity 仍在被销毁和重新创建,但 Fragment 正在被保留和重用。因此,回调 DialogTestListener 仍然指向正确的对象,您不必在配置更改后重新连接 Fragments。 此解决方案的问题是,如果您激活“不保留活动”打开对话框,然后按 HOME 并从主页启动器打开应用程序,将重新创建对话框,您将获得BadParcelableException,我正在为此苦苦挣扎。 非常干净的解决方案;以前没有听说过 ResultReceiver。【参考方案3】:

虽然 André 的解决方案有效,但更好的解决方案是在您的 Fragment 中获取 onAttach() 期间的更新活动。

private DialogTestListener mListener;

@Override
public void onAttach(Activity activity) 
    super.onAttach(activity);
    mListener = (DialogTestListener) activity;

使用此解决方案,您将不再需要在 newInstance() 中传递 Activity。您只需要确保拥有您的FragmentActivityDialogTestListener。您也不需要像ResultReceiver 解决方案那样保存状态。

【讨论】:

【参考方案4】:

首先,从FragmentParent 调用setTargetFragment 以启动dialogFragment。在dialogFragment 中使用getTargetFragment 回调片段并返回数据。所有数据结果将在onactivityresultFragmentParent 中执行

点击此链接: Receive result from DialogFragment

【讨论】:

虽然这是正确的,但它对问题没有任何帮助。因为数据传输和轮换后恢复回调是不同的事情。例如,在回调中我可以打开另一个活动或发出请求。【参考方案5】:

另一种方法是您可以停止重新创建活动。您必须告诉 Android 您将自己处理方向更改,并且 android 不会重新创建您的活动。您需要将此活动添加到清单文件中:

android:configChanges="keyboardHidden|orientation"

如果不是这样,那么您可以按照 Google 的建议使用标准 onSaveInstanceState() 来保存您的状态并使用 savedInstanceState 进行恢复。

这是 Google 的官方指南: http://developer.android.com/guide/components/activities.html#Lifecycle

如果您还没有,请仔细阅读。它会真正帮助您进行 android 开发。

【讨论】:

我不想停止在方向更改期间销毁和重新创建的活动,它在这方面按预期工作。我只想知道在方向更改期间处理由 FragmentActivity 打开的 DialogFragment 之间的回调的最佳实践。

以上是关于方向更改时的 DialogFragment 回调的主要内容,如果未能解决你的问题,请参考以下文章

重叠DialogFragment,在方向更改时以错误的顺序重新创建

DialogFragment 方向更改崩溃与 getActivity()

从 DialogFragment 回调片段

android DialogFragment 回调到 Fragment

依赖属性收到 xaml 更改时的回调

Android - 查看子项数量更改时的侦听器/回调