Android DialogFragment实现底部弹出菜单效果

底部弹出式菜单, 可以使用PopupWindow来做,也可以用自定义View来做。当然这里采用DialogFragment来做。

DialogFragment是3.0之后引入的,使用DialogFragment,我们不用管理其生命周期,并且可以作为组件重用。比如当屏幕旋转的时候,如果PopupWindow没有dismiss掉,会抛出异常。AlertDialog则会消失,DialogFragment创建的对话框则不受影响。

概述

使用DialogFragment,需要重写onCreateView或者onCreateDialog方法,前者是通过layout下的自定义布局来创建对话框,后者则是用AlertDialog或者Dialog创建出Dialog,适用于创建简单的对话框。

如果同时复写onCreateViewonCreateDialog会报如下异常,

AndroidRuntimeException: requestFeature() must be called before adding content

通过查看DialogFragment的源码,我们发现会有下面的注释

     * This method will be called after {@link #onCreate(Bundle)} and
     * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.  The
     * default implementation simply instantiates and returns a {@link Dialog}
     * class.

那这句异常的意思是什么呢?具体可以参见stackoverflow:
http://stackoverflow.com/questions/13257038/custom-layout-for-dialogfragment-oncreateview-vs-oncreatedialog/15602648#15602648
http://stackoverflow.com/questions/27045451/dialog-fragment-is-crashing

You can override both (in fact the DialogFragment says so), the problem comes when you try to inflate the view after having already creating the dialog view. You can still do other things in onCreateView, like use the savedInstanceState, without causing the exception.

可以看出onCreateDialog优先于onCreateView执行,如果我们复写了这两个方法,那么对话框是在onCreateDialog中创建的,但是我们依然可以在onCreateView中做状态保存等操作。

onCreateDialog

这个回调方法是DialogFragment独有的,通过它返回的是一个Dialog对象,这个对象就会被显示到屏幕上。

AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setView(R.layout.dialog_fragment_item);
                builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
                builder.show();

Activity中我们可以通过如下两种方式将对话框展示出来

 BottomDialogFragment bottomDialogFragment = (BottomDialogFragment) Fragment.instantiate(this, BottomDialogFragment.class.getName());
                getSupportFragmentManager().beginTransaction().add(bottomDialogFragment, "bottomDialogFragment").commitAllowingStateLoss();
                break;

或者:

BottomDialogFragment dialog = new BottomDialogFragment();  
        dialog.show(getSupportFragmentManager(), "bottomDialogFragment");

onCreateView

通过onCreateView自定义布局展示对话框。生命周期同Fragment,同时,支持FragmentManager 事务

  • 布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="30dp"
    android:orientation="horizontal">

    <ImageView
        android:id="@android:id/icon"
        android:layout_width="50dp"
        android:layout_height="match_parent"
        android:adjustViewBounds="true"
        android:padding="5dp"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@android:id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ellipsize="marquee"
        android:padding="10dp"
        android:singleLine="true"
        android:text="@string/image_content"
        android:textSize="15sp" />
LinearLayout>
  • java代码,
    继承DialogFragment,重写onCreateView方法
@Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        if (null == fragmentRoot) {
            fragmentRoot = inflater.inflate(R.layout.dialog_fragment_item, container, false);
        }

        if (null != fragmentRoot) {
            ViewGroup parent = (ViewGroup) fragmentRoot.getParent();
            if (null != parent)
                parent.removeAllViews();
        }

        return fragmentRoot;
    }

去标题

去标题有两种方式,通过 代码 或者 通过 theme

  • 代码设置,需要用到DialogFragment.STYLE_NO_TITLE

  • 主题设置,需要在style.xml中使用NoActionBar属性。

 <item name="windowNoTitle">trueitem>

注意

一. 如果使用onCreateDialog 创建对话框时,可以通过如下方式设置style

AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.BottomDialog);

同时,可以在onCreateDialog 通过Dialog 对象获取Window 对象,并设置相关属性

dialog = builder.create();
Window window = dialog.getWindow();

二. 如果使用 onCreateView 创建对话框,则设置 style 的方式将有所变化.

  • 必须在onCreateView 中获取window 对象,并设置Window 的相关属性,在onCreate 中设置无效
 getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
  • 必须在onCreate 中设置Style ,而在OnCreateView 中设置无效,因为此时对话框已经init
 setStyle(DialogFragment.STYLE_NO_TITLE, R.style.BottomDialog);

位置控制

像很多UI都采用底部弹出的效果,比如展示菜单,分享等操作,我们都知道Dialog是展示在屏幕中央,而且宽度是没有填充屏幕的,其实我们只要给dialog设置一个LayoutParams即可。

ps:

 @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.BottomDialog);
        LayoutInflater inflater = getActivity().getLayoutInflater();
        View view = inflater.inflate(R.layout.dialog_fragment_layout, null);

        initView(view);

        builder.setView(view);

        dialog = builder.create();

        dialog.setCanceledOnTouchOutside(true);

        // 设置宽度为屏宽、靠近屏幕底部。
        Window window = dialog.getWindow();
        WindowManager.LayoutParams wlp = window.getAttributes();
        wlp.gravity = Gravity.BOTTOM;
        window.setAttributes(wlp);

        return dialog;
    }

上面是通过设置了Gravity.BOTTOM来实现在屏幕下方显示。默认情况下DialogFragment是现实在屏幕中间的,我们如果想要改变其现实位置,同理,也可以用此方法。

比如,在屏幕中间靠上显示,可以这样设置。

        Window window = dialog.getWindow();
        WindowManager.LayoutParams wlp = window.getAttributes();
        wlp.gravity = Gravity.TOP;
        // 这里是坐标值,即离屏幕上方距离是100
        wlp.y = 100;
        window.setAttributes(wlp);

我们发现在DialogFragment 弹出的时候,左右两边会留白,这是所有Dialog
都有的,本来试过通过LayoutParams控制,当时失败,之后找到了解决办法
相关代码

@Override public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (null != dialog) {
            dialog.getWindow().setLayout(-1, -2);
        }
    }

DialogFragment动画

这里涉及到了一个theme,主要是设置动画的,dialog自下而上的弹出来


    <style name="BottomDialog" parent="AppTheme">
        <item name="android:windowNoTitle">trueitem>
        <item name="android:windowIsFloating">trueitem>
        -- Dialog进入及退出动画 -->
        <item name="android:windowAnimationStyle">@style/BottomToTopAnim

    style>
<style name="BottomToTopAnim" parent="android:Animation">
        <item name="@android:windowEnterAnimation">@anim/bottomview_anim_enter
        "@android:windowExitAnimation">@anim/bottomview_anim_exit
    style>

enter动画:


<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromYDelta="100%p"
        android:toYDelta="0%p" />

    <alpha
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
set>

exit动画:


<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromYDelta="0%p"
        android:toYDelta="100%p" />

    <alpha
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromAlpha="1.0"
        android:toAlpha="0.3" />
set>

防止重复弹出

我们都知道,如果我们点击一个按钮弹出一个对话框,如果每次都是新建Dialog ,则会出现重叠现象,在FragmentManager中有一个方法isAdded()可以用来判断此 Dialog是否被添加,同时,为了减少相同Dialog 的创建,我们并不需要每次都new 一个出来,并通过isAdded()来判断是否添加,减少不必要的消耗

 public static BottomDialogFragment showDialog(AppCompatActivity appCompatActivity) {
        FragmentManager fragmentManager = appCompatActivity.getSupportFragmentManager();
        BottomDialogFragment bottomDialogFragment =
            (BottomDialogFragment) fragmentManager.findFragmentByTag(TAG);
        if (null == bottomDialogFragment) {
            bottomDialogFragment = newInstance();
        }

        if (!appCompatActivity.isFinishing()
            && null != bottomDialogFragment
            && !bottomDialogFragment.isAdded()) {
            fragmentManager.beginTransaction()
                .add(bottomDialogFragment, TAG)
                .commitAllowingStateLoss();
        }

        return bottomDialogFragment;
    }

实例demo:
BottomDialogFragment @[Github]

你可能感兴趣的:(Android基础)