Android DialogFragment實現底部彈出選單效果
底部彈出式選單, 可以使用PopupWindow
來做,也可以用自定義View
來做。當然這裡採用DialogFragment
來做。
DialogFragment
是3.0之後引入的,使用DialogFragment
,我們不用管理其生命週期,並且可以作為元件重用。比如當螢幕旋轉的時候,如果PopupWindow
沒有dismiss掉,會丟擲異常。AlertDialog
則會消失,DialogFragment
建立的對話方塊則不受影響。
概述
使用DialogFragment
,需要重寫onCreateView
或者onCreateDialog
方法,前者是通過layout下的自定義佈局來建立對話方塊,後者則是用AlertDialog
Dialog
創建出Dialog
,適用於建立簡單的對話方塊。
如果同時複寫onCreateView
和onCreateDialog
會報如下異常,
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.
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;
}