一种实现Android的流程页面方法
介绍
我们在应用开发的过程中,经常会遇到一整个模块,它里面比较完整的流程。就是这个流程中有很多的点(页面),有一个开始点(页面),多个结束点(页面),他是从开始点到结束点的实现方法。比如一个 常用的登陆流程,其实一个登陆流程也是比较复杂的包含好多的页面,首页A (登陆首页)包含验证码登陆,微信登录等选择;页面B(输入手机号或者用户名);页面C (输入密码);页面D:(更改用户);页面E(更改密码);页面F(更改手机号等),页面G (设置手势密码);页面H(登陆成功);页面I(登陆失败)。
比如下面的这张图(图1)是一个流程的模块,A点是开始G点和H点是两个结束的点,这个图是我随意画的。这个流程里面 A到H的页面可能都会展示到,我们开发应用的时候普通页面判断跳转也可以实现总感觉有点繁琐,今天介绍的就是一种比较简洁的一种方法去实现这种流程。
方法的来源
这个方法是原生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的展示方法去展示当前页面。
对于这个流程我画了一个流程图 如下图。
上面的图里面是一个输入用户面,输入密码 ,登陆成功三个过程中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 展示页面