1. 程式人生 > >Android中設計模式--狀態模式(將動作委託到當前狀態,狀態之間可以互相轉換)

Android中設計模式--狀態模式(將動作委託到當前狀態,狀態之間可以互相轉換)

   狀態模式:將狀態封裝成為獨立類,並將動作委託到當前狀態;狀態之間可以相互轉換,因為實現了相同的介面;狀態改變,則動作會跟著改變。

 理解

1.定義狀態介面,所有的狀態均實現該介面,這樣對於客戶(呼叫者)來說,狀態是可以替換的,客戶不關心具體的狀態是什麼,只是呼叫介面中的方法即可。

 2.狀態之間可以互相轉換,如:去自動售貨機買飲料,先投入硬幣,那麼這時,售貨機會將狀態從“請投幣”,轉換為“請選擇飲料”,這就是狀態的轉換。

 總結

 找出程式碼中整體邏輯相同,執行動作略有差異的地方,考慮用狀態模式來實現。定義統一的介面,不同的狀態實現相同的介面,介面方法中每種狀態有自己的特色,狀態之間可以相互影響和轉換。

 狀態模式圖如下:


例子

1.先定義介面,用於所有的狀態來實現該介面

public interface State {
	void doOK(); // 處理確定鍵
	void doBack(); // 處理返回鍵
	void doLeft(); // 處理左鍵
	void doRight(); // 處理右鍵
	MAbsoluteLayout getLayout(); //獲取佈局
	boolean showLeftIcon(); // 左側icon是否顯示
	boolean showRightIcon(); // 右側icon是否顯示
	void animationEndCallBack(); // 動畫結束回撥(用於光纖、耳機口、亮點動畫效果顯示)
	boolean dispatchKeyEvent(KeyEvent event);  // 按鍵處理

}

2.根據需要,定義不同的狀態類,但都要實現統一的介面
/**
 * 選擇頁,實現State介面
 *
 */
public class SelectView implements State {
	private MAbsoluteLayout mLayout;
	private Context mContext;
	private SelectAdapter mAdapter;
	private MListView mViewList;
	private changeViewCallBack mCallBack; // view切換回調

	@Override
	public void doOK() {
		return;
	}

	@Override
	public void doBack() {
		StaticFunction.getPageHelper().finishPage(KEY_PAGEID.PAGE_HOME);
		
	}

	@Override
	public void doLeft() {
		return;
	}

	@Override
	public void doRight() {
		return;
	}

	
	public SelectView(changeViewCallBack cb, Context context){
		mCallBack = cb;
		mContext = context;		
		initView();		
	}
	
	void initView(){
		View rootView = LayoutInflater.from(mContext).inflate(R.layout.view_select, null);
		mViewList = (MListView)rootView.findViewById(R.id.activity_home_list);
        mAdapter = new SelectAdapter(mCallBack, mContext);
		
		mViewList.setAdapter(mAdapter);
		mViewList.setFocusView(new FocusView(mContext));
		mViewList.setMFocus(true);
		if(null != mViewList.getTopCover()){
			mViewList.getTopCover().setVisibility(View.GONE);
		}
		mLayout = (MAbsoluteLayout)rootView.findViewById(R.id.view_select);
	}
	
	@Override
	public boolean dispatchKeyEvent(KeyEvent event){
		if(KeyEvent.ACTION_DOWN == event.getAction() && KeyCode.BACK == KeyCode.getKeyCode(event)){
			this.doBack();
			return true;
		}
		if (null != mViewList ) {
			mViewList.dispatchKeyEvent(event);
			return true;
		}
		return false;

	}

	@Override
	public MAbsoluteLayout getLayout() {
		return mLayout;
	}

	@Override
	public boolean showLeftIcon() {
		return false;
	}

	@Override
	public boolean showRightIcon() {
		return true;
	}

	@Override
	public void animationEndCallBack() {
		// TODO Auto-generated method stub
		
	}

}
3. 狀態之間可以相互替換,因為都實現了介面,所以不需要關心具體狀態,直接呼叫方法。這點和策略模式非常類似。策略模式請參考我的上一篇文章http://blog.csdn.net/adayabetter/article/details/45580841

與策略模式不同之處在於,狀態模式執行了相應動作後,可以改變當前狀態

舉個例子,進行狀態動作呼叫和狀態切換的應該由Activity負責,而具體狀態實現應該是View實現,程式碼如下:

public class GuideHomeActivity extends BaseActivity implements changeViewCallBack{
	private static final String TAG = "GuideHomeActivity";
	private State mNowState;
	private State mBeforeState;
	private MAbsoluteLayout mLayout;
	private MImageView mLeftIcon;
	private MImageView mRightIcon;
	private boolean canTranslate = true; // 是否可以移動
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		LogHelper.releaseLog(TAG, "onCreate");
		setContentView(R.layout.activity_home);				
		mLayout   = getViewById(R.id.container);
		mLeftIcon = getViewById(R.id.left_icon);
		mRightIcon = getViewById(R.id.right_icon);
		SelectView mView = new SelectView(this, getApplicationContext());
		addPage(mView);
		
	}
	
	@Override
	public boolean dispatchKeyEvent(KeyEvent event) {
		if(canTranslate == true){
			super.dispatchKeyEvent(event);			
			if (KeyEvent.ACTION_DOWN == event.getAction()) {
				if(null != mNowState && mNowState.dispatchKeyEvent(event)){
					return true;
				}
				switch (KeyCode.getKeyCode(event)) {
				case KeyCode.BACK:
					LogHelper.releaseLog(TAG, "dispatchKeyEvent--"+mNowState.getClass().getSimpleName()+"--"+"doBack()");
					mNowState.doBack();
					
					break;
				case KeyCode.LEFT:
					LogHelper.releaseLog(TAG, "dispatchKeyEvent--"+mNowState.getClass().getSimpleName()+"--"+"doLeft()");
					mNowState.doLeft();
					break;
				case KeyCode.RIGHT:
					LogHelper.releaseLog(TAG, "dispatchKeyEvent--"+mNowState.getClass().getSimpleName()+"--"+"doRight()");
					mNowState.doRight();
					break;
				case KeyCode.OK:
					LogHelper.releaseLog(TAG, "dispatchKeyEvent--"+mNowState.getClass().getSimpleName()+"--"+"doOK()");
					mNowState.doOK();
					break;
				default:
					return false;
					
				}
				return true;
			}
			return false;
		}
		return true;
	}
	
	
	
	/**
	 * 設定左右箭頭顯示隱藏
	 * @param showleft true: 顯示,false:隱藏
	 * @param showright
	 */
	public void setLeftRightIcon(boolean showleft, boolean showright){
		if(null != mLeftIcon){ // 左icon
			if(showleft){
				mLeftIcon.setVisibility(View.VISIBLE);
			}
			else{
				mLeftIcon.setVisibility(View.INVISIBLE);
			}
		}
		if(null != mRightIcon){ // 右icon
			if(showright){
				mRightIcon.setVisibility(View.VISIBLE);
			}
			else{
				mRightIcon.setVisibility(View.INVISIBLE);
			}
		}
	}
	
	public void addPage(State state){
		if(null != mPageList && null != state ){   
			if(mPageList.size() > 0){
				mBeforeState = mPageList.get(mPageList.size() - 1);
			}
			mPageList.add(state);
			mNowState = state;
			if(null != mBeforeState){
				animationEnter(mBeforeState.getLayout(), mNowState.getLayout());
			}else{
				mLayout.addView(mNowState.getLayout());
			}
			setLeftRightIcon(mNowState.showLeftIcon(), mNowState.showRightIcon());
		}
	}
	
	public void finishPage(){
		if(null != mPageList && mPageList.size() > 0){
			if(mPageList.size() >1){
				mBeforeState = mPageList.remove(mPageList.size() - 1);
			}
			mNowState = mPageList.get(mPageList.size() - 1);
			if(null != mBeforeState){
				animationBack(mBeforeState.getLayout(), mNowState.getLayout());
			}
			setLeftRightIcon(mNowState.showLeftIcon(), mNowState.showRightIcon());
		}
	}
	
	private List<State> mPageList = new ArrayList<State>(); 
	/**
	 * 進入動畫,左移
	 * @param layoutNow 當前layout
	 * @param layoutRight 右方layout 
	 * 視窗不變,移動mLayout
	 */
	public void animationEnter(MAbsoluteLayout layoutNow,MAbsoluteLayout layoutRight){
		int transDistance = 0;
		if(null != mLayout){
			clearView(layoutNow, layoutRight);
			AbsoluteLayout.LayoutParams nowParams  = (LayoutParams) layoutNow.getMLayoutParams();
			nowParams.x += 1920;
			transDistance = - nowParams.x;
			if(null == layoutRight.getParent() || mLayout != layoutRight.getParent()){
				mLayout.addIView(layoutRight,nowParams);
			}
		}
		ViewPropertyAnimator.animate(mLayout).translationX(transDistance).setListener(new AnimatorListener() {
			
			@Override
			public void onAnimationStart(Animator arg0) {
				canTranslate = false;
			}
			
			@Override
			public void onAnimationRepeat(Animator arg0) {
				
			}
			
			@Override
			public void onAnimationEnd(Animator arg0) {
				canTranslate = true;
				if(null != mNowState){
					mNowState.animationEndCallBack();
				}
			}
			
			@Override
			public void onAnimationCancel(Animator arg0) {
				canTranslate = true;
				
			}
		}).setDuration(500).start();
		
	}
	/**
	 * 退出動畫,右移
	 * @param layoutNow 當前layout
	 * @param layoutRight 左方layout
	 * 視窗不變,移動mLayout
	 */
	public void animationBack(MAbsoluteLayout layoutNow, MAbsoluteLayout layoutLeft){
		int transDistance = 0;
		if(null != mLayout){
			clearView(layoutNow,layoutLeft);
			AbsoluteLayout.LayoutParams nowParams  = (LayoutParams) layoutNow.getMLayoutParams();
			nowParams.x -= 1920;
			transDistance = - nowParams.x;
			
			if(null == layoutLeft.getParent() || mLayout != layoutLeft.getParent()){
				mLayout.addIView(layoutLeft,nowParams);
			}
		}
		ViewPropertyAnimator.animate(mLayout).translationX(transDistance).setListener(new AnimatorListener() {
			
			@Override
			public void onAnimationStart(Animator arg0) {
				canTranslate = false;
			}
			
			@Override
			public void onAnimationRepeat(Animator arg0) {
				
			}
			
			@Override
			public void onAnimationEnd(Animator arg0) {
				canTranslate = true;
			}
			
			@Override
			public void onAnimationCancel(Animator arg0) {
				canTranslate = true;
			}
		}).setDuration(500).start();
	}
	

	/**
	 * 
	 * @param id 頁面標識 
	 * @param duration  0:left,1:right
	 */
	@Override
	public void changeState(StateID id, int direction, KEY_MODE mode) {
		State tmpState = null;
		switch (id) {
		case SELECT_PAGE_BLUETOOTH:
			tmpState = new LightView(this, getApplicationContext(), KEY_MODE.MODE_BLUETOOTH);
			this.addPage(tmpState);	
			break;
		case SELECT_PAGE_OPTICALFIBER:
			tmpState = new SocketView(this, getApplicationContext(), KEY_MODE.MODE_OPTICALFIBER);
			this.addPage(tmpState);	
			break;
		case SELECT_PAGE_HEADSET:
			tmpState = new SocketView(this, getApplicationContext(), KEY_MODE.MODE_HEADSET);
			this.addPage(tmpState);	
			break;
		case SOCKECT_PAGE:
			if(DIRECTION.DIRECTION_RIGHT == direction){
				tmpState = new LightView(this, getApplicationContext(), mode);
				this.addPage(tmpState);
			}
			else if(DIRECTION.DIRECTION_LEFT == direction){
				this.finishPage();
			}
			
			break;
		case LIGHT_PAGE:
			if(DIRECTION.DIRECTION_RIGHT == direction){
				tmpState = new SuccessView(this, getApplicationContext(), mode);
				this.addPage(tmpState);
			}
			else if(DIRECTION.DIRECTION_LEFT == direction){
				this.finishPage();
			}
			
			break;
		case SUCCESS_PAGE:
			this.finishPage();
			break;

		default:
			break;
		}
		
	}
}

從Activity程式碼中可以看到,引用了State全域性變數,執行動作時,直接呼叫State的相應方法,而State狀態值是在Activity中通過changeState方法變化的,通過回撥設定到不同的狀態View中,View是知道何時做出改變,但具體的改變要通過回撥在Activity中實現。關於回撥的理解,請參考我的另一篇文章:http://blog.csdn.net/adayabetter/article/details/44116993


 學習一種模式,一定要自己認真的想一遍,再根據自己的想法,通過程式碼驗證一下,當測試結果和自己想的一致時,再把那種感覺記錄下來,以便日後其他專案用作參考。 謝謝閱讀~