底部弹出式菜单, 可以使用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.
那这句异常的意思是什么呢?具体可以参见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
中做状态保存等操作。
这个回调方法是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
自定义布局展示对话框。生命周期同Fragment
,同时,支持FragmentManager
事务
<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>
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">trueitem>
一. 如果使用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);
}
}
这里涉及到了一个theme
,主要是设置动画的,dialog
自下而上的弹出来
<style name="BottomDialog" parent="AppTheme">
<item name="android:windowNoTitle">trueitem>
<item name="android:windowIsFloating">trueitem>
-- Dialog进入及退出动画 -->
<item name="android:windowAnimationStyle">@style/BottomToTopAnim
style>
<style name="BottomToTopAnim" parent="android:Animation">
<item name="@android:windowEnterAnimation">@anim/bottomview_anim_enter
- "@android:windowExitAnimation"
>@anim/bottomview_anim_exit
style>
enter动画:
<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动画:
<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]