所見即所得 dialog

咱們平時在作普通頁面的時候,當 app 運行起來時,所看到的界面,每每就是咱們預覽 xml 佈局文件所看到的那樣,即所見即所得。但是若是這些佈局文件是放在 dialog 裏展現的,狀況就不同了,每每要煞費苦心,才能獲得咱們想要的效果。php

本文分享如何定義一個 BaseDialogFragment 來實現所見即所得的效果。文末還附有處理 dialog 中嵌套 Fragment,status bar 相關問題實踐方案。java

首先咱們建立一個 DialogFragmentandroid

public class MyDialogFragment extends DialogFragment {
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_dialog, container, false);
    }
}
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<!--fragment_dialog.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFF" android:gravity="center" android:orientation="vertical">
    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Hello Dialog" android:textSize="32dp" />
</LinearLayout>
複製代碼

咱們期待的結果是 dialog 充滿整個屏幕,而且 Hello Dialog 這幾個字居中顯示,但實際的結果是:git

咱們在根佈局設置的 layout 是 match_parent, 顯示出來的結果倒是 wrap_contentgithub

咱們知道,一個 dialog 對應着一個 window, 而 window 有一個神奇的屬性:isFloating。當 isFloating 爲 true 時,dialog contentView 的 寬高被重置爲 wrap_content,否者重置爲 match_parentapp

讓咱們爲 dialog 自定義主題,來改變這個值:ide

<!-- styles.xml -->
<resources>
    <style name="FullScreenDialog" parent="Theme.AppCompat.Dialog"> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">false</item> <item name="android:windowBackground">@android:color/transparent</item> </style>
</resources>
複製代碼

在 MyDialogFragment 中應用這個主題佈局

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setStyle(DialogFragment.STYLE_NORMAL, R.style.FullScreenDialog);
}
複製代碼

跑起來看看:動畫

果真實現全屏了,可是有兩個問題,第一,狀態欄變黑色了,第二,'Hello Dialog' 不見了。ui

第一個問題咱們延後解決,先讓咱們來解決第二個問題。

目前,支持庫中存在一個錯誤,致使樣式沒法正常顯示。 能夠經過使用 Activity 的 inflater 來解決這個問題,更改 onCreateView 方法:

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return getActivity().getLayoutInflater().inflate(R.layout.fragment_dialog, container, false);
}
複製代碼

如今,Dialog 的樣式能正常顯示了,具體細節請參看 stackoverflow 這篇文章

如今讓咱們更改根佈局的 margin, 留出一些空間來顯示遮罩:

<?xml version="1.0" encoding="utf-8"?>
<!--fragment_dialog.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginLeft="32dp" android:layout_marginRight="32dp" android:layout_gravity="center" android:background="#FFFFFF" android:gravity="center" android:orientation="vertical">

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Hello Dialog" android:textSize="32dp" />

</LinearLayout>
複製代碼

跑起來看看,結果是使人失望的:

layout_height 不是 200dp, 而是 match_parent, 這是和 isFloating 這個屬性密切相關的。

如今咱們想到的一個解決方案是,在 LinearLayout 外再套一層 FrameLayout

<?xml version="1.0" encoding="utf-8"?>
<!--fragment_dialog.xml-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
    
    <LinearLayout android:layout_width="match_parent" android:layout_height="200dp" android:layout_gravity="center" android:layout_marginLeft="32dp" android:layout_marginRight="32dp" android:background="#FFFFFF" android:gravity="center" android:orientation="vertical">

        <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Hello Dialog" android:textSize="32dp" />

    </LinearLayout>
</FrameLayout>
複製代碼

如今,咱們獲得了預期效果:

可是點擊遮罩,dialog 並無消失,由於這個 dialog 其實是全屏的,並無 outside 能夠點擊。

如今開始封裝咱們的 BaseDialogFragment, 來解決如下問題:

  1. 不須要在正常的佈局外再套一層 FrameLayout
  2. 點擊遮罩,Dialog 能夠消失
  3. 解決黑色狀態欄的問題

定義 DialogFrameLayout,用來處理點擊遮罩的問題

public class DialogFrameLayout extends FrameLayout {

    interface OnTouchOutsideListener {
        void onTouchOutside();
    }

    GestureDetector gestureDetector = null;

    OnTouchOutsideListener onTouchOutsideListener;

    public void setOnTouchOutsideListener(OnTouchOutsideListener onTouchOutsideListener) {
        this.onTouchOutsideListener = onTouchOutsideListener;
    }

    public DialogFrameLayout(@NonNull Context context) {
        super(context);
        commonInit(context);
    }

    private void commonInit(@NonNull Context context) {
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDown(MotionEvent e) {
                return true;
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                Rect rect = new Rect();
                getHitRect(rect);
                int count = getChildCount();
                for (int i = count - 1; i > -1; i--) {
                    View child = getChildAt(i);
                    Rect outRect = new Rect();
                    child.getHitRect(outRect);
                    if (outRect.contains((int) e.getX(), (int) e.getY())) {
                        return false;
                    }
                }
                if (onTouchOutsideListener != null) {
                    onTouchOutsideListener.onTouchOutside();
                }
                return true;
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }
}
複製代碼

定義 DialogLayoutInflater, 讓咱們能夠再也不須要額外的 FrameLayout

public class DialogLayoutInflater extends LayoutInflater {

    private LayoutInflater layoutInflater;

    private DialogFrameLayout.OnTouchOutsideListener listener;

    public DialogLayoutInflater(Context context, LayoutInflater layoutInflater, DialogFrameLayout.OnTouchOutsideListener listener) {
        super(context);
        this.layoutInflater = layoutInflater;
        this.listener = listener;
    }

    @Override
    public LayoutInflater cloneInContext(Context context) {
        return layoutInflater.cloneInContext(context);
    }

    @Override
    public View inflate(int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        DialogFrameLayout dialogFrameLayout = new DialogFrameLayout(getContext());
        dialogFrameLayout.setOnTouchOutsideListener(listener);
        dialogFrameLayout.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));
        layoutInflater.inflate(resource, dialogFrameLayout, true);
        return dialogFrameLayout;
    }
}
複製代碼

編寫 BaseDialogFragment, 把一切鏈接起來:

public class BaseDialogFragment extends DialogFragment {

    @NonNull
    @Override
    public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
        setStyle(DialogFragment.STYLE_NORMAL, R.style.FullScreenDialog);

        super.onGetLayoutInflater(savedInstanceState);
        // 換成 Activity 的 inflater, 解決 fragment 樣式 bug
        LayoutInflater layoutInflater = getActivity().getLayoutInflater();
        if (!getDialog().getWindow().isFloating()) {
            setupDialog();
            layoutInflater = new DialogLayoutInflater(requireContext(), layoutInflater,
                    new DialogFrameLayout.OnTouchOutsideListener() {
                        @Override
                        public void onTouchOutside() {
                            if (isCancelable()) {
                                dismiss();
                            }
                        }
                    });
        }
        return layoutInflater;
    }

    protected void setupDialog() {
        Window window = getDialog().getWindow();
        // 解決黑色狀態欄的問題
        AppUtils.setStatusBarTranslucent(window, true);
        AppUtils.setStatusBarColor(window, Color.TRANSPARENT, false);

        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialogInterface, int keyCode, KeyEvent event) {
                if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                    if (isCancelable()) {
                        dismiss();
                    }
                    return true;
                }
                return false;
            }
        });
    }
}

複製代碼

就這樣,一個 BaseDialogFragment 封裝好了,MyDialogFragment 繼承 BaseDialogFragment, 便可實現所見即所得。

public class MyDialogFragment extends BaseDialogFragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // 注意,這裏再也不須要 getActivity().getLayoutInflater(), 由於 BaseDialogFragment 已經返回了正確的 inflater
        return inflater.inflate(R.layout.fragment_dialog, container, false);
    }
}
複製代碼

佈局文件也再也不須要在外面再套個 FrameLayout

<?xml version="1.0" encoding="utf-8"?>
<!--fragment_dialog.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="200dp" android:layout_gravity="center" android:layout_marginLeft="32dp" android:layout_marginRight="32dp" android:background="#FFFFFF" android:gravity="center" android:orientation="vertical">

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Hello Dialog" android:textSize="32dp" />

</LinearLayout>
複製代碼

一切正如期待的那樣,一切都變得簡單,只要關注佈局就能夠了。不過咱們能夠走得更遠:

當 Fragment 根佈局有 layout_gravity="bottom" 屬性時,自動附加 slide 動畫:

狀態欄花樣變幻以及 Fragment 嵌套

詳情請查看 AndroidNavigation。該庫不只處理了 Dialog 的問題,還處理了 Fragment 嵌套,嵌套 Fragment 懶加載,右滑返回,沉浸式狀態欄,Toolbar 等一系列問題,讓你能夠專一於業務,而無需爲導航等應用級 UI 問題操心。

相關文章
相關標籤/搜索