MVC是一種設計模式,也就是一種解決問題的方法和思路, 是上世紀80年代提出的,到現在已經頗有歷史了。 MVC的意義在于指導開發者將數據與表現解耦,提高代碼,特別是模型部分代碼的復用性。
MVC模式的意思是,軟件可以分成三個部分。 視圖(View):用戶界面。 控制器(Controller):業務邏輯 模型(Model):數據保存
View 傳送指令到 Controller Controller 完成業務邏輯后,要求 Model 改變狀態 Model 將新的數據發送到 View,用戶得到反饋
所有通信都是單向的。 最上面的一層,是直接面向最終用戶的”視圖層”(View)。它是提供給用戶的操作界面,是程序的外殼。 最底下的一層,是核心的”數據層”(Model),也就是程序需要操作的數據或信息。 中間的一層,就是”控制層”(Controller),它負責根據用戶從”視圖層”輸入的指令,選取”數據層”中的數據,然后對其進行相應的操作,產生最終結果。
這三層是緊密聯系在一起的,但又是互相獨立的,每一層內部的變化不影響其他層。每一層都對外提供接口(Interface),供上面一層調用。這樣一來,軟件就可以實現模塊化,修改外觀或者變更數據都不用修改其他層,大大方便了維護和升級。
舉例
用Windows的計算器小程序為例,解釋一下MVC模式,雖然它不一定使用這個模式編寫。 在這個計算器程序中,外部的那些按鈕和最上面的顯示條,就是”視圖層”,那些需要運算的數字就是”數據層”,執行加減乘除的那些內部運算步驟就是”控制層”。每一層執行不同的功能,整個程序的結構非常清楚。 如果我們擴大一點想象,就會發現,很多程序本質上都是這種模式:對外提供一組觸發器(本例中是按鈕),然后執行一些內部操作,最后返回結果。因此,MVC模式的應用是非常廣泛的。
以家用微波爐為例,可以將它也理解成三層結構。最簡單的情況下,微波爐的操作用兩個轉盤實現,一個控制溫度,另一個控制時間。這兩個轉盤就是”視圖層”(view),而其內部的微波產生裝置則是”數據層”(Model),這里的”數據”需要理解成”核心功能”。至于將用戶通過轉盤輸入的信息,轉換成對微波產生器的操作,則用”控制層”來實現。 如果每一層都是獨立的,那么微波爐外部更換一個新潮的外殼,或者內部更換更大功率的微波產生器,完全可以在不更改其他層的情況下實現。這就是MVC模式的優勢。
View:自定義View或ViewGroup,負責將用戶的請求通知Controller,并根據model更新界面;
Controller:Activity或者Fragment,接收用戶請求并更新model;
Model:數據模型,負責數據處理相關的邏輯,封裝應用程序狀態,響應狀態查詢,通知View改變,對應Android中的datebase、SharePReference等。
(1)耦合性低。所謂耦合性就是模塊代碼之間的關聯程度。利用MVC框架使得View(視圖)層和Model(模型)層可以很好的分離,這樣就達到了解耦的目的,所以耦合性低,減少模塊代碼之間的相互影響。
(2)可擴展性好。由于耦合性低,添加需求,擴展代碼就可以減少修改之前的代碼,降低bug的出現率。
(3)模塊職責劃分明確。主要劃分層M,V,C三個模塊,利于代碼的維護。
以登錄界面為例
編寫V
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main"android:layout_width="match_parent" android:layout_height="match_parent"tools:context="com.itheima.a001mvclogin.MainActivity"><LinearLayoutandroid:layout_width="match_parent"android:orientation="vertical"android:layout_centerInParent="true"android:layout_height="wrap_content"><EditTextandroid:layout_width="match_parent"android:maxLines="1"android:hint="輸入賬號"android:id="@+id/account_et"android:layout_height="wrap_content" /><EditTextandroid:layout_width="match_parent"android:maxLines="1"android:hint="輸入密碼"android:id="@+id/pwd_et"android:layout_height="wrap_content" /><Buttonandroid:layout_width="match_parent"android:onClick="login"android:text="登錄"android:layout_height="wrap_content" /><ProgressBarandroid:layout_width="wrap_content"android:id="@+id/pbar"android:visibility="invisible"android:layout_gravity="center_horizontal"android:layout_height="wrap_content" /></LinearLayout></RelativeLayout>編寫M http或者database
public class User {public String pwd;public String account;public User(String account, String pwd) {this.account = account;this.pwd = pwd;}}編寫C
public class MainActivity extends AppCompatActivity {private EditText accountEt;private EditText pwdEt;private ProgressBar pBar;@Overrideprotected void onCreate(Bundle savedInstanceState{ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);//編寫登錄//1.編寫V 在android中就是在layout里面布局 accountEt = (EditText) findViewById(R.id.account_et); pwdEt = (EditText) findViewById(R.id.pwd_et); pBar = (ProgressBar) findViewById(R.id.pbar);//2.編寫M 將頁面上獲取的賬號密碼提交給服務器, 返回校驗結果 //3.編寫C 事件 線程 判斷 } public void login(View view) {//3.1.獲取頁面值 String inputAccount = accountEt.getText().toString(); String inputPwd = pwdEt.getText().toString();//3.2.進行邏輯判斷 if (!TextUtils.isEmpty(inputAccount) && !TextUtils.isEmpty(inputPwd)) { //3.3.提示等待 pBar.setVisibility(View.VISIBLE);//3.4.數據提交 final User user = new User(inputAccount, inputPwd);//發送 new Handler().postDelayed(new Runnable() {@Overridepublic void run() {//3.5.獲取請求結果 int code = new Random().nextInt(2);//3.6.關閉等待 pBar.setVisibility(View.INVISIBLE);if (code == 0)//賬號密碼正確 { Toast.makeText(MainActivity.this, "登錄成功,歡迎" + user.account, Toast.LENGTH_SHORT).show(); } else {Toast.makeText(MainActivity.this, "賬號密碼錯誤", Toast.LENGTH_SHORT).show(); } } }, 3000); } else { Toast.makeText(this, "賬號密碼不能為空", Toast.LENGTH_SHORT).show(); } }}參考別人寫的代碼 點擊查看
View實現:
public class TrackCtrlView implements View.OnClickListener{ private ImageView btnStartTrack, btnStopTrack, btnPauseTrack; private TrackCtrlViewListener listener; private TrackRecordInfo trackRecordInfo; public TrackCtrlView(Activity activity, TrackCtrlViewListener listener){ this.listener = listener; btnStartTrack = (ImageView) activity.findViewById(R.id.btnStartTrack); btnStopTrack = (ImageView) activity.findViewById(R.id.btnStopTrack); btnPauseTrack = (ImageView) activity.findViewById(R.id.btnPauseTrack); btnStartTrack.setOnClickListener(this); btnStopTrack.setOnClickListener(this); btnPauseTrack.setOnClickListener(this); btnPauseTrack.setOnClickListener(this); } /** * 將用戶請求通知Controller */ @Override public void onClick(View v) { switch(v.getId()){ case R.id.btnStartTrack: if(listener != null){ listener.trackStatusRequest(TrackRecordStatus.Recording); } break; case R.id.btnStopTrack: if(listener != null){ listener.trackStatusRequest(TrackRecordStatus.Stoped); } break; case R.id.btnPauseTrack: if(listener != null){ if(trackRecordInfo.status == TrackRecordStatus.Paused){ listener.trackStatusRequest(TrackRecordStatus.Recording); }else{ listener.trackStatusRequest(TrackRecordStatus.Paused); } } break; default: break; } } private void refreshView(){ TrackRecordStatus trackStatus = trackRecordInfo == null ? TrackRecordStatus.Stoped : trackRecordInfo.status; if (trackStatus == TrackRecordStatus.Recording) { btnStartTrack.setVisibility(View.GONE); btnPauseTrack.setVisibility(View.VISIBLE); btnStopTrack.setVisibility(View.VISIBLE); btnPauseTrack.setImageResource(R.drawable.btn_track_ctrl_pause); } else if (trackStatus == TrackRecordStatus.Paused) { btnStartTrack.setVisibility(View.GONE); btnPauseTrack.setVisibility(View.VISIBLE); btnStopTrack.setVisibility(View.VISIBLE); btnPauseTrack.setImageResource(R.drawable.btn_track_ctrl_resume); } else { // TrackRecordStatus.Stoped btnStartTrack.setVisibility(View.VISIBLE); btnPauseTrack.setVisibility(View.GONE); btnStopTrack.setVisibility(View.GONE); } } public void setTrackRecordInfo(@Nullable TrackRecordInfo trackRecordInfo) { this.trackRecordInfo = trackRecordInfo; refreshView(); } public interface TrackCtrlViewListener{ /** * 用戶點擊按鈕 */ public void trackStatusRequest(@Nullable TrackRecordStatus newStatus); }}Model實現:
public class TrackRecordInfo { private static final Gson gson = new Gson(); /** * 應該是保存軌跡數據庫id,此demo中數據庫操作不實現,暫時trackId一直為0 */ public int trackId; public TrackRecordStatus status; public TrackRecordInfo(int trackId, TrackRecordStatus status) { this.trackId = trackId; this.status = status; } @NonNull public static TrackRecordInfo loadTrackRecordInfo(@NonNull Context context){ String pref = SpUtil.getString(context, SpUtil.KEY_TRACK_RECORD_INFO, ""); if(!TextUtils.isEmpty(pref)){ return gson.fromJson(pref, TrackRecordInfo.class); } return null; } public static void changeTrackRecordInfo(@NonNull Context context, @Nullable TrackRecordInfo info){ SpUtil.saveString(context, SpUtil.KEY_TRACK_RECORD_INFO, info == null ? "" : gson.toJson(info)); //model通過消息總線,通知View刷新 EventBus.getDefault().post(new EventTrackRecordInfoChanged(info)); }}Controller實現:
public class MainActivity extends ActionBarActivity implements TrackCtrlView.TrackCtrlViewListener{ private TrackCtrlView trackCtrlView; private TrackRecordInfo trackRecordInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); trackCtrlView = new TrackCtrlView(this, this); EventBus.getDefault().register(this); trackRecordInfo = TrackRecordInfo.loadTrackRecordInfo(this); trackCtrlView.setTrackRecordInfo(trackRecordInfo); } @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); } @Override public void trackStatusRequest(@Nullable TrackRecordStatus newStatus) { if(newStatus == TrackRecordStatus.Recording){ int trackId = 0; //在數據庫創建一條軌跡,并獲取到數據庫id trackRecordInfo = new TrackRecordInfo(trackId, TrackRecordStatus.Recording); }else if (newStatus == TrackRecordStatus.Paused) { if(trackRecordInfo != null){ trackRecordInfo.status = newStatus; } } else { trackRecordInfo = null; } TrackRecordInfo.changeTrackRecordInfo(this, trackRecordInfo); } public void onEventMainThread(EventTrackRecordInfoChanged event){ trackRecordInfo = event.info; trackCtrlView.setTrackRecordInfo(trackRecordInfo); }}
參考博客John-Chen
新聞熱點
疑難解答