前言
在日常的android开发中,我们或多或少都有做过应用内的一些弹窗,比如在应用的某些页面弹窗展示广告,弹窗通知消息等。你的app中使用弹窗是否比较频繁?你是否厌烦了每次敲击一大堆代码就为了展示一个弹窗?是否同个页面有多个弹窗且伴有优先级?…等等,那么,可能这篇分享会帮助到你。
使用场景
1. 普通场景
弹窗任务只是展示一些内容,如文本、图片等信息,可能还要有点击事件等,如果对UI要求不是很严格,那么可以使用系统提供的AlertDialog,这里简单写个示范:
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog alertDialog = builder.setTitle("温馨提示") .setMessage("这是弹窗内容") .setPositiveButton("知道了", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ToastUtil.getInstance().show("点击了知道了"); } }).create(); alertDialog.show();
看下效果
到这里完成了弹窗的展示和关闭,不过弹窗的样式随着系统版本的不同而不同,在低版本上尤其不美观。
多数时候我们还需要根据设计图来实现弹窗,这个时候用系统提供的默认样式就不适用了,需要我们自定义实现弹窗。
2. 自定义弹窗
包括Dialog和PopupWindow,我们可能经常都需要自行定义合适的Dialog或PopupWindow来满足我们的业务场景,当业务场景满足的时候还需要考虑使用的便捷性和简洁性。
- 这里以PopupWindow为例,来看看我们的不单单是自定义且还需要有通用性的自定义弹窗应该是什么样子的:
new CommonPopupWindow.ViewBuilder<CustomLayout>() .width(ConstraintLayout.LayoutParams.MATCH_PARENT) .height(ConstraintLayout.LayoutParams.WRAP_CONTENT) .outsideTouchable(true) .focusable(true) .view(new CustomLayout(this)) .alpha(0.618f) .animationStyle(R.style.pop_animation) .intercept(new CommonPopupWindow.ViewEvent<CustomLayout>() { @Override public void getView(CommonPopupWindow popupWindow, CustomLayout view) { /*view表示我们设置的弹窗上的ViewGroup,可在这里做一些初始化操作*/ } }) .build(context) .showAsDropDown(target);
这里不是本文的重点,就不展开说明了,找时间会单独写一篇文章来分享我的自定义弹窗心得。
3. 特殊场景
前面说的都是为了引出本文要将的重点:页面弹窗接入优先级管理。
- 这里先提出几个问题,如果一个页面需要多个弹窗,弹窗之间的展示有规定的先后顺序,单个弹窗展示有前提条件(如页面处于某个tab)等等跟弹窗相关的一大堆逻辑,这个时候你会怎么处理?也许你会说:我创建多个弹窗,先展示优先级高的弹窗,在该弹窗的onDismissListener回调中展示优先级低的弹窗。
- 我想说的是,如果页面中的弹窗一两个的时候这么做是可以的,但是如果每个页面中有很多个弹窗呢?比如什么弹窗广告啊,引导提示啊,活动提示啊等等,这个时候如果还是以这种方法去实现优先级的逻辑,那要写的地方就太多了,一不小心还会出错。如果再来个变态的需求,我们这里随便举个例子,比如某个弹窗只能当页面处于某个tab
下才能展示,是不是又要写一大堆判断?有没有什么办法能解决这个问题,当然有,请往下看。
4. 弹窗优先级管理
这里说的弹窗管理者(以下称DialogManager)对应的弹窗应该是一个抽象的概念,不单指Dialog或PopupWindow,所以我们的DialogManager使用的范围也不受限制,只要遵循约定的interface任何类都可以纳入DialogManager的管理,为了好理解我们将此interface命名为Dialog。
组成DialogManager的除了Dialog(interface)还有DialogParam,下边我们先看看源码:
Dialog
/** * “窗口”约定规则 */ public interface Dialog { /** * 展示 */ void show(); /** * 关闭 * * @param isCrowdOut 是否被挤出 */ void dismiss(boolean isCrowdOut); /** * 设置“窗口”关闭监听 */ void setOnDismissListener(OnDismissListener listener); /** * 设置“窗口”展示监听 */ void setOnShowListener(OnShowListener listener); /** * 是否满足show的条件(如处于某个tab,不关心时返回true就行) */ boolean isCanShow(); } 其中OnDismissListener 和OnShowListener是我们自定义的,源码如下: /** * “窗口”展示监听 */ public interface OnShowListener { void onShow(); } /** * “窗口”关闭监听 */ public interface OnDismissListener { /** * @param isCrowdOut 是否被挤出 */ void onDismiss(boolean isCrowdOut); } 1. DialogParam /** * 窗口参数类 */ public static class DialogParam { /** * “窗口” */ private Dialog dialog; /** * 优先级,值越大优先级越高 */ private int priority; /** * 当前是否处于show状态 */ private boolean isShowing; /** * 是否准备show(在show之后非用户自己手动关掉弹窗(调dismiss或触摸弹窗外部) * 接着show了一个优先级更高的Dialog,当该Dialog dismiss后可自动show剩下的 * 优先级最高的Dialog * ) */ private boolean prepareShow; private DialogParam(Builder builder) { dialog = builder.dialog; priority = builder.priority; prepareShow = builder.prepareShow; } public Dialog getDialog() { return dialog; } public void setDialog(Dialog dialog) { this.dialog = dialog; } public int getPriority() { return priority; } public void setPriority(int priority) { this.priority = priority; } public boolean isShowing() { return isShowing; } public void setShowing(boolean showing) { isShowing = showing; } public boolean isPrepareShow() { return prepareShow; } public void setPrepareShow(boolean prepareShow) { this.prepareShow = prepareShow; } public static class Builder { /** * “窗口” */ private Dialog dialog; /** * 优先级,值越大优先级越高 */ private int priority; /** * 是否准备show(在show之后非用户自己手动关掉弹窗(调dismiss或触摸弹窗外部) * 接着show了一个优先级更高的Dialog,当该Dialog dismiss后可自动show剩下的 * 优先级最高的Dialog * ) */ private boolean prepareShow = true; public Builder dialog(Dialog dialog) { this.dialog = dialog; return this; } public Builder priority(int priority) { this.priority = priority; return this; } public Builder prepareShow(boolean prepareShow) { this.prepareShow = prepareShow; return this; } public DialogParam build() { return new DialogParam(this); } } }
DialogManager
import android.text.TextUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 作者:lpx on 2020/8/24 17:39 * Email : 1966353889@qq.com * Describe:弹窗管理(支持设置弹窗优先级) * update on 2020/8/26 16:49 */ public class DialogManager { private List<DialogParam> mDialogs; private static DialogManager mDefaultInstance; private DialogManager() { } /** * 获取弹窗管理者 */ public static DialogManager getInstance() { if (mDefaultInstance == null) { synchronized (DialogManager.class) { if (mDefaultInstance == null) { mDefaultInstance = new DialogManager(); } } } return mDefaultInstance; } /** * 添加弹窗 * * @param dialogParam 待添加的弹窗 */ public synchronized void add(DialogParam dialogParam) { if (dialogParam != null && dialogParam.getDialog() != null) { if (mDialogs == null) { mDialogs = new ArrayList<>(); } dialogParam.getDialog().setOnShowListener(new OnShowListener() { @Override public void onShow() { dialogParam.setShowing(true); dialogParam.setPrepareShow(false); } }); dialogParam.getDialog().setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(boolean isCrowdOut) { dialogParam.setShowing(false); if (isCrowdOut) { dialogParam.setPrepareShow(true); } else { mDialogs.remove(dialogParam); showNext(); } } }); mDialogs.add(dialogParam); } } /** * 展示弹窗 * * @param dialogParam 待展示的弹窗 */ public synchronized void show(DialogParam dialogParam) { if (dialogParam != null && dialogParam.getDialog() != null) { if (mDialogs == null) { if (dialogParam.getDialog().isCanShow()) { dialogParam.getDialog().show(); } } else { /*判断优先级及是否可展示*/ maybeShow(dialogParam); } } } /** * 展示弹窗(优先级最高的Dialog) */ public synchronized void show() { DialogParam dialogParam = getMaxPriorityDialog(); if (dialogParam != null) { Dialog dialog = dialogParam.getDialog(); if (dialog != null && dialog.isCanShow()) { dialog.show(); } } } /** * 清除弹窗管理者 */ public synchronized void clear() { if (mDialogs != null) { for (int i = 0, size = mDialogs.size(); i < size; i++) { if (mDialogs.get(i) != null) { mDialogs.get(i).setPrepareShow(false); } } for (int i = 0, size = mDialogs.size(); i < size; i++) { if (mDialogs.get(i) != null) { Dialog dialog = mDialogs.get(i).getDialog(); if (dialog != null) { dialog.dismiss(false); } } } mDialogs.clear(); } } /** * 清除弹窗管理者 * * @param dismiss 是否同时dismiss掉弹窗管理者维护的弹窗 */ public synchronized void clear(boolean dismiss) { if (mDialogs != null) { for (int i = 0, size = mDialogs.size(); i < size; i++) { if (mDialogs.get(i) != null) { mDialogs.get(i).setPrepareShow(false); } } if (dismiss) { for (int i = 0, size = mDialogs.size(); i < size; i++) { if (mDialogs.get(i) != null) { Dialog dialog = mDialogs.get(i).getDialog(); if (dialog != null) { dialog.dismiss(false); } } } } mDialogs.clear(); } } /** * 展示下一个优先级最大的Dialog(非自行调用dismiss而是被优先级高的弹窗show后挤掉) */ private synchronized void showNext() { DialogParam dialog = getMaxPriorityDialog(); if (dialog != null) { if (dialog.isPrepareShow() && dialog.getDialog().isCanShow()) { dialog.getDialog().show(); } } } /** * 展示弹窗(满足条件可展示) * * @param dialogParam 待展示的弹窗 */ private void maybeShow(DialogParam dialogParam) { if (dialogParam != null && dialogParam.getDialog() != null) { DialogParam topShowDialog = getShowingDialog(); if (topShowDialog == null) { if (dialogParam.getDialog().isCanShow()) { dialogParam.getDialog().show(); } } else { /*获取优先级*/ int priority = dialogParam.getPriority(); if (priority >= topShowDialog.getPriority()) { if (dialogParam.getDialog().isCanShow()) { dialogParam.getDialog().show(); topShowDialog.getDialog().dismiss(true); /*设置参数支持当前show关闭后自动show带该参数的优先级最高的弹窗*/ topShowDialog.setPrepareShow(true); } } } } } /** * 获取当前栈中优先级最高的Dialog(优先级相同则返回后添加的弹窗) */ private synchronized DialogParam getMaxPriorityDialog() { if (mDialogs != null) { int maxPriority = -1; int position = -1; for (int i = 0, size = mDialogs.size(); i < size; i++) { DialogParam dialog = mDialogs.get(i); if (i == 0) { position = 0; maxPriority = dialog.getPriority(); } else { if (dialog.getPriority() >= maxPriority) { position = i; maxPriority = dialog.getPriority(); } } } if (position != -1) { return mDialogs.get(position); } else { return null; } } return null; } /** * 获取当前处于show状态的弹窗 */ private synchronized DialogParam getShowingDialog() { if (mDialogs != null) { for (int i = 0, size = mDialogs.size(); i < size; i++) { DialogParam dialogParam = mDialogs.get(i); if (dialogParam != null && dialogParam.getDialog() != null && dialogParam.isShowing()) { return dialogParam; } } } return null; } } ### 使用方法 1. 自定义弹窗类实现Dialog(Interface) 这里还是以AlertDialog的自定义为例 import android.content.Context; import android.content.DialogInterface; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; /** * 作者:lpx on 2020/8/27 11:47 * Email : 1966353889@qq.com * Describe:窗口例子类 */ public class DialogSample extends AlertDialog implements Dialog { /** * 是否被挤出(每个实现DialogManager.Dialog的窗口类都需要新建该变量) */ private boolean isCrowdOut; protected DialogSample(@NonNull Context context) { super(context); } protected DialogSample(@NonNull Context context, int themeResId) { super(context, themeResId); } protected DialogSample(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) { super(context, cancelable, cancelListener); } @Override public void show() { super.show(); } @Override public void dismiss(boolean isCrowdOut) { /*isCrowdOut在super.dismiss()之前赋值*/ this.isCrowdOut = isCrowdOut; super.dismiss(); } @Override public void setOnDismissListener(DialogManager.OnDismissListener listener) { setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { listener.onDismiss(isCrowdOut); } }); } @Override public void setOnShowListener(DialogManager.OnShowListener listener) { setOnShowListener(new OnShowListener() { @Override public void onShow(DialogInterface dialog) { listener.onShow(); } }); } /** * 每个实现DialogManager.Dialog的窗口类都需要实现该 * 方法告诉DialogManager是否可展示此窗口(比如有些窗 * 口只在页面的某个tab下才能展示) */ @Override public boolean isCanShow() { return true; } } 1. 添加到DialogManager 在对应的页面添加自定义弹窗并纳入优先级管理 DialogSample alertDialog1; DialogSample alertDialog2; DialogSample alertDialog3; int[] prioritys = new int[]{3, 1, 2}; alertDialog1 = new DialogSample2(context); alertDialog1.setTitle("温馨提示"); alertDialog1.setMessage("第一个弹窗,优先级:" + prioritys[0]); alertDialog1.setCancelable(false); alertDialog1.setButton(DialogInterface.BUTTON_POSITIVE, "关闭", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { /*调用此方法传false告诉DialogManager此窗口 dismiss是用户自己关闭的,而非被优先级更高的弹 窗show后被挤出,这种情况优先级更高的弹窗dismiss 后DialogManager不会重新show此弹窗*/ alertDialog1.dismiss(false); } }); alertDialog2 = new DialogSample2(context); alertDialog2.setTitle("温馨提示"); alertDialog2.setMessage("第二个弹窗,优先级:" + prioritys[1]); alertDialog2.setCancelable(false); alertDialog2.setButton(DialogInterface.BUTTON_POSITIVE, "关闭", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { /*调用此方法传false告诉DialogManager此窗口 dismiss是用户自己关闭的,而非被优先级更高的弹 窗show后被挤出,这种情况优先级更高的弹窗dismiss 后DialogManager不会重新show此弹窗*/ alertDialog2.dismiss(false); } }); alertDialog3 = new DialogSample2(context); alertDialog3.setTitle("温馨提示"); alertDialog3.setMessage("第三个弹窗,优先级:" + prioritys[2]); alertDialog3.setCancelable(false); alertDialog3.setButton(DialogInterface.BUTTON_POSITIVE, "关闭", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { /*调用此方法传false告诉DialogManager此窗口 dismiss是用户自己关闭的,而非被优先级更高的弹 窗show后被挤出,这种情况优先级更高的弹窗dismiss 后DialogManager不会重新show此弹窗*/ alertDialog3.dismiss(false); } }); DialogManager.getInstance().add(new DialogParam.Builder().dialog(alertDialog1).priority(prioritys[0]).build()); DialogManager.getInstance().add(new DialogParam.Builder().dialog(alertDialog2).priority(prioritys[1]).build()); DialogManager.getInstance().add(new DialogParam.Builder().dialog(alertDialog3).priority(prioritys[2]).build());
在需要展示弹窗时调:
DialogManager.getInstance().show();
效果图如下:
结尾
是不是很方便快捷,希望本文可以帮助到您,也希望各位不吝赐教,提出您在使用中的宝贵意见,谢谢。