Android弹窗优先级管理高级应用

先看接入步骤:
Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:

 allprojects {
		repositories {
			...
			maven { url 'https://jitpack.io' }
		}
	}

Step 2. Add the dependency

  dependencies {
	        implementation 'com.github.linpeixu:DialogManager:1.0.0'
            //或者implementation'com.gitlab.linpeixu:dialogmanager:1.0.0'
	}

工程地址:https://github.com/linpeixu/DialogManager
更新说明:https://github.com/linpeixu/DialogManager/releases
工程地址:https://gitlab.com/linpeixu/dialogmanager
更新说明:https://gitlab.com/linpeixu/dialogmanager/-/releases

背景:

之前我写过一篇Android优雅实现弹窗优先级管理来介绍如何在android日常开发中管理多个弹窗的优先级,这个解决方案也被其他开发者所采用,在这里也感谢大家的认可。

在这里插入图片描述

我们常说世界上唯一不变的就是变化,开发的业务场景也在不停地变化,所以之前的弹窗管理解决方案在某些业务上则有场景的限制了,比如Android优雅实现弹窗优先级管理更偏向于将各个自定义的Dialog(api上的Dialog)纳入管理,且每个Dialog需要实现我们约定好的“Dialog Interface”,这样的话某种意义上已经对自定义Dialog构成代码的入侵了,再比如我们的DialogActivity也是可以实现Dialog的效果的,业务上的需要有时候需要我们使用DialogActivity来完成弹窗的业务,这个时候其实我们所说的“弹窗”的概念已经需要包括Dialog和Activity等不同组件了,由此我在思考,能不能有一种通用上的“弹窗”概念,而不仅仅局限于日常的自定义的Dialog,且不要对我们需要被管理的“弹窗”有任何代码上的入侵。

想法是美好的,开始行动,最终我重新封装了DialogManager,先看使用实例。

这里我们为了说明“弹窗”只是一个概念,需要纳入优先级管理的class不限制具体的类型,我们使用这几种不同“概念”的弹窗来看看怎么应用,例如自定义MessageDialog(继承Dialog)、AlertDialog、以及String类型的“弹窗”。

首先把对应的“概念”弹窗纳入管理只需要调用

DialogManager.getInstance().add(Config<Type> config);

在需要展示弹窗的时候只需要调用

DialogManager.getInstance().show();

接下来我们看看上边提到的三种“概念”弹窗是怎么纳入优先级管理的

MessageDialog“概念”弹窗的配置

DialogManager.Config<MessageDialog> config1= new DialogManager
                .Config
                .Builder<MessageDialog>()
                .as(new DialogManager.BaseType<MessageDialog>(new MessageDialog(context, "第一个弹窗")) {
                    @Override
                    void init(DialogManager.Config<MessageDialog> config) {
                        config.getBaseType().getType().setOnDismissListener(new DialogInterface.OnDismissListener() {
                            @Override
                            public void onDismiss(DialogInterface dialog) {
                               /*需要记住,在合适的地方一定要调用方法config.dispatch()通知DialogManager
                               *接着展示下个“概念”弹窗
                               */
                                config.dispatch();
                            }
                        });
                    }

                    @Override
                    void show(DialogManager.Config<MessageDialog> config) {
                       /*这是DialogManager执行到展示“概念”弹窗的逻辑,需外部实现相应的show逻辑*/
                        config.getBaseType().getType().show();
                    }

                    @Override
                    void dismiss(DialogManager.Config<MessageDialog> config) {
                       /*这是DialogManager执行到隐藏“概念”弹窗的逻辑,需外部实现相应的dismiss逻辑*/
                        config.getBaseType().getType().dismiss();
                    }
                })
                .priority(1)
                .onShowCheckListener(new DialogManager.OnShowCheckListener() {
                    @Override
                    public boolean isCanShow() {
                       /*这是告诉DialogManager此“概念”弹窗可否展示*/
                       /*如我们有些业务需要首页处于哪个Tab时才可展示此弹窗
                        *可以在这里自行实现判断条件
                       */
                        return true;
                    }
                })
                .build();

String“概念”弹窗的配置

DialogManager.Config<String> config2 = new DialogManager
                .Config
                .Builder<String>()
                .as(new DialogManager.BaseType<String>("这是个文字") {
                    @Override
                    void init(DialogManager.Config<String> config) {

                    }

                    @Override
                    void show(DialogManager.Config<String> config) {
                        DialogManager.getInstance().getILog().onPrintLog("DialogManager", "show->" + config.getBaseType().getType());
                        Toast.makeText(context, "String Type show->这是个文字", Toast.LENGTH_SHORT).show();

                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                              /*需要记住,在合适的地方一定要调用方法config.dispatch()通知DialogManager
                               *接着展示下个“概念”弹窗
                               */
                                config.dispatch();
                            }
                        }, 3000);
                    }

                    @Override
                    void dismiss(DialogManager.Config<String> config) {
                        DialogManager.getInstance().getILog().onPrintLog("DialogManager", "dismiss->" + config.getBaseType().getType());
                    }
                })
                .priority(2)
                .onShowCheckListener(new DialogManager.OnShowCheckListener() {
                    @Override
                    public boolean isCanShow() {
                        return true;
                    }
                })
                .build();

AlertDialog“概念”弹窗的配置

AlertDialog alertDialog = new AlertDialog.Builder(context).create();
        alertDialog.setMessage("第三个弹窗");
        alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "我知道了", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                alertDialog.dismiss();
            }
        });
        DialogManager.Config<AlertDialog> config3 = new DialogManager
                .Config
                .Builder<AlertDialog>()
                .as(new DialogManager.BaseType<AlertDialog>(alertDialog) {
                    @Override
                    void init(DialogManager.Config<AlertDialog> config) {
                        config.getBaseType().getType().setOnDismissListener(new DialogInterface.OnDismissListener() {
                            @Override
                            public void onDismiss(DialogInterface dialog) {
                                config.dispatch();
                            }
                        });
                    }

                    @Override
                    void show(DialogManager.Config<AlertDialog> config) {
                        config.getBaseType().getType().show();
                    }

                    @Override
                    void dismiss(DialogManager.Config<AlertDialog> config) {
                        config.getBaseType().getType().dismiss();
                    }
                })
                .priority(3)
                .onShowCheckListener(new DialogManager.OnShowCheckListener() {
                    @Override
                    public boolean isCanShow() {
                        return true;
                    }
                })
                .build();

将三种“概念”弹窗纳入管理

DialogManager.getInstance().add(config1);
DialogManager.getInstance().add(config2);
DialogManager.getInstance().add(config3);

在需要展示的地方只需要调用

DialogManager.getInstance().show();

这样我们做到了对“概念”弹窗无代码入侵,待添加的“概念”弹窗对应的Config只需要在合适的地方调用方法config.dispatch()通知DialogManager接着展示下个“概念”弹窗,实现show和dismiss逻辑,实现isCanshow逻辑即可,从代码的角度上来看逻辑更加清晰。

另外,可调用如下方法设置是否开启调试模式,调试模式带日志

DialogManager.getInstance().(boolean debug);

可调用如下方法代理调试模式下日志的输出,如

DialogManager.getInstance().setILog(new DialogManager.ILog() {
                    @Override
                    public void onPrintLog(String TAG, String log) {
                        LogUtil.d(TAG, log);
                    }
                });

完整的DialogManager代码如下

import android.util.Log;

import java.util.UUID;
import java.util.Vector;

/**
 * 作者:lpx on 2021/11/18 17:39
 * Email : 1966353889@qq.com
 * Describe:“概念”弹窗优先级管理(支持设置弹窗优先级)
 * update on 2021/11/20 11:29
 */
public class DialogManager {
    private final String TAG = "DialogManager";
    /*由于instance = new Singleton(),这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
    1.给 instance 分配内存
    2.调用 Singleton 的构造函数来初始化成员变量
    3.将instance对象指向分配的内存空间(执行完这步instance就为非null了)
    但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
    我们只需要将 instance 变量声明成 volatile 就可以了。*/
    private static volatile DialogManager mInstance;
    /**
     * 所有已添加的Type集合(线程安全的List,用法和ArrayList类似)
     */
    private Vector<Config> mConfigs = new Vector<>();
    /**
     * 当前正在显示的Type
     */
    private Config mCurrentConfig;
    /**
     * 日志输出类
     */
    private ILog mILog;
    /**
     * 是否调试模式
     */
    private boolean debug = BuildConfig.DEBUG;

    private DialogManager() {
        mILog = new ILog() {
            @Override
            public void onPrintLog(String TAG, String log) {
                Log.d(TAG, log);
            }
        };
    }

    /**
     * 打印log(内部调用)
     */
    private void printLog(String log) {
        if (debug && mILog != null) {
            mILog.onPrintLog(TAG, log);
        }
    }


    /**
     * 尝试移除相同优先级的Config
     */
    private <Type> void tryRemoveType(Config<Type> config) {
        if (mConfigs != null && !mConfigs.isEmpty() && config != null) {
            for (int i = 0, size = mConfigs.size(); i < size; i++) {
                if (mConfigs.get(i).getPriority() == config.getPriority()) {
                    mConfigs.get(i).dismiss();
                    mConfigs.remove(mConfigs.get(i));
                    break;
                }
            }
        }
    }

    /**
     * 获取已添加的最大优先级的且满足显示条件的Type
     */
    private <Type> Config<Type> getMaxPriorityCanShowType() {
        if (mConfigs != null && !mConfigs.isEmpty()) {
            int size = mConfigs.size();
            int position = size - 1;
            for (int i = size - 1; i >= 0; i--) {
                if (mConfigs.get(i).isCanShow() && mConfigs.get(i).getPriority() > mConfigs.get(position).getPriority()) {
                    position = i;
                }
            }
            return mConfigs.get(position).isCanShow() ? mConfigs.get(position) : null;
        }
        return null;
    }

    /**
     * 移除对应的Type
     */
    private <Type> void remove(Config<Type> config) {
        if (mConfigs != null && !mConfigs.isEmpty() && config != null) {
            int size = mConfigs.size();
            boolean result = mConfigs.remove(config);
            printLog("remove(object)\nresult:" + result + "\nsize:" + size + "->" + mConfigs.size());
        } else {
            printLog("remove(object)\nresult:does not perform\nsize:" + (mConfigs != null ? mConfigs.size() : "configs is null"));
        }
    }

    /**
     * 显示下个Type
     */
    private void showNext() {
        show();
    }

    /**
     * 返回当前实例
     */
    public static DialogManager getInstance() {
        if (mInstance == null) {
            synchronized (DialogManager.class) {
                if (mInstance == null) {
                    mInstance = new DialogManager();
                }
            }
        }
        return mInstance;
    }

    /**
     * 设置对应的log处理类
     */
    public void setILog(ILog log) {
        if (log != null) {
            mILog = log;
        }
    }

    public ILog getILog() {
        return mILog;
    }

    /**
     * 是否打开调试模式
     */
    public void enableDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * 添加Type(支持添加相同优先级的Type)
     */
    public <Type> void add(Config<Type> config) {
        if (mConfigs != null) {
            mConfigs.add(config);
        }
    }

    /**
     * 添加Type(支持添加相同优先级的Type)
     *
     * @param priorityReplace 是否替换存在的相同优先级的Type
     */
    public <Type> void add(Config<Type> config, boolean priorityReplace) {
        if (priorityReplace) {
            tryRemoveType(config);
        }
        if (mConfigs != null) {
            mConfigs.add(config);
        }
    }


    /**
     * 显示Type
     */
    public <Type> void show() {
        Config<Type> config = getMaxPriorityCanShowType();
        if (config != null) {
            if (mCurrentConfig != null) {
                if (mCurrentConfig.getUuid().equals(config.getUuid())) {
                    printLog("show()->getMaxPriorityCanShowType()\nresult:success"
                            + "\ntype:" + (config.getBaseType() != null && config.getBaseType().getType() != null ? config.getBaseType().getType().getClass().getSimpleName() : "unknown")
                            + "\npriority:" + config.getPriority()
                            + "\nextra:the same as current type uuid");
                    return;
                }
                mCurrentConfig.closeByManager();
                mCurrentConfig.dismiss();
            }
            printLog("show()->getMaxPriorityCanShowType()\nresult:success"
                    + "\ntype:" + (config.getBaseType() != null && config.getBaseType().getType() != null ? config.getBaseType().getType().getClass().getSimpleName() : "unknown")
                    + "\npriority:" + config.getPriority());
            mCurrentConfig = config;
            config.show();
        } else {
            printLog("show()->getMaxPriorityCanShowType():\nresult:target not found");
        }
    }

    /**
     * 日志输出接口
     */
    public interface ILog {
        void onPrintLog(String TAG, String log);
    }

    /**
     * Type配置类
     */
    public static class Config<Type> {
        /**
         * 标识码
         */
        private final String uuid;
        /**
         * Type包装类(泛型)
         */
        private BaseType<Type> baseType;
        /**
         * 弹窗优先级(值越大优先级越高)
         */
        private int priority;
        /**
         * 是否通过优先级机制关闭的Type(若为true则可能重新被打开)
         */
        private boolean closeByManager;
        private OnShowCheckListener onShowCheckListener;
        private OnDismissCheckListener onDismissCheckListener;

        private Config(Builder<Type> builder) {
            uuid = UUID.randomUUID().toString();
            this.baseType = builder.baseType;
            this.priority = builder.priority;
            this.onShowCheckListener = builder.onShowCheckListener;
            this.onDismissCheckListener = builder.onDismissCheckListener;
            if (this.baseType != null) {
                this.baseType.init(this);
            }
        }

        /**
         * 当前Type是否可以show(不用考虑其它Type的情况)
         */
        private boolean isCanShow() {
            return (getBaseType() != null && getBaseType().getType() != null) && (onShowCheckListener == null || onShowCheckListener.isCanShow());
        }

        /**
         * 当前Type是否可以dismiss(不用考虑其它Type的情况)
         */
        private boolean isCanDismiss() {
            return (getBaseType() != null && getBaseType().getType() != null) && (onDismissCheckListener == null || onDismissCheckListener.isCanDismiss());
        }

        /**
         * 内部调用
         */
        private void closeByManager() {
            closeByManager = true;
        }

        /**
         * 分发事件(Type dismiss后调用)
         */
        protected void dispatch() {
            /*被优先级机制暂时隐藏的弹窗不移除*/
            if (!closeByManager) {
                DialogManager.getInstance().remove(this);
            }
            DialogManager.getInstance().showNext();
        }

        public BaseType<Type> getBaseType() {
            return baseType;
        }

        public int getPriority() {
            return priority;
        }

        public String getUuid() {
            return uuid;
        }

        /**
         * 展示当前Type
         */
        private void show() {
            if (isCanShow()) {
                /*有些Type是优先级机制暂时关闭的,重新show之后需要重置closeByManager,这样在主动dismiss当前的时候才会remove掉*/
                closeByManager = false;
                getBaseType().show(this);
            }
        }

        /**
         * 隐藏当前Type
         */
        private void dismiss() {
            if (isCanShow()) {
                getBaseType().dismiss(this);
            }
        }

        public static class Builder<Type> {
            private BaseType<Type> baseType;
            private int priority;
            private OnShowCheckListener onShowCheckListener;
            private OnDismissCheckListener onDismissCheckListener;

            public Builder<Type> as(BaseType<Type> baseType) {
                this.baseType = baseType;
                return this;
            }

            public Builder<Type> priority(int priority) {
                this.priority = priority;
                return this;
            }

            public Builder<Type> onShowCheckListener(OnShowCheckListener onShowCheckListener) {
                this.onShowCheckListener = onShowCheckListener;
                return this;
            }

            public Builder<Type> onDismissCheckListener(OnDismissCheckListener onDismissCheckListener) {
                this.onDismissCheckListener = onDismissCheckListener;
                return this;
            }

            public Config<Type> build() {
                return new Config<>(this);
            }

        }

    }

    /**
     * Type包装类
     */
    public abstract static class BaseType<Type> {
        private Type type;

        public BaseType(Type type) {
            this.type = type;
        }

        public Type getType() {
            return type;
        }

        abstract void init(Config<Type> config);

        abstract void show(Config<Type> config);

        abstract void dismiss(Config<Type> config);
    }

    /**
     * 当前Type show条件检测(不用考虑其它Type的情况)-默认返回true,可外部实现更改判断条件
     */
    public interface OnShowCheckListener {
        boolean isCanShow();
    }

    /**
     * 当前Type dismiss条件检测(不用考虑其它Type的情况)-默认返回true,可外部实现更改判断条件)
     */
    public interface OnDismissCheckListener {
        boolean isCanDismiss();
    }
}

代码的注释挺详细的,就不做过多说明了,有问题的话可随时联系我或评论区留言。

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一个通用的Android弹窗管理框架,内部维护弹窗优先级队列 具备弹窗管理扩展功能 整合Dialog,PoupoWindow,悬浮Widget,透明Webview,Toast,SnackBar,无需再为繁琐的业务弹窗逻辑所困扰如何添加依赖只需要两行代码轻松接入//add this to your repositories  maven { url 'https://www.jitpack.io' } //add this to your dependencies implementation 'com.github.MrCodeSniper:PopLayer:2.0.0'具体如何使用1.根据策略创建对应的弹窗view//Dialog形式 PopLayerView  mLayerView = new PopLayerView(this,R.layout.common_dialog_upgrade_app); //透明Webview形式 PopLayerView mLayerView = new PopLayerView(this,LayerConfig.redPocketScheme);2.开始装配弹窗配置Popi mUpgradePopi1 = new Popi.Builder()                 .setmPopId(4)//弹窗的唯一标识 当id发生改变 视为新的弹窗                 .setmPriority(2)//优先级这里不具体划分对应的范围 值越小优先级越高                 .setmCancelType(TRIGGER_CANCEL)//弹窗消失的类型分为 TRIGGER_CANCEL(触摸消失) COUNTDOWN_CANCEL (延时消失)                 .setMaxShowTimeLength(5)//最长显示时间(S)                 .setMaxShowCount(5)//最大显示次数                 .setmBeginDate(1548858028)//开始时间 2019-01-30 22:20:28                 .setmEndDate(1548944428)//结束时间 2019-01-31 22:20:28                 .setLayerView(mLayerView)//弹窗View                 .build();3.纳入弹窗管理//纳入弹窗管理 PopManager.getInstance().pushToQueue(mUpgradePopi); //开始显示弹窗 PopManager.getInstance().showNextPopi();效果预览未来的计划逐步统一 其他类型的弹窗 希望能提供给大家一个较为全面的应对业务需求的弹窗管理框架版本记录V1方案版本号LOG进度更新V1.0.0项目开源,完成弹窗管理与Dialog形式扩展Dialog策略扩展完成V1.0.1修复Dialog策略无法获取dialog实体bugDialog策略优化V1.0.2修复activity摧毁造成的弹窗异常 bugDialog策略优化V1.0.3优化了弹窗的使用更加方便快捷框架使用优化V2方案版本号LOG进度更新V2.0.0正式加入透明Webview弹窗策略扩展透明Webview策略扩展完成作者介绍Hello 我叫lalala,如果您喜欢这个项目 请给个star 能follow我那真是太好了!!
根据需求,以下是关于手环系统弹窗优先级的测试用例: 1. 测试用例名称:来电弹窗优先级 描述:验证来电弹窗在手环系统中的优先级是否正确 步骤: 1. 模拟来电事件 2. 检查手环系统是否正确显示来电弹窗 预期结果:来电弹窗应具有较高的优先级,能够及时显示并提醒用户 2. 测试用例名称:闹钟弹窗优先级 描述:验证闹钟弹窗在手环系统中的优先级是否正确 步骤: 1. 设置一个闹钟并等待触发 2. 检查手环系统是否正确显示闹钟弹窗 预期结果:闹钟弹窗应具有较高的优先级,能够及时显示并提醒用户 3. 测试用例名称:日程(事件)提醒弹窗优先级 描述:验证日程(事件)提醒弹窗在手环系统中的优先级是否正确 步骤: 1. 设置一个日程事件并等待触发 2. 检查手环系统是否正确显示日程(事件)提醒弹窗 预期结果:日程(事件)提醒弹窗应具有较高的优先级,能够及时显示并提醒用户 4. 测试用例名称:查找我的手表弹窗优先级 描述:验证查找我的手表弹窗在手环系统中的优先级是否正确 步骤: 1. 触发查找我的手表功能 2. 检查手环系统是否正确显示查找我的手表弹窗 预期结果:查找我的手表弹窗应具有较高的优先级,能够立即显示并引起用户的注意 5. 测试用例名称:定时器弹窗优先级 描述:验证定时器弹窗在手环系统中的优先级是否正确 步骤: 1. 设置一个定时器并等待触发 2. 检查手环系统是否正确显示定时器弹窗 预期结果:定时器弹窗应具有较高的优先级,能够及时显示并提醒用户 6. 测试用例名称:跌倒检测弹窗优先级 描述:验证跌倒检测弹窗在手环系统中的优先级是否正确 步骤: 1. 模拟跌倒事件 2. 检查手环系统是否正确显示跌倒检测弹窗 预期结果:跌倒检测弹窗应具有较高的优先级,能够及时显示并提醒用户 7. 测试用例名称:其他系统弹窗优先级 描述:验证其他系统弹窗(如系统更新、低电量等)在手环系统中的优先级是否正确 步骤: 1. 触发其他系统弹窗事件(如系统更新、低电量等) 2. 检查手环系统是否正确显示相应的弹窗 预期结果:其他系统弹窗应具有适当的优先级,能够及时显示并提醒用户 以上是关于手环系统弹窗优先级的测试用例,根据需要可以进一步细化和补充。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值