背景
项目需要在首页弹一系列弹窗,每个弹窗是否弹出都有自己的策略,以及哪个优先弹出,哪个在上一个关闭后再弹出,为了更好管理,于是封装了一个Dialog管理工具
效果
- 整体采用责任链模块设计,控制优先级及弹出策略
原理分析
- 每个弹窗都当做一个节点Node,抽象出一些公共接口
public interface Node {
int getId();
String getTag();
void complete();
void error(ChainException e);
}
- 定义弹窗的状态
INIT:初始创建
PROGRESS:开始弹出
COMPLETE:完成弹出到关闭的流程
ERROR:弹出错误
public interface Operation {
State.INIT INIT = new State.INIT();
State.PROGRESS PROGRESS = new State.PROGRESS();
State.COMPLETE COMPLETE = new State.COMPLETE();
abstract class State {
State() {
}
public static final class INIT extends State {
private INIT() {
super();
}
@Override
@NonNull
public String toString() {
return "INIT";
}
}
public static final class PROGRESS extends State {
private PROGRESS() {
super();
}
@Override
@NonNull
public String toString() {
return "PROGRESS";
}
}
public static final class COMPLETE extends State {
private COMPLETE() {
super();
}
@Override
@NonNull
public String toString() {
return "COMPLETE";
}
}
public static final class ERROR extends State {
private final Throwable mThrowable;
public ERROR(@NonNull Throwable exception) {
super();
mThrowable = exception;
}
@NonNull
public Throwable getThrowable() {
return mThrowable;
}
@Override
@NonNull
public String toString() {
return String.format("ERROR (%s)", mThrowable.getMessage());
}
}
}
}
- 为Dialog节点定义具体的状态切换
public class DialogNode implements Node {
private static final String TAG = "ChainNode";
private int id;
private String tag;
private Operation.State state = Operation.INIT;
private Executor executor;
private CallBack callBack;
private DialogNode(int id, String tag, Executor executor) {
this.id = id;
this.tag = tag;
this.executor = executor;
}
public static DialogNode create(int id, Executor executor) {
return create(id, TAG + id, executor);
}
public static DialogNode create(int id, String tag, Executor executor) {
return new DialogNode(id, tag, executor);
}
@Override
public void complete() {
setState(Operation.COMPLETE);
if (callBack != null) {
callBack.onComplete();
}
}
@Override
public void error(ChainException e) {
setState(Operation.COMPLETE);
if (callBack != null) {
callBack.onError(e);
}
}
public void process(CallBack callBack) {
this.callBack = callBack;
if (executor != null) {
executor.execute(this);
}
}
public static String getTAG() {
return TAG;
}
@Override
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public Executor getExecutor() {
return executor;
}
public void setExecutor(Executor executor) {
this.executor = executor;
}
public CallBack getCallBack() {
return callBack;
}
public void setCallBack(CallBack callBack) {
this.callBack = callBack;
}
public Operation.State getState() {
return state;
}
public void setState(Operation.State state) {
this.state = state;
}
@Override
public String toString() {
return "ChainNode{" +
"id=" + id +
", tag='" + tag + '\'' +
", state=" + state +
", executor=" + executor +
", callBack=" + callBack +
'}';
}
public interface CallBack {
void onComplete();
void onError(ChainException e);
}
}
- 抽象DialogNode的构建工厂类,弹窗链上每个Dialog必须继承该类,并实现createDialog方法返回具体的业务Dialog;实现execute方法控制弹窗是否要弹出,以及通知工具什么时候完成弹出到关闭的流程
abstract class MDialogNodeCreator {
protected var nodeDialog: Dialog? = null
fun build(context: Context, dialogId: Int): DialogNode? {
nodeDialog = createDialog(context)
val node = DialogNode.create(dialogId) { node ->
execute(node)
}
return node
}
/**
* 构造一个对话框
*/
abstract fun createDialog(context: Context): Dialog
/**
* 在此执行业务弹窗逻辑
*/
abstract fun execute(node: Node)
}
- ChainProcessor核心类,保存了每一个DialogNode,在调用start后开始从队列里面去头节点,当DialogNode回调onComplete后递归取下一个节点,直到队列尾部
public class ChainProcessor {
private final SparseArray<DialogNode> nodeArrays;
private final Builder builder;
private ChainProcessor(SparseArray<DialogNode> nodeArrays, Builder builder) {
this.nodeArrays = nodeArrays;
this.builder = builder;
}
public void start() {
if (nodeArrays == null || nodeArrays.size() <= 0) {
Log.e("zbm111", "nodeArrays == null || nodeArrays.size <= 0");
return;
}
startNode(nodeArrays.keyAt(0));
}
private void startNode(int nodeId) {
int index = nodeArrays.indexOfKey(nodeId);
DialogNode node = nodeArrays.valueAt(index);
if (node != null && node.getState() == Operation.INIT) {
node.setState(Operation.PROGRESS);
node.process(new DialogNode.CallBack() {
@Override
public void onComplete() {
nextNode(index);
}
@Override
public void onError(ChainException e) {
cancel();
}
});
}
}
private void nextNode(int index) {
//移除执行过的第一个
removeNode(index);
if (nodeArrays != null && nodeArrays.size() > 0) {
startNode(nodeArrays.keyAt(0));
}
}
private void removeNode(int index) {
if (nodeArrays != null && nodeArrays.size() > 0) {
nodeArrays.removeAt(index);
}
}
private void cancel() {
if (nodeArrays != null && nodeArrays.size() > 0) {
nodeArrays.clear();
}
}
public Builder getBuilder() {
return builder;
}
public static class Builder {
private final SparseArray<DialogNode> nodeArrays;
private String tag;
public Builder() {
this.nodeArrays = new SparseArray<>();
}
public Builder addNode(DialogNode node) {
if (node != null) {
nodeArrays.append(node.getId(), node);
if (TextUtils.isEmpty(tag)) {
tag = UUID.randomUUID().toString();
}
node.setTag(tag);
}
return this;
}
public Builder addNodes(DialogNode... nodes) {
if (nodes != null && nodes.length > 0) {
for (DialogNode node : nodes) {
addNode(node);
}
}
return this;
}
public Builder addTag(String tag) {
this.tag = tag;
return this;
}
public ChainProcessor build() {
checkTag();
return new ChainProcessor(nodeArrays, this);
}
private void checkTag() {
if (nodeArrays.size() > 0) {
if (TextUtils.isEmpty(tag)) {
tag = UUID.randomUUID().toString();
}
for (int i = 0; i < nodeArrays.size(); i++) {
nodeArrays.get(nodeArrays.keyAt(i)).setTag(tag);
}
}
}
public String getTag() {
return tag;
}
public SparseArray<DialogNode> getNodes() {
return nodeArrays;
}
}
}
- 工具外部接口,主要是构建ChainProcessor,支持处理多个弹窗链
bject MDialogChainHelper {
private val chainNodeMap = mutableMapOf<String, ChainProcessor>()
private fun build(chainProcessor: ChainProcessor) {
if (chainNodeMap.containsKey(chainProcessor.builder.tag)) {
chainNodeMap.remove(chainProcessor.builder.tag)
}
chainNodeMap[chainProcessor.builder.tag] = chainProcessor
}
fun startDialogChain(tag: String) {
chainNodeMap[tag]?.start()
}
private fun clearAllChain() {
chainNodeMap.clear()
}
private fun clearDialogChain(tag: String): ChainProcessor? {
return chainNodeMap.remove(tag)
}
fun addDialogChain(tag: String): ChainProcessor {
val chainProcessor = ChainProcessor.Builder().addTag(tag).build()
if (chainNodeMap.containsKey(chainProcessor.builder.tag)) {
chainNodeMap.remove(chainProcessor.builder.tag)
}
chainNodeMap[chainProcessor.builder.tag] = chainProcessor
return chainProcessor
}
fun getDialogChain(tag: String): ChainProcessor? {
return chainNodeMap[tag]
}
}
Demo验证
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MDialogChainHelper.run {
addDialogChain("test_main")
.builder
.addNode(OneDialogNode().build(this@MainActivity, 0))
.addNode(TwoDialogNode().build(this@MainActivity, 1))
startDialogChain("test_main" )
}
}
}
class OneDialogNode: MDialogNodeCreator() {
override fun createDialog(context: Context): Dialog {
val dialog = Dialog(context)
dialog.setContentView(R.layout.dialog_one)
dialog.findViewById<View>(R.id.tv_title)?.setOnClickListener {
dialog.dismiss()
}
dialog.window?.setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT)
return dialog
}
override fun execute(node: Node) {
nodeDialog?.setOnDismissListener {
node.complete()
nodeDialog = null
}
nodeDialog?.show()
}
}
class TwoDialogNode: MDialogNodeCreator() {
override fun createDialog(context: Context): Dialog {
val dialog = Dialog(context)
dialog.setContentView(R.layout.dialog_two)
dialog.findViewById<View>(R.id.tv_title)?.setOnClickListener {
dialog.dismiss()
}
dialog.window?.setLayout(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
return dialog
}
override fun execute(node: Node) {
nodeDialog?.setOnDismissListener {
node.complete()
nodeDialog = null
}
nodeDialog?.show()
}
}
完整代码点击下载
大家觉得不错的点个赞鼓励下哦,有任何建议欢迎留言,谢谢🌹