咱們平時在作普通頁面的時候,當 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_content
。github
咱們知道,一個 dialog 對應着一個 window, 而 window 有一個神奇的屬性:isFloating
。當 isFloating
爲 true 時,dialog contentView 的 寬高被重置爲 wrap_content
,否者重置爲 match_parent
。app
讓咱們爲 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, 來解決如下問題:
定義 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 問題操心。