一种实现Android的流程页面方法

一种实现Android的流程页面方法

介绍

我们在应用开发的过程中,经常会遇到一整个模块,它里面比较完整的流程。就是这个流程中有很多的点(页面),有一个开始点(页面),多个结束点(页面),他是从开始点到结束点的实现方法。比如一个 常用的登陆流程,其实一个登陆流程也是比较复杂的包含好多的页面,首页A (登陆首页)包含验证码登陆,微信登录等选择;页面B(输入手机号或者用户名);页面C (输入密码);页面D:(更改用户);页面E(更改密码);页面F(更改手机号等),页面G (设置手势密码);页面H(登陆成功);页面I(登陆失败)。

比如下面的这张图(图1)是一个流程的模块,A点是开始G点和H点是两个结束的点,这个图是我随意画的。这个流程里面 A到H的页面可能都会展示到,我们开发应用的时候普通页面判断跳转也可以实现总感觉有点繁琐,今天介绍的就是一种比较简洁的一种方法去实现这种流程。


图1

方法的来源

这个方法是原生TvSetting里面的实现,因为最近QA 提个一些关于原生TVSetting的问题,问题里面有好多关于流程比如网络连接,蓝牙连接等流程的问题,它里面大量用到了一个State的Class,每个流程点都继承这个class,为了解问题,就了解了一下他怎么实现的。初看觉得复杂,后来觉得挺简洁的。

实现的原理

关键的核心class 有三个,1是一个interface名字是State,每个页面就是一个state,里面对应一个fragment;2 第二个class 是Transition,是定义两个State的关系的;第三个class 是StateMachine,用来管理这个流程的所有State的。
实现的原理是, 流程初始化的时候把所有的页面的对应的State(State的子类)初始化;然后把每两个State的关系Transition也都初始化;然后把所有的State和Transtion 都放到StateMachine里面,让他去管理所有的页面。当每个State达到一定条件的时候(比如点击)去调用StateMachine里面的对应的transation方法,就会跳转到新的State页面。

下面代码分析是我自己写的的demo。

State介绍

public interface State {

  /**
   * Process when moving forward.  前进跳到本页面会执行的方法
   */
  void processForward();

  /**
   * Process when moving backward. 后退跳到本页面 会执行的方法 。
   */
  void processBackward();

  /**
   * Listener for sending notification about state completion.
在StateMachine里面实现,state 里面 用来通知本state结束,通过event StateMachine 知道要去哪个界面。
   */
  interface StateCompleteListener {
      void onComplete(@StateMachine.Event int event);
  }

  /**
   * Listener for sending notification about fragment change. 这个方法的实现主要在Activity 里面 ,用来切换界面展示的。
   */
  interface FragmentChangeListener {
      void onFragmentChange(Fragment newFragment, boolean movingForward);
  }

  /**
   * @return the corresponding fragment for current state. If there is no corresponding fragment,
   * return null.
   */
  Fragment getFragment();
}

解读一下这几个方法

  • processForward
    被别人告知要进入本页面了,你接下来要做好准备,准备好fragment 让他进入页面。
  • processBackward
    被别人告知要别的页面要结束,要回到你这个页面了,你接下来要做好准备,准备好fragment 让他进入页面。
  • StateCompleteListener
    State 里面调用,StateMachine实现的。
    这个Listenner 是通知StateMachine我这边OK了,我这个Event 结束了,接下来根据Event 去找别的界面让他显示吧。
  • FragmentChangeListener
    State 里面调用,Activity实现的。
    Activity 实现了这个接口,State界面告诉管理界面展示的(Activity),我准备好界面了,我要展示界面,你来展示吧,
    Activity 接受到之后就展示了。

Transition 介绍

public class Transition {
    public State source;
     // event   source 经过 event 进入 destination
    public @StateMachine.Event int event;
    public State destination;

    public Transition(State source, @StateMachine.Event int event, State destination) {
        this.source = source;
        this.event = event;
        this.destination = destination;
    }
}

Transition 是用来设置 两个State之间的关系的。event 是两个之间的关系,state 里面的
void onComplete(@StateMachine.Event int event); 这个方法就是通知StateMachine,然后StateMachine遍历Transition, 让他根据 state 和 event 找另一个state。

StateMachine

这是个管理的State的class,也是最重要的一个class。

/**
 * State machine responsible for handling the logic between different states.
 */
public class StateMachine extends ViewModel {

    private Callback mCallback;
    private Map<State, List<Transition>> mTransitionMap = new HashMap<>();
    private LinkedList<State> mStatesList = new LinkedList<>();
    private State.StateCompleteListener mCompletionListener = this::updateState;
    //  登陆 流程  1 输入用户名 2 输入密码 3 成功 4 失败
    public static final int CONTINUE = 0;
    public static final int CANCEL = 1;
    public static final int FAIL = 2;
    public static final int EARLY_EXIT = 3;

    public static final int LOGIN_ENTER_PSD = 5;
    public static final int LOGIN_SUCCESS = 6;
    public static final int LOGIN_FAILED = 7;

    @IntDef({
            CONTINUE,
            CANCEL,
            FAIL,
            EARLY_EXIT,
            LOGIN_ENTER_PSD,
            LOGIN_SUCCESS,
            LOGIN_FAILED
           })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Event {
    }

    public StateMachine() {
    }

    public StateMachine(Callback callback) {
        mCallback = callback;
    }

    /**
     * Set the callback for the things need to done when the state machine leaves end state.
     */
    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    /**
     * Add state with transition.
     *
     * @param state       start state.
     * @param event       transition between two states.
     * @param destination destination state.
     */
    public void addState(State state, @Event int event, State destination) {
        if (!mTransitionMap.containsKey(state)) {
            mTransitionMap.put(state, new ArrayList<>());
        }
        mTransitionMap.get(state).add(new Transition(state, event, destination));
    }

    /**
     * Add a state that has no outward transitions, but will end the state machine flow.
     */
    public void addTerminalState(State state) {
        mTransitionMap.put(state, new ArrayList<>());
    }

    /**
     * Enables the activity to be notified when state machine enter end state.
     */
    public interface Callback {
        /**
         * Implement this to define what to do when the activity is finished.
         *
         * @param result the activity result.
         */
        void onFinish(int result);
    }

    /**
     * Set the start state of state machine/
     *
     * @param startState start state.
     */
    public void setStartState(State startState) {
        mStatesList.addLast(startState);
    }

    /**
     * Start the state machine.
     */
    public void start(boolean movingForward) {
        if (mStatesList.isEmpty()) {
            throw new IllegalArgumentException("Start state not set");
        }
        State currentState = getCurrentState();
        if (movingForward) {
            currentState.processForward();
        } else {
            currentState.processBackward();
        }
    }

    /**
     * Initialize the states list.
     */
    public void reset() {
        mStatesList = new LinkedList<>();
    }

    /**
     * Make the state machine go back to the previous state.
     */
    public void back() {
        updateState(CANCEL);
    }

    /**
     * Return the current state of state machine.
     */
    public State getCurrentState() {
        if (!mStatesList.isEmpty()) {
            return mStatesList.getLast();
        } else {
            return null;
        }
    }

    /**
     * Notify state machine that current activity is finished.
     *
     * @param result the result of activity.
     */
    public void finish(int result) {
        mCallback.onFinish(result);
    }

    private void updateState(@Event int event) {
        // Handle early exits first.
        if (event == EARLY_EXIT) {
            finish(Activity.RESULT_OK);
            return;
        } else if (event == FAIL) {
            finish(Activity.RESULT_CANCELED);
            return;
        }

        // Handle Event.CANCEL, it happens when the back button is pressed.
        if (event == CANCEL) {
            if (mStatesList.size() < 2) {
                mCallback.onFinish(Activity.RESULT_CANCELED);
            } else {
                mStatesList.removeLast();
                State prev = mStatesList.getLast();
                prev.processBackward();
            }
            return;
        }

        State next = null;
        State currentState = getCurrentState();

        List<Transition> list = mTransitionMap.get(currentState);
        if (list != null) {
            for (Transition transition : mTransitionMap.get(currentState)) {
                if (transition.event == event) {
                    next = transition.destination;
                }
            }
        }

        if (next == null) {
            if (event == CONTINUE) {
                mCallback.onFinish(Activity.RESULT_OK);
                return;
            }
            throw new IllegalArgumentException(
                    getCurrentState().getClass() + "Invalid transition " + event);
        }

        addToStack(next);
        next.processForward();
    }

    private void addToStack(State state) {
        for (int i = mStatesList.size() - 1; i >= 0; i--) {
            if (equal(state, mStatesList.get(i))) {
                for (int j = mStatesList.size() - 1; j >= i; j--) {
                    mStatesList.removeLast();
                }
            }
        }
        mStatesList.addLast(state);
    }

    private boolean equal(State s1, State s2) {
        if (!s1.getClass().equals(s2.getClass())) {
            return false;
        }
        return true;
    }

    public State.StateCompleteListener getListener() {
        return mCompletionListener;
    }
}

先看一下重要的方法和成员变量

  • mStatesList
    mStatesList 是state的一个列表,是一个栈的数据结构,保存的是当前的列表里面已经存在的state,根据当前页面的减少和增加实时的变化。addToStack这个方法是添加的方法,他会把之前存在的state 删除掉,在添加到栈顶。相当于是把栈中的提到栈顶。

  • mTransitionMap
    mTransitionMap 是一个map 结构 key是state ,value 是一个transtion的列表。mTransitionMap 的内容是跟页面(Activity)初始化的时候去填充的,把所有的state的关系都先定义好。一般情况定义好之后中间过程就不变了。通过 addState 这个方法去填充的。

+mCallback
mCallback 一般是在activity实现的,用来通知activity 你该finish了,并且把流程结果告诉activity。

  • mCompletionListener
    mCompletionListener 是每个State里面去调用的,用来通知StateMachine 我该结束了,你去判断接下来该显示哪个界面。

  • interface Event
    Event 是我们定义的各种类型的state之前的关系,是transation的主要参数,原生setting的定义的类型比较多,我这个以简单登陆流程为例。
    // 登陆 流程 输入用户名 输入密码 成功 失败
    public static final int CONTINUE = 0;
    public static final int CANCEL = 1;
    public static final int FAIL = 2;
    public static final int EARLY_EXIT = 3;

    public static final int LOGIN_ENTER_PSD = 5;
    public static final int LOGIN_SUCCESS = 6;
    public static final int LOGIN_FAILED = 7;
    上面的 CONTINUE CANCEL FAIL EARLY_EXIT 这几个一般是固定的定义的类型。一般如果是一条直线的流程就可以不在定义新的, 需要分叉比较复杂的就需要定义新的了。
    比如我们简单的登陆流程有输入用户名,输入密码,登陆成功,登陆失败四个页面。定义输入用户名页面 EnterNameState;输入密码页面是EnterPsdState ;登录成功页面是 LoginSuccessState;登录失败页面是LoginFailedState。
    EnterNameState 和 EnterPsdState的关系就是 Event LOGIN_ENTER_PSD;
    EnterPsdState 和 LoginSuccessState 的关系就是Event LOGIN_SUCCESS;
    LOGIN_FAILED 和 LOGIN_ENTER_PSD 的关系就是Event LOGIN_SUCCESS;
    我们要把所有的关系都先定义后,以后满足什么条件就可以按照这个关系去进行了。

  • updateState 方法
    mCompletionListener 的方法就是走到updateState这个方法,参数是一个Event。这个方法是主要核心的寻找页面的方法。
    判断 event 类型
    步骤1 EARLY_EXIT 类型的话通知activity finish 原因是 success
    步骤2 FAIL 类型的话通知activity finish 原因 failed
    步骤3 CANCEL 类型的话 判断栈的个数 小于2的话 通知activity finish 原因 cancel。个数多的话 把栈顶的state 删除掉 在展示新的栈顶的state。
    步骤4 其他类型Event,找到currentState 通过transationmap 找到 transtion的List再找到 event 一样的state,然后把这个state并把他加到站顶并展示。

登陆流程例子

登陆流程一共有 有输入用户名,输入密码,登陆成功,登陆失败四个页面,也就是四个state。我们接下来看一下Activity的代码和state的实现来分析一下。

Activity的代码如下

public class LoginActivity extends AppCompatActivity  implements State.FragmentChangeListener{
    private static final String TAG = "LoginActivity";
    private StateMachine mStateMachine;
    // finish  set result
    private final StateMachine.Callback mStateMachineCallback = result -> {
        setResult(result);
        finish();
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        // 初始化
        mStateMachine = new ViewModelProvider(this).get(StateMachine.class);
        // set result callback
        mStateMachine.setCallback(mStateMachineCallback);
        State mEnterNameState = new EnterNameState(this);
        State mEnterPsdState = new EnterPsdState(this);
        State mSuccessState = new LoginSuccessState(this);
        State mFailedState = new LoginFailedState(this);

        mStateMachine.addState(mEnterNameState,StateMachine.LOGIN_ENTER_PSD, mEnterPsdState);
        mStateMachine.addState(mEnterPsdState,StateMachine.LOGIN_SUCCESS, mSuccessState);
        mStateMachine.addState(mEnterPsdState,StateMachine.LOGIN_FAILED, mFailedState);
        mStateMachine.setStartState(mEnterNameState);
        mStateMachine.start(true);
    }

    @Override
    public void onBackPressed() {
        mStateMachine.back();
    }
    @Override
    public void onFragmentChange(Fragment newFragment, boolean movingForward) {
        updateView(newFragment,movingForward);
    }
    /**
     *
     * @param fragment
     * @param movingForward 前进还是后退 设置 FragmentTransaction 设置 展示的动画
     */
    private void updateView(Fragment fragment, boolean movingForward) {
        if (fragment != null) {
            FragmentTransaction updateTransaction = getSupportFragmentManager().beginTransaction();
            if (movingForward) {
                updateTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
            } else {
                updateTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
            }
            updateTransaction.replace(R.id.fragment_container, fragment, TAG);
            updateTransaction.commit();
        }
    }

activity 里面主要有两个重要的小块功能;

  • 1 初始化
    初始化每个页面对应的State,并且把所有的state之间的关系通过Event添加到statemachine里面。
    并且往栈里面放一个默认页面的state,然后调用start方法展示

  • 2 实现 onFragmentChange这个方法
    参数1 时要展示的页面,参数2 说的是要前进展示的还是返回展示的。代码里面是根据参数2 设置选择展示的动画。

输入密码的State 代码如下

public class EnterPsdState implements State{
    FragmentActivity mActivity;

    public EnterPsdState(FragmentActivity activity) {
        this.mActivity = activity;
    }

    private Fragment mFragment;

    @Override
    public void processForward() {
        mFragment = EnterPsdFragment.newInstance("processForward");
        FragmentChangeListener listener = (FragmentChangeListener) mActivity;
        listener.onFragmentChange(mFragment,true);
    }

    @Override
    public void processBackward() {
        mFragment = EnterPsdFragment.newInstance("processForward");
        FragmentChangeListener listener = (FragmentChangeListener) mActivity;
        listener.onFragmentChange(mFragment,false);
    }

    @Override
    public Fragment getFragment() {
        return mFragment;
    }



    public static class EnterPsdFragment extends Fragment implements View.OnClickListener {

        public static EnterPsdFragment newInstance(String param){
            EnterPsdFragment enterNameFragment = new EnterPsdFragment();
            Bundle args = new Bundle();
            args.putString("arg1",param);
            enterNameFragment.setArguments(args);
            return enterNameFragment;
        }

        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View inflate = inflater.inflate(R.layout.state_enter_psd, null);
            return inflate;
        }

        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            initView(view);
            super.onViewCreated(view, savedInstanceState);
        }

        private void initView(View rootView){
            rootView.findViewById(R.id.success).setOnClickListener(this);
            rootView.findViewById(R.id.failed).setOnClickListener(this);
            rootView.findViewById(R.id.exit).setOnClickListener(this);
        }

        @SuppressLint("NonConstantResourceId")
        @Override
        public void onClick(View view) {
            StateMachine stateMachine = new ViewModelProvider(requireActivity()).get(StateMachine.class);
            switch (view.getId()) {
                case R.id.success:
                    stateMachine.getListener().onComplete(StateMachine.LOGIN_SUCCESS);
                    break;
                case R.id.failed:
                    stateMachine.getListener().onComplete(StateMachine.LOGIN_FAILED);
                    break;
                case R.id.exit:
                    stateMachine.getListener().onComplete(StateMachine.EARLY_EXIT);
                    break;

            }
        }
    }
}

输入密码页面 EnterPsdState里面有两个模块比较重要

  • 模块1 Fragment模块
    这个Fragment 是State 真实的页面实现者,经常会有一写点击事件等条件触发,去调用StateMachine去管理state。
  • 模块2 processForward 和 processBackward 方法 获取state对应的fragment 并且调用activity的展示方法去展示当前页面。

对于这个流程我画了一个流程图 如下图。


登陆流程图.png

上面的图里面是一个输入用户面,输入密码 ,登陆成功三个过程中Activity 和State 和 Statemachine之间的调用关系,看图的流程的时候可以同时看上面的代码部分。
流程1 初始化 state 并 给StateMachine 填充数据
流程2 StateMachine 添加一个state 并展示EnterNameState 调用state的processForward 方法
流程3 EnterNameState 获取fragment 调用activity updatefragment 展示页面
流程4 EnterNameState 的fragment 满足条件 输入完名字 调用 Statemachine的updatestate 寻找接下来的页面。
流程5 寻找到输入密码的PsdState,调用state的processForward 方法
流程6 PsdState 获取fragment 调用activity updatefragment 展示页面
流程7 PsdState 的fragment 满足条件 登录成功 调用 Statemachine的updatestate 寻找接下来的页面 。
流程8 寻找到登陆成功的SuccessState,调用state的processForward 方法
流程9 SuccessState 获取fragment 调用activity updatefragment 展示页面

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

推荐阅读更多精彩内容