几个月前写到了弹框业务,以前经常用Dialog、ButtomDialog 、popupWindow 组件,为了契合项目结构参考了原有的 DialogFragment 组件,特此予以记录
我一般在项目中写弹框组件的话,主要用到 alertDialog、popupWindow 组件,关于 DialogFragment 组件将在该篇简单学一下
弹框
- Dialog 基础入门 - 普通对话框、水平进度条对话框、普通列表对话框、单选对话框、复选对话框
- ButtomDialog 使用方式(早期所写,回头看来当时词不达意,本质还是用了alertDialog ,参考意义不大)
- popupWindow 使用方式 - 个人认为可以和Dialog一较高下,项目中使用相对频繁,定制化也高
- 用Kotlin写个能让我进步的Dialog - 近几年因为Kotlin盛行,写了一个项目中的应用场景
Tip:嗯… 可以了解,但是如果你已经掌握了其他弹框技术,在无特定需求下可以先不学,毕竟这么多年下来这款组件的普及率、使用率好像并不太高,而我也在逐渐替换掉项目中 DialogFragment 的使用场景…
六月梅雨季
- 基础了解
- 函数分析
- 实战检验
基础了解
起初其实我不太理解为何要用
DialogFragment
?它相比常用弹框组件的优势在哪里?
通过DialogFragment
源码可以确定其继承自Fragment
故拥有其特性,同时实现了Dialog接口
监听弹框的一个取消状态、关闭状态
查看内部方法并不多,除了 Dilaog
的一些show、dismiss
方法外,我觉得最能引起能注意的应该就是生命周期
的特性了,所以这应该算是这款组件的一个优势,可以动态监听与Activity的绑定状态,以及自身的一个生命周期状态
单从以上源码来看,目前为止至少具备一些基础优势
- 生命周期清晰,扩展了适用场景
- 支持弹框布局自定义化
- 支持Dialog相关设置
- 与Activity生命周期绑定,会随着Activity消失而消失(未复测)
函数分析
当我们创建 DialogFragment
时,因未声明抽象方法,所以我们根据需求,可自行选择重写几个关键方法,如
- onCreate:生命周期第一步,一般根据业务可获取创建DialogFragment时的入参,用于当前组件显示等
- onCreateView: 用于设置弹框布局、事件处理
- onCreateDialog:可在此处调用其Dialog特性
- onResume:弹窗展示,可在此处获取当前显示的Dialog,便于设置一些特定属性,常用于设置组件展示范围、形式
- onDismiss: 监听弹窗消失,根据业务需求自行加入逻辑
- onActivityCreated:便于监听Activity的状态,支持Activity关联创建后,及时展示DialogFragment(方法已过时,未用过)
onCreate
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
//获取外部传入的数据
String ourContent = bundle.getString("keyContent");
}
onCreateView
绑定弹框要显示的布局,同时可以设置一些组件显示、事件等
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.our_layout, container, false);
TextView tvContent = rootView.findViewById(R.id.tv_content);
TextView tvClose = rootView.findViewById(R.id.tv_close);
tvClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissAllowingStateLoss();
}
});
tvContent.setOnClickListener(ourClick);
return rootView;
}
有的人蛮喜欢抽方法,其实本质相同,这里就是将点击事件抽到了外部(建议初方法内部复杂、繁琐、调用频繁外,并不推荐抽方法)
View.OnClickListener ourClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissAllowingStateLoss();
}
};
onCreateDialog
关于Dialog属性设置,可以在此处进行设置,例如触摸、点击视图以外区域不会关闭弹框等
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
// dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
onResume
显示时设置弹框大小、位置、背景等
@Override
public void onResume() {
super.onResume();
Dialog dialog = getDialog();
if (dialog != null && dialog.getWindow() != null) {
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
WindowManager.LayoutParams layoutParams = dialog.getWindow().getAttributes();
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.gravity = Gravity.CENTER;
dialog.getWindow().setAttributes(layoutParams);
}
}
onDismiss
通常为了灵活性,我们大多会监听弹框消失做一些逻辑处理,不过也不排除一些固有行为直接在组件内部实现
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
// 可自行做一些数据记录之类的 SPUtils.AppSP().put("key", "value");
}
关于 DialogFragment
常用函数,我们已经都说完了,那么简单说一下它的调用方式,通常我们主要有俩种常见方式
- 创建
DialogFragment
实例后,调用类似show()函数显示弹框
DialogFragment
内部提供静态方法
有的说遇到了 Fragment already added(重复添加)
的问题,可以参考下方DialogFragment
内部提供的静态方法
/**
* 静态方法,支持便捷调用,同时传入所需参数
*/
public static OurFirstDialogFragment display(final FragmentManager fragmentManager, String contentUrl) {
OurFirstDialogFragment fragment = (OurFirstDialogFragment) fragmentManager.findFragmentByTag(OurFirstDialogFragment.class.getCanonicalName());
if (fragment == null) {
fragment = new OurFirstDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(KEY_CONTENT, contentUrl);
fragment.setArguments(bundle);
}
if (!fragment.isAdded()) {
fragment.show(fragmentManager, OurFirstDialogFragment.class.getCanonicalName());
}
return fragment;
}
关于 DialogFragment
显示,通常有show
、showNow
函数,主要区别于此
- show显示稍慢于showNow,这导致调用show了后,立刻修改dialog中的view(例如textView修改字符内容)会崩溃,而showNow不会(
showNow容错率更高
) - 待检验:(废弃)展示弹窗后fragment对象会添加到activity,
showNow会在弹窗dismiss消失后移除fragment,show不会移除
(以前同一个对象非连续地调用两次show会崩溃,现在不会了,可能是google更新了,使show也在弹窗消失后移除了) - 待检验:不可连续地调用show或者showNow;这个“连续”是指在弹窗还没有消失的时候再次调用,原因在上方说了,展示弹窗后fragment对象会添加到activity,而同一个fragment只能添加一次,所以连续调用可能会崩溃
实战检验
调用方式
涉及到了Fragment,所以一般会用到fragmentManager
,在Activity、Fragment都有现成API
//当前我用的是静态函数,可以直接通过类型+函数调用
//关于调用函数,如果为了兼容多场景,可以重载其静态方法
OurFirstDialogFragment .display(fragmentManager) //不传值
OurFirstDialogFragment .display(fragmentManager,"数据") //传值
//通过创建实例的方式,显示弹框,因项目未采用此方式,仅做示例
var ourFirstDialogFragment = OurFirstDialogFragment()
fragmentManager?.let { ourFirstDialogFragment.show(it, "tag一般独一无二,防止重复") }
OurFirstDialogFragment(自定义DialogFragment )
package xx;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
public class OurFirstDialogFragment extends DialogFragment {
public static final String KEY_CONTENT = "KEY_CONTENT";
private String ourContent;
private TextView tvContent, tvClose;
/**
* 静态方法,支持便捷调用,同时传入所需参数
*/
public static OurFirstDialogFragment display(final FragmentManager fragmentManager, String content) {
OurFirstDialogFragment fragment = (OurFirstDialogFragment) fragmentManager.findFragmentByTag(OurFirstDialogFragment.class.getCanonicalName());
if (fragment == null) {
fragment = new OurFirstDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(KEY_CONTENT, content);
fragment.setArguments(bundle);
}
if (!fragment.isAdded()) {
fragment.show(fragmentManager, OurFirstDialogFragment.class.getCanonicalName());
}
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
//获取外部传入的数据
ourContent = bundle.getString(KEY_CONTENT);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.our_layout, container, false);
tvContent = rootView.findViewById(R.id.tv_content);
tvClose = rootView.findViewById(R.id.tv_close);
tvClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissAllowingStateLoss();
}
});
tvContent.setOnClickListener(ourClick);
return rootView;
}
View.OnClickListener ourClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissAllowingStateLoss();
}
};
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
// dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public void onResume() {
super.onResume();
Dialog dialog = getDialog();
if (dialog != null && dialog.getWindow() != null) {
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
WindowManager.LayoutParams layoutParams = dialog.getWindow().getAttributes();
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.gravity = Gravity.CENTER;
dialog.getWindow().setAttributes(layoutParams);
}
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
//SPUtils.AppSP().put("key", "value");
}
}
嗯,当你看到这儿的话,不知道你是否遇到了下面这个问题,当我们在Dialog内部操作时,我们希望外部可以实时监听,这时候就用到了接口回调
public interface DialogCallback {
void onButtonClicked(String buttonText);
}
private DialogCallback callback;
public static OurFirstDialogFragment display(final FragmentManager fragmentManager, String content) {
OurFirstDialogFragment fragment = (OurFirstDialogFragment) fragmentManager.findFragmentByTag(OurFirstDialogFragment.class.getCanonicalName());
if (fragment == null) {
fragment = new OurFirstDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(KEY_CONTENT, content);
fragment.setArguments(bundle);
}
//在静态方法中绑定监听回调,如果你采用的是实例调用,需要重写构造参数,然后在绑定监听回调
fragment.callback = callback;
if (!fragment.isAdded()) {
fragment.show(fragmentManager, OurFirstDialogFragment.class.getCanonicalName());
}
return fragment;
}
tvView.setOnClickListener(v -> {
dismissAllowingStateLoss();
callback.onButtonClicked("动态回传要监听的内容即可");
});