对话框是提示用户做出决定或输入额外信息的小窗口。对话框不会填充屏幕,通常用于需要用户采取行动才能继续执行的模式事件。本篇我们主要使用对话框对我们的应用进行进一步的功能完善。

官方推荐的使用方法是将DialogFragment 作为对话框的容器。DialogFragment 类提供创建对话框和管理其外观所需的所有控件,而非调用 Dialog 对象上的方法。使DialogFragment 管理对话框可确保它能正确处理生命周期事件,如用户按“返回”按钮或旋转屏幕时。此外,DialogFragment 类还允许以嵌入式组件的形式在较大界面中重复使用对话框的界面,类似于传统的 Fragment。

使用对话框可以分为下面几个步骤:

创建对话框片段

通过扩展 DialogFragment 并在 onCreateDialog() 回调方法中创建 AlertDialog。

public class DatePickerFragment extends DialogFragment {

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.date_picker_title)
                .setPositiveButton(R.string.date_picker_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                    }
                })
                .setNegativeButton(R.string.date_picker_cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                    }
                });
        return builder.create();
    }
}

Android有3种可用于对话框的按钮: positive按钮、 negative按钮以及neutral按钮。用户点击positive按钮接受对话框展现信息。如果同一对话框上放置有多个按钮,按钮的类型与命名决定着它们在对话框上显示的位置。)

显示对话框

DialogFragment实例也是由托管activity的FragmentManager管理的。要将DialogFragment添加给FragmentManager管理并放置到屏幕上,可调用fragment实例的public void show(FragmentManager manager, String tag)方法。

在NoteItemFragment中首先为String tag参数创建一个静态常量:

private static final String DIALOG_DATE = "DialogDate";

稍微调整下布局,将显示日记的TextView修改为Button。

<Button
        android:id="@+id/btn_note_create_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="创建日期"
        app:layout_constraintTop_toBottomOf="@id/et_note_title" />

接下来我们需要给显示日期的按钮添加点击事件完成点击创建对话框。

@Override
    protected void initFragmentListener() {

        tvNodeCreateDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                new DatePickerFragment().show(fragmentManager,DIALOG_DATE);
            }
        });

     }

运行程序:

android AlertDialog显示在底部 android dialogfragment_点击事件

android AlertDialog显示在底部 android dialogfragment_ide_02

创建自定义布局

如果想让对话框拥有自定义布局,那么需要我们创建自定义的布局,然后通过调用 AlertDialog.Builder 对象上的 setView(),将该布局添加至 AlertDialog。默认情况下,自定义布局会填充对话框窗口,但仍可使用 AlertDialog.Builder 方法来添加按钮和标题。

添加布局文件dialog_date_picker.xml:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <DatePicker
        android:id="@+id/dlg_date_picker"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:calendarViewShown="false" />

</androidx.constraintlayout.widget.ConstraintLayout>

要扩展 DialogFragment 中的布局,首先使用getLayoutInflater() 获取一个 LayoutInflater 并调用 inflate(),其中第一个参数是布局资源 ID,第二个参数是布局的父视图。然后,调用 setView(),将布局放入对话框中。

修改DatePickerFragment代码如下:

public class DatePickerFragment extends DialogFragment {

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

         LayoutInflater inflater = requireActivity().getLayoutInflater();
        View view = inflater.inflate(R.layout.dialog_date_picker, null);

        builder.setView(view)
                .setMessage(R.string.date_picker_title)
                .setPositiveButton(R.string.date_picker_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                    }
                })
                .setNegativeButton(R.string.date_picker_cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                    }
                });
        return builder.create();
    }
}

运行效果:

android AlertDialog显示在底部 android dialogfragment_ide_03

设置DatePicker初始值

每篇日记记录都有一个创建时间,我们期望打开DatePicker的时候,显示的是我们创建日期的时间,和前面一样,我们在fragment中添加newInstance方法,将参数保存在DatePickerFragment的argument bundle中,这样DatePickerFragment就能直接获取。

public static final String DATE_PICKER_ARGS = "date_picker_args";

    private DatePicker datePicker;

 
    public static DatePickerFragment newInstance( Date date) {
        Bundle args = new Bundle();
        args.putSerializable(DATE_PICKER_ARGS, date);
        DatePickerFragment fragment = new DatePickerFragment();
        fragment.setArguments(args);
        return fragment;
    }

然后我们需要替换NoteItemFragment中DatePickerFragment 的构造方法:

btnNodeCreateDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                DatePickerFragment.newInstance(noteItem.getDateCreate()).show(fragmentManager,DIALOG_DATE);
            }
        });

DatePickerFragment使用Date中的信息来初始化DatePicker对象,首先要创建一个Calendar对象,然后用Date对象进行初始化,再从Calendar对象中取回所需信息。

修改DatePickerFragment的onCreateDialog方法:

@NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        LayoutInflater inflater = requireActivity().getLayoutInflater();
        View view = inflater.inflate(R.layout.dialog_date_picker, null);

        Date date = (Date) getArguments().getSerializable(DATE_PICKER_ARGS);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        datePicker = view.findViewById(R.id.dlg_date_picker);
        datePicker.init(year, month, day, null);

        builder.setView(view)
        .......

其中init 方法原型如下:

void init (int year, 
                int monthOfYear, 
                int dayOfMonth, 
                DatePicker.OnDateChangedListener onDateChangedListener)
将事件传递回对话框的宿主

当用户轻触对话框的某个操作按钮或从列表中选择某一项时,alogFragment 可能会自行执行必要操作,但通常我们想将事件传递给打开该对话框的 Activity 或片段。为此,可以为每种点击事件定义带有方法的接口。然后,在从该对话框接收操作事件的宿主组件中实现该接口。

为DatePickerFragment的Button设置点击事件的接口,

首先我们需要在DatePickerFragment声明接口定义:

private NoticeDialogListener noticeDialogListener;

    public interface NoticeDialogListener {
        public void onDialogPositiveClick(Date date);

        public void onDialogNegativeClick();
    }
  public void setNoticeDialogListener(NoticeDialogListener noticeDialogListener) {
        this.noticeDialogListener = noticeDialogListener;
    }

这里我们需要传递用户选择的日期,所以直接在fragment中给处理后直接传递给宿主。添加设置NoticeDialogListener 的public方法

对话框fragment可使用接口回调方法向 或者fragment传递点击事件:

builder.setView(view)
                .setMessage(R.string.date_picker_title)
                .setPositiveButton(R.string.date_picker_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        Date date = new Date();
                        noticeDialogListener.onDialogPositiveClick(date);
                    }
                })
                .setNegativeButton(R.string.date_picker_cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        noticeDialogListener.onDialogNegativeClick();
                    }
                });

我们先来运行一下,这里看看如果宿主Activity不实现对应的接口,会不会如我们所想抛出异常:

android AlertDialog显示在底部 android dialogfragment_点击事件_04


OK,那我们就去宿主NoteItemFragment中去实现该接口:

public class NoteItemFragment extends BaseFragment implements DatePickerFragment.NoticeDialogListener {
@Override
    public void onDialogPositiveClick(Date date) {
        Log.d(TAG, "onDialogPositiveClick: " + date.toString());
    }

    @Override
    public void onDialogNegativeClick() {
        Log.d(TAG, "onDialogNegativeClick: ");
    }

接着我们需要在NoteItemFragment 创建DatePickerFragment的时候设置NoticeDialogListener ,因为NoteItemFragment 实现了该接口,所以可以直接传this。

btnNodeCreateDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                DatePickerFragment datePickerFragment =  DatePickerFragment.newInstance(noteItem.getDateCreate());
                datePickerFragment.setNoticeDialogListener(NoteItemFragment.this);
                datePickerFragment.show(fragmentManager, DIALOG_DATE);
            }
        });

运行程序:

android AlertDialog显示在底部 android dialogfragment_点击事件_05

android AlertDialog显示在底部 android dialogfragment_android_06

除了实现接口之外,我们其实还有一种更加方便的方法。

使用setTargetFragment设置目标fragment

首先我们先来修改NoteItemFragment:

private static final int DATE_PICKER_REQUEST = 0;
  ...
 btnNodeCreateDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                DatePickerFragment datePickerFragment = DatePickerFragment.newInstance(noteItem.getDateCreate());
                //datePickerFragment.setNoticeDialogListener(NoteItemFragment.this);
                datePickerFragment.setTargetFragment(NoteItemFragment.this, DATE_PICKER_REQUEST);
                datePickerFragment.show(fragmentManager, DIALOG_DATE);
            }
        });

其中setTargetFragment使用如下:

android AlertDialog显示在底部 android dialogfragment_点击事件_07


在代码中我们使用setTargetFragment将NoteItemFragment设置为DatePickerFragment 目标fragment,指定请求码用于区别是哪一个目标fragment。

设置完目标fragment后,我们需要在目标fragment中实现onActivityResult方法来接收DatePickerFragment 返回的数据。

传递数据给目标fragment

要想传递数据,其实质上是获取到目标fragment,然后调用fragment的onActivityResult方法来实现。

public static final String DATE_PICKER_EXTRA = "com.qiushangge.likenotes.fragment.date_picker_extra";

getTargetFragment用来获取目标fragment

android AlertDialog显示在底部 android dialogfragment_android_08

getTargetRequestCode用来获取请求码。

android AlertDialog显示在底部 android dialogfragment_ide_09

ok,接下来就是在PositiveButton和NegativeButton的click事件中返回修改后的时间。

builder.setView(view)
                .setMessage(R.string.date_picker_title)
                .setPositiveButton(R.string.date_picker_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        int year = datePicker.getYear();
                        int month = datePicker.getMonth();
                        int day = datePicker.getDayOfMonth();
                        Date date = new GregorianCalendar(year, month, day).getTime();

                        if (getTargetFragment() == null) {
                            return;
                        }
                        Intent intent = new Intent();
                        intent.putExtra(DATE_PICKER_EXTRA, date);
                        getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, intent);
                        // noticeDialogListener.onDialogPositiveClick(date);
                    }
                })
                .setNegativeButton(R.string.date_picker_cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        Intent intent = new Intent();
                        getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, intent);
                    }
                });

最后一步就是在NoteItemFragment中接收传递的参数了:

@Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (resultCode != Activity.RESULT_OK) {
            return;
        }
        if (requestCode == DATE_PICKER_REQUEST) {
            Date date = (Date) data.getSerializableExtra(DatePickerFragment.DATE_PICKER_EXTRA);
            noteItem.setDateCreate(date);
            btnNodeCreateDate.setText(noteItem.getDateCreate().toString());
        }
    }