简介
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等。
MVC的优点:
(1)耦合性低。所谓耦合性就是模块代码之间的关联程度。利用MVC框架使得View(视图)层和Model(模型)层可以很好的分离,这样就达到了解耦的目的,所以耦合性低,减少模块代码之间的相互影响。
(2)可扩展性好。由于耦合性低,添加需求,扩展代码就可以减少修改之前的代码,降低bug的出现率。
(3)模块职责划分明确。主要划分层M,V,C三个模块,利于代码的维护。
代码Demo
以登录界面为例
编写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">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_centerInParent="true"
android:layout_height="wrap_content">
<EditText
android:layout_width="match_parent"
android:maxLines="1"
android:hint="输入账号"
android:id="@+id/account_et"
android:layout_height="wrap_content" />
<EditText
android:layout_width="match_parent"
android:maxLines="1"
android:hint="输入密码"
android:id="@+id/pwd_et"
android:layout_height="wrap_content" />
<Button
android:layout_width="match_parent"
android:onClick="login"
android:text="登录"
android:layout_height="wrap_content" />
<ProgressBar
android: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;
@Override
protected 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() {
@Override
public 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();
}
}
}
代码Demo2
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);
}
}