前言
在聊DialogFragment之前,我們看看以往我們在Android里實現一個對話框一般有這幾種方式:
- Dialog 繼承重寫Dialog實現一個自定義的Dialog
- AlertDialog Android原生提供的對話框(底層是繼承Dialog實現)
- PopupWindow 用彈出懸浮框,實現對話框。這種對話框可以用在指定位置顯示,一般用於一些非常小的按鍵彈窗。怎么實現可以參考我的博客:https://www.cnblogs.com/guanxinjing/p/10156153.html
這3種彈窗對話框都有一個問題,就是與activity的生命周期不是捆綁的,得時刻注意在activity后台之后關閉Dialog。所以,后面google推薦使用DialogFragment來取代它們。DialogFragment本質其實是Fragment,有Fragment的生命周期並且與創建它的activity有捆綁,在google推出了Jetpack系列后,配合Jetpack系列的LiveData與navigation在使用上比一般的Dialog安全更多,並且在數據傳遞上也非常簡單,配合navigation架構管理起來也十分簡單明晰。
如果你未接觸過不了解Jetpack系列,可以參考我的博客:https://www.cnblogs.com/guanxinjing/category/1550385.html 了解完Jetpack系列,你就可以明白google為什么推出這種對話框了。
下面我們就根據2個最簡單demo和與一些使用特例,來介紹DialogFragment的使用。
以Dialog創建DialogFragment的簡單Demo
DialogFragment有2種方法創建我們需要的對話框內容,其中就有以Dialog來創建內容方式。
繼承重寫DialogFragment:
public class MyDialog1 extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { //創建對話框,我們需要返回dialog AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); dialog.setTitle("測試Dialog"); dialog.setMessage("DialogFragment"); return dialog.create(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); //此方法在視圖已經創建后返回的,但是這個view 還沒有添加到父級中,我們在這里可以重新設定view的各個數據 } }
在activity里顯示對話框:
mBtnTest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { myDialog = new MyDialog1(); myDialog.show(getSupportFragmentManager(),"myDialog"); } });
效果圖:
以布局View創建DialogFragment的簡單Demo
DialogFragment另一種創建內容方法,導入一個View
public class MyDialog extends DialogFragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.set_wifi_password_dialog_layout, container, false); return view; } @Override public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); /* 此方法在視圖View已經創建后返回的,但是這個view 還沒有添加到父級中。 我們在這里可以重新設定view的各個數據,但是不能修改對話框最外層的ViewGroup的布局參數。 因為這里的view還沒添加到父級中,我們需要在下面onStart生命周期里修改對話框尺寸參數 */ } @Override public void onStart() { /* 因為View在添加后,對話框最外層的ViewGroup並不知道我們導入的View所需要的的寬度。 所以我們需要在onStart生命周期里修改對話框尺寸參數 */ WindowManager.LayoutParams params = getDialog().getWindow().getAttributes(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; getDialog().getWindow().setAttributes((WindowManager.LayoutParams) params); super.onStart(); } }
顯示對話框的代碼跟上面的demo一樣,就不重復貼出來了,看看效果圖:
改變對話框的顯示位置
public class MyDialog2 extends DialogFragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.set_wifi_password_dialog_layout, container, false); return view; } @Override public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); } @Override public void onStart() { WindowManager.LayoutParams params = getDialog().getWindow().getAttributes(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.gravity = Gravity.BOTTOM; //將對話框放到布局下面,也就是屏幕下方 getDialog().getWindow().setAttributes((WindowManager.LayoutParams) params); super.onStart(); } }
效果圖:
將對話框的寬或者高鋪滿屏幕
設置對話框鋪滿屏幕有2種方式:
第一種 需要在styles.xml文件里,添加一個沒有內邊距的style,如下:
在上面的出現在屏幕下方的對話框中,依然與屏幕有小段距離,那個其實是dialog自帶的padding內邊距屬性導致的。這種方式可以設置只在寬度上鋪滿屏幕,但是高度上依然留有一定的內邊距。
<style name="dialogFullScreen" parent="Theme.AppCompat.Dialog"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="android:padding">0dp</item> <item name="android:windowBackground">@android:color/white</item> <item name="android:textColor">@android:color/black</item> </style>
第二種 需要在styles.xml文件里,設置 android:windowFullscreen 屬性:
<style name="dialogFullScreen" parent="Theme.AppCompat.Dialog"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="android:windowBackground">@android:color/white</item> <item name="android:textColor">@android:color/black</item> <item name="android:windowFullscreen">true</item> </style>
以上2種互為互補,都可以實現需要的效果
然后依然是重寫DialogFragment
public class MyDialog3 extends DialogFragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.set_wifi_password_dialog_layout, container, false); return view; } @Override public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); } @Override public void onStart() { WindowManager.LayoutParams params = getDialog().getWindow().getAttributes(); params.width = ViewGroup.LayoutParams.MATCH_PARENT;//設置寬度為鋪滿 params.gravity = Gravity.BOTTOM; getDialog().getWindow().setAttributes((WindowManager.LayoutParams) params); super.onStart(); } }
然后是重點,在創建DialogFragment對話框的時候添加我們的style。
mBtnTest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MyDialog3 myDialog = new MyDialog3(); myDialog.setStyle(DialogFragment.STYLE_NORMAL, R.style.dialogFullScreen);//添加上面創建的style myDialog.show(getSupportFragmentManager(),"aa"); } });
下方的效果圖里,我們就獲得了一個在寬度上鋪滿屏幕的對話框,舉一反三在設置高度上也是一樣的:
設置點擊外部空白處不會關閉對話框
方式一
在前面創建的對話框里,在點擊外部后依然會關閉對話框,我們有時候有些重要消息並不希望用戶可以點擊外部可以取消。
這個屬性一樣在styles.xml,創建style里添加 <item name="android:windowCloseOnTouchOutside">false</item>
<style name="dialogFullScreen" parent="Theme.AppCompat.Dialog"> <item name="android:padding">0dp</item> <item name="android:windowBackground">@android:color/white</item> <item name="android:textColor">@android:color/black</item> <item name="android:windowCloseOnTouchOutside">false</item> </style>
方式二
//getDialog().setCancelable(false);//這個會屏蔽掉返回鍵 getDialog().setCanceledOnTouchOutside(isCanceledOnTouchOutside());
設置在彈出對話框后同時彈出軟鍵盤
我只需要兩步,1.將需要輸入內容的EditText設置為焦點 2.設置軟鍵盤可見
public class MyDialog3 extends DialogFragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.set_wifi_password_dialog_layout, container, false); return view; } @Override public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) { EditText editPassword = view.findViewById(R.id.edit_password); editPassword.requestFocus();//設置焦點 getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);//設置輸入盤可見 super.onViewCreated(view, savedInstanceState); } @Override public void onStart() { WindowManager.LayoutParams params = getDialog().getWindow().getAttributes(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.gravity = Gravity.BOTTOM; getDialog().getWindow().setAttributes((WindowManager.LayoutParams) params); super.onStart(); } }
效果圖:
在Fragment里啟動對話框
與activity里一樣沒啥區別,唯一的區別就是你打算依然用老套的onActivityResult來向下傳值,那么你就需要設置一個目標Fragment在下面的代碼里setTargetFragment()方法就是起到這個作用的,在下面的代碼里我們用MyDialog1 啟動了 MyDialog2。
DialogFragment其實就是Fragment,所以我這里就偷懶一下,直接用對話框啟動對話框了。。。不在單獨寫一個Fragment
MyDialog1.Java
public class MyDialog1 extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); dialog.setTitle("測試Dialog"); dialog.setMessage("啟動另外一個對話框"); dialog.setPositiveButton("啟動", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { MyDialog2 myDialog2 = new MyDialog2(); myDialog2.setTargetFragment(MyDialog1.this, 300); myDialog2.setStyle(DialogFragment.STYLE_NORMAL, R.style.dialogFullScreen); myDialog2.show(getFragmentManager(), "myDialog2"); } }); return dialog.create(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); } @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); //這里可以返回 MyDialog2 Fragment的數據 } }
MyDialog2.Java
public class MyDialog2 extends DialogFragment { private static final String TAG = "MyDialog"; private ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.set_wifi_password_dialog_layout, container, false); return view; } @Override public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); } @Override public void onStart() { WindowManager.LayoutParams params = getDialog().getWindow().getAttributes(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.gravity = Gravity.BOTTOM; getDialog().getWindow().setAttributes((WindowManager.LayoutParams) params); super.onStart(); } }
設置圓角與實際不符合的問題
實際上是你設置的背景圖片,被Dialog自帶的背景遮蓋了,導致圓角無法顯示。所以設置一下透明背景就可以了。
注意設置DecorView的背景與設置Window的背景是有區別的,區別如下:
設置DecorView背景
@Override public void onStart() { WindowManager.LayoutParams params = getDialog().getWindow().getAttributes(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; getDialog().getWindow().setAttributes(params); getDialog().getWindow().getDecorView().setBackground(new ColorDrawable(Color.TRANSPARENT));
super.onStart(); }
效果圖:
設置Window的背景
@Override public void onStart() { WindowManager.LayoutParams params = getDialog().getWindow().getAttributes(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; getDialog().getWindow().setAttributes(params); getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); super.onStart(); }
改變Window背景的透明度
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); WindowManager.LayoutParams layoutParams = getWindow().getAttributes(); layoutParams.dimAmount = 0f;//調整透明度 getWindow().setAttributes(layoutParams); }
end