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 事务

  • 布局文件:
<?xml version="1.0" encoding="utf-8"?>
<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">true</item>
注意

一. 如果使用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自下而上的弹出来

<!--屏幕底部的dialog-->
    <style name="BottomDialog" parent="AppTheme">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">true</item>
        <!-- Dialog进入及退出动画 -->
        <item name="android:windowAnimationStyle">@style/BottomToTopAnim</item>

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

enter动画:

<?xml version="1.0" encoding="utf-8"?>
<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动画:

<?xml version="1.0" encoding="utf-8"?>
<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]

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当使用dialog实现loading时,每次dialog弹出时手机的状态栏会变成其他颜色,这会影响用户的感官体验。为了解决这个问题,你可以使用以下方法: 1. 首先,设置透明蒙层来解决状态栏变色的问题。你可以使用以下代码: ```java Window dialogWindow = mDialog.getWindow(); dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); dialogWindow.setBackgroundDrawableResource(android.R.color.transparent); WindowManager.LayoutParams lp = dialogWindow.getAttributes(); lp.dimAmount = 0.0f; dialogWindow.setAttributes(lp); ``` 2. 然后,当dialog初始化时调用`dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)`方法,这样dialog弹出时,状态栏的颜色将和Activity展示的状态栏保持一致,不会随着dialog弹出和销毁而改变。 然而,这种方法会引发另一个问题,就是当设置了透明背景后,状态栏的字体也会变成白色。如果Activity标题背景是白色的话,会造成冲突。为了解决这个问题,你可以尝试以下方法: 1. 在dialog弹出之前,将状态栏字体颜色设置为黑色。你可以使用以下代码: ```java dialogWindow.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); ``` 2. 在dialog关闭后,将状态栏字体颜色恢复为白色。你可以使用以下代码: ```java dialogWindow.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); ``` 这样就可以解决android dialogfragment弹出时状态栏变色的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值