State 状态模式在 Android 多弹窗的应用

序言

最近项目的首页弹窗进行调整,要加几个弹窗,而且还是要按顺序弹出的。原来的只有悬浮窗权限弹窗和存储权限弹窗,用一两个标志位就可以解决了。现在加了隐私协议弹窗和青少年模式弹窗,变成了四个弹窗,如果还是按照原来的方法,即加标志位解决,逻辑机会变得非常复杂,也很容易出 Bug.

经过调研,发现可以用 state 转态模式去解决这个问题。

下面我们先看看 state 转态模式

State 状态模式

意图

State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类

State 模式与状态机有着类似的地方,都是从一个状态切换到另一个状态


state_2.png

State 模式的类图

state_1.png

图片来源 design-patterns: State

参与者

Context 上下文

  • 定义 Client 感兴趣的接口
  • 维护一个 ConcreteState 子类的实例,这个实例是当前的 state

State 状态

定义接口以封装与 Context 的一个特定状态相关的行为.

ConcreteState 具体状态类

每一个子类实现一个与 Context 的一个状态相关的行为

协作

  1. Context 将状态相关的请求委托给当前的 ConcreteState 对象处理

  2. Context 可以将自身作为一个参数传递给 state, 让 state 可以访问到 Context

  3. Context 是 client 使用的主要接口, 一般情况下 client 不需要直接与 state 打交道

  4. Context 或 ConcreteState 子类都可以决定 next state, 以及设置转态转换的条件

适用性

  1. 一个对象的行为取决于它的状态,并且必须在运行时刻根据状态改变它的行为

  2. 一个操作中含有庞大的分支条件,并且这些分支依赖该对象的状态

实现过程

  1. 确定 Context 上下文

  2. 确定 state 的接口

  3. 继承 state 接口,实现具体 state 类

  4. 在 Context 中,用一个成员变量指向当前的状态,并且提供对外方法可以设置这个值

  1. 初始化一个开始的 State 并传进 context。

State 模式在 Android 中的应用

回到我们文章开头的问题,我们想要把弹窗顺序的弹出,刚好和 state 模式中的状态切换是一致的。 第一个弹窗弹窗后,切换到另一个状态,下个弹窗是否弹出,完全取决于所在的状态。这样就可以减少一堆的标志位判断了。

这样说比较抽象,可以结合下面的例子来看;

需求:

在第一个弹窗之后,点击确认或者取消;
弹窗第二个弹窗,点击第二个弹窗的确认或者取消,弹窗第三个弹窗;
点击第三个弹窗,结束;

StateDialog 的设计

下面是类图

state 模式1.png

DialogContext 是上下文,用来存储当前 DialogState,在 nextDialogState 方法设置下个状态

public class DialogContext {

    private BaseDialogState mCurrentDialogState;

    private Activity mActivity;

    public DialogContext(Activity activity) {
        mActivity = activity;
    }

    public void nextDialogState(BaseDialogState baseDialogState) {
        mCurrentDialogState = baseDialogState;
        mCurrentDialogState.setDialogContext(this); // 将自身作为参数传递给 DialogState
        mCurrentDialogState.handle(); // 同时调用 DialogState#handle 方法
    }

    public Activity getActivity() {
        return mActivity;
    }
    
    public void onResume() {
        if (mCurrentDialogState != null){
            mCurrentDialogState.onResume();
        }
    }
}

BaseDialogState 是做弹窗的基类,提供 nextDialogState 和 handle 方法
在 handle 方法里面进行自身逻辑的处理

public abstract class BaseDialogState {
    protected static final String TAG = "DialogState";
    protected DialogContext mDialogContext;
    protected Activity mActivity;

    public void setDialogContext(DialogContext dialogContext) {
        mDialogContext = dialogContext;
        mActivity = dialogContext.getActivity();
    }

    // 进行自身逻辑处理
    public abstract void handle();

    /**
     * 设置下一个 state
     */
    protected abstract void nextDialogState(); 
    
    public void onResume(){

    }
}

IDialogStateManager 和它的实现类 DialogStateManager 是 Activity 连接 DialogContext 的中介,相当于 Client。

public interface IDialogStateManager {

    void init(Activity activity);

    void start();

    void onResume();

}

// DialogStateManager.java
public class DialogStateManager implements IDialogStateManager{

    private DialogContext mDialogContext;

    private boolean mIsStarted; // 首次启动

    @Override
    public void init(Activity activity) {
        mDialogContext = new DialogContext(activity);
    }

    @Override
    public void start() {
        if (mDialogContext != null && !mIsStarted){
            mIsStarted = true;
            // 设置第一个 state
            mDialogContext.nextDialogState(new DialogOneState());
        }
    }

    @Override
    public void onResume() {
        mDialogContext.onResume();
    }
}

调用过程

在 MainActivity 调用 DialogStateManager,进行管理

调用的时序图


首页弹窗的时序图1.png
public class MainActivity extends AppCompatActivity {
    
    private TextView mTvStartDialog;
    private IDialogStateManager mDialogStateManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvStartDialog = (TextView) findViewById(R.id.tv_start);
        
        // 创建 DialogStateManager 并进行初始化
        mDialogStateManager = new DialogStateManager();
        mDialogStateManager.init(this);

        mTvStartDialog.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDialogStateManager.start(); // 启动状态
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        mDialogStateManager.onResume();
    }
}

最后我们看调用的效果

state_dialog_demo.gif

完整的代码已上传到 github

参考

  • 《设计模式 可复用面向对象软件的基础》第 5 章, 5.8 State (转态)
  • design-patterns: State
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容

  • 面向对象的六大原则 单一职责原则 所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于...
    JxMY阅读 922评论 1 3
  • 本文是《设计模式——可复用面对对象软件的基础》的笔记。 面对对象设计的几个原则:1.针对接口编程,而不是针对实现编...
    Lension阅读 1,158评论 0 0
  • 下面总结设计模式中的行为型模式: 1.责任链模式 顾名思义,责任链模式(Chain of Responsibili...
    Steven1997阅读 3,429评论 0 1
  • 1.状态模式的定义 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被...
    书上的烟灰阅读 933评论 0 4
  • 高考不过一场修行,分数多少并非最终。只要你在一直前行,余生就有很多可能。对于考生来说,高考决定你读哪所大学...
    水晶之语阅读 225评论 0 1