搜索
您的当前位置:首页正文

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

参与者

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

参考

  • 《设计模式 可复用面向对象软件的基础》第 5 章, 5.8 State (转态)

  • qrcode.jpg
Top