一種實現Android的流程頁面方法
介紹
我們在應用開發(fā)的過程中,經常會遇到一整個模塊,它里面比較完整的流程。就是這個流程中有很多的點(頁面),有一個開始點(頁面),多個結束點(頁面),他是從開始點到結束點的實現方法。比如一個 常用的登陸流程,其實一個登陸流程也是比較復雜的包含好多的頁面,首頁A (登陸首頁)包含驗證碼登陸,微信登錄等選擇;頁面B(輸入手機號或者用戶名);頁面C (輸入密碼);頁面D:(更改用戶);頁面E(更改密碼);頁面F(更改手機號等),頁面G (設置手勢密碼);頁面H(登陸成功);頁面I(登陸失?。?/p>
比如下面的這張圖(圖1)是一個流程的模塊,A點是開始G點和H點是兩個結束的點,這個圖是我隨意畫的。這個流程里面 A到H的頁面可能都會展示到,我們開發(fā)應用的時候普通頁面判斷跳轉也可以實現總感覺有點繁瑣,今天介紹的就是一種比較簡潔的一種方法去實現這種流程。

方法的來源
這個方法是原生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. 前進跳到本頁面會執(zhí)行的方法
*/
void processForward();
/**
* Process when moving backward. 后退跳到本頁面 會執(zhí)行的方法 。
*/
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 真實的頁面實現者,經常會有一寫點擊事件等條件觸發(fā),去調用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 展示頁面