Android之 MVC到MVVM架构发展和封装
一 简介
1.1 软件架构发展趋势是解耦,即分离数据层和视图层,使得数据层专注于业务的数据和逻辑处理。从而提高代码的可读可编辑效率,提高团队协作能力,项目的生产能力,降低后期维护成本。
1.2 Android架构发展MVC -> MVP -> MVVM,目前最流程的MVVM,配合google的jetpack开发工具可以轻松实现MVVM架构
二 MVC
2.1 概念MVC (Model-View-Controller, 模型-视图-控制器)
- 模型层 (Model):业务逻辑对应的数据模型,无View无关,而与业务相关;
- 视图层 (View):一般使用XML或者Java对界面进行描述;
- 控制层 (Controllor):在Android中通常指Activity和Fragment,或者由其控制的业务类
2.2 基类封装BaseActivity
title.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_title_layout"
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@color/white">
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:src="@mipmap/back" />
<TextView
android:id="@+id/tv_head_title"
style="@style/Font_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:includeFontPadding="false"
android:singleLine="true" />
<TextView
android:id="@+id/tv_right_text"
style="@style/Font_title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:gravity="center"
android:includeFontPadding="false"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:singleLine="true"
android:textColor="@color/color_3e"
android:textSize="15sp" />
<ImageView
android:id="@+id/iv_right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp" />
</RelativeLayout>
activity_base.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/title"/>
<FrameLayout
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
BaseActivity.java
public abstract class BaseActivity extends AppCompatActivity {
public Activity mContext;
private RelativeLayout rlTitleLayout;
private ImageView ivBack;
private TextView tvHeadTitle;
private TextView tvRightText;
private ImageView ivRight;
private FrameLayout rootLayout;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(R.layout.activity_base);
initRootView();
}
private void initRootView() {
rlTitleLayout = (RelativeLayout) findViewById(R.id.rl_title_layout);
ivBack = (ImageView) findViewById(R.id.iv_back);
tvHeadTitle = (TextView) findViewById(R.id.tv_head_title);
tvRightText = (TextView) findViewById(R.id.tv_right_text);
ivRight = (ImageView) findViewById(R.id.iv_right);
rootLayout = (FrameLayout) findViewById(R.id.root_layout);
ivBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
rootLayout.addView(View.inflate(this, getLayoutResId(), null));
initView();
initData();
}
public void hideTitleLayout() {
rlTitleLayout.setVisibility(View.GONE);
}
public void setTitleText(String text) {
tvHeadTitle.setText(text);
}
public void setTitleText(int resId) {
tvHeadTitle.setText(getResources().getText(resId));
}
public void setRightText(int resId, View.OnClickListener onClickListener) {
tvRightText.setText(getResources().getText(resId));
tvRightText.setOnClickListener(onClickListener);
}
public void setRightImage(int resId, View.OnClickListener onClickListener) {
ivRight.setImageResource(resId);
ivRight.setOnClickListener(onClickListener);
}
protected abstract int getLayoutResId();
public abstract void initView();
/**
* 初始化数据
*/
public abstract void initData();
}
使用MainActivity.java
public class MainActivity extends BaseActivity implements View.OnClickListener {
private FrameLayout flMainFragment;
private LinearLayout llBottom;
private LinearLayout llFirst;
private ImageView ivFirst;
private TextView tvFirst;
private LinearLayout llTwo;
private ImageView ivTwo;
private TextView tvTwo;
private LinearLayout llThree;
private ImageView ivThree;
private TextView tvThree;
private FragmentTransaction transaction;
private HomeFragment homeFragment;
private CommunityFragment communityFragment;
private MeFragment meFragment;
@Override
protected int getLayoutResId() {
return R.layout.activity_main;
}
@Override
public void initView() {
//UltimateBarXUtils.setNoAppBar(this, true);
hideTitleLayout();
flMainFragment = (FrameLayout) findViewById(R.id.fl_main_fragment);
llBottom = (LinearLayout) findViewById(R.id.ll_bottom);
llFirst = (LinearLayout) findViewById(R.id.ll_first);
ivFirst = (ImageView) findViewById(R.id.iv_first);
tvFirst = (TextView) findViewById(R.id.tv_first);
llTwo = (LinearLayout) findViewById(R.id.ll_two);
ivTwo = (ImageView) findViewById(R.id.iv_two);
tvTwo = (TextView) findViewById(R.id.tv_two);
llThree = (LinearLayout) findViewById(R.id.ll_three);
ivThree = (ImageView) findViewById(R.id.iv_three);
tvThree = (TextView) findViewById(R.id.tv_three);
}
@Override
public void initData() {
llFirst.setOnClickListener(this);
llTwo.setOnClickListener(this);
llThree.setOnClickListener(this);
setTabSelection(1);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.ll_first:
setTabSelection(1);
break;
case R.id.ll_two:
setTabSelection(2);
break;
case R.id.ll_three:
setTabSelection(3);
break;
}
}
public void setTabSelection(int index) {
transaction = getSupportFragmentManager().beginTransaction();
hideFragments(transaction);
resetBtn();
switch (index) {
case 1:
if (homeFragment == null) {
homeFragment = new HomeFragment();
transaction.add(R.id.fl_main_fragment, homeFragment);
}
transaction.show(homeFragment);
ivFirst.setImageResource(R.mipmap.tab1_se);
tvFirst.setTextColor(getResources().getColor(R.color.color_blue_tab));
break;
case 2:
if (communityFragment == null) {
communityFragment = new CommunityFragment();
transaction.add(R.id.fl_main_fragment, communityFragment);
}
transaction.show(communityFragment);
ivTwo.setImageResource(R.mipmap.tab2_se);
tvTwo.setTextColor(getResources().getColor(R.color.color_blue_tab));
break;
case 3:
if (meFragment == null) {
meFragment = new MeFragment();
transaction.add(R.id.fl_main_fragment, meFragment);
}
transaction.show(meFragment);
ivThree.setImageResource(R.mipmap.tab3_se);
tvThree.setTextColor(getResources().getColor(R.color.color_blue_tab));
break;
}
transaction.commitAllowingStateLoss();
}
private void resetBtn() {
ivFirst.setImageResource(R.mipmap.tab1);
ivTwo.setImageResource(R.mipmap.tab2);
ivThree.setImageResource(R.mipmap.tab3);
tvFirst.setTextColor(getResources().getColor(R.color.color_d8d8d8));
tvTwo.setTextColor(getResources().getColor(R.color.color_d8d8d8));
tvThree.setTextColor(getResources().getColor(R.color.color_d8d8d8));
}
private void hideFragments(FragmentTransaction transaction) {
if (homeFragment != null) {
transaction.hide(homeFragment);
}
if (communityFragment != null) {
transaction.hide(communityFragment);
}
if (meFragment != null) {
transaction.hide(meFragment);
}
}
}
2.3 基类封装BaseFragment
BaseFragment.java
public abstract class BaseFragment extends Fragment {
public Activity mContext;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(getLayoutId(), container, false);
initView(view, savedInstanceState);
initData();
return view;
}
/**
* Fragment数据的懒加载.
*/
protected boolean isVisible;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
lazyLoad();
} else {
isVisible = false;
}
}
protected void lazyLoad() {
}
public abstract int getLayoutId();
public abstract void initView(View view, @Nullable Bundle savedInstanceState);
/**
* 初始化数据
*/
public abstract void initData();
}
fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/view_statues"
android:layout_width="match_parent"
android:layout_height="0dp"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp">
<TextView
style="@style/Font_black"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:background="@drawable/shape_bg_color_f0eff5_30"
android:gravity="center"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:text="@string/home_property"
android:textColor="@color/color_3e"
android:textSize="15sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/iv_search"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:src="@mipmap/home_search" />
</RelativeLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
FragmentHome.java
public class HomeFragment extends BaseFragment{
private View viewStatues;
@Override
public int getLayoutId() {
return R.layout.fragment_home;
}
@Override
public void initView(View view, @Nullable Bundle savedInstanceState) {
viewStatues = (View) view.findViewById(R.id.view_statues);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) viewStatues.getLayoutParams();
layoutParams.height = ScreenUtils.getStatusHeight(mContext);
viewStatues.setLayoutParams(layoutParams);
}
@Override
public void initData() {
}
}
三 MVP
3.1 概念MVP (Model-View-Presenter)
- 模型层 (Model):主要提供数据存取功能。
- 视图层 (View):处理用户事件和视图。在Android中,可能是指Activity、Fragment或者View。
- 展示层 (Presenter):负责通过Model存取书数据,连接View和Model,从Model中取出数据交给View。
3.2 封装基类
新建Presenter接口,Presenter.java
public interface Presenter<V extends BaseView> {
void attachView(V view);
void detachView();
}
新建Presenter基类BasePresenter.java
public class BasePresenter<V> implements IPresenter<V> {
protected WeakReference<V> mViewRef; //View接口类型的弱引用
/**
* 建立关联
* @param view
*/
public void attachView(V view){
mViewRef=new WeakReference<V>(view);
}
/**
* 解除关联
*/
public void detachView(){
if (mViewRef!=null){
mViewRef.clear();
mViewRef=null;
}
}
/**
* 判断是否与View建立关联
* @return
*/
public boolean isViewAttach(){
return mViewRef != null && mViewRef.get() != null;
}
/**
* 获取View
* @return
*/
protected V getView(){
return mViewRef.get();
}
}
新建Activity基类BaseActivity.java
public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity {
protected T mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
mPresenter=createPresneter();
//关联view
if (mPresenter != null) {
mPresenter.attachView(this);
}
init();
}
protected abstract T createPresneter();
protected abstract void init();
protected abstract int getLayoutId();
@Override
protected void onDestroy() {
super.onDestroy();
//解除
if (mPresenter != null) {
mPresenter.detachView();
}
}
}
新建Fragment基类BaseFragment.java
public abstract class BaseFragment<T extends BasePresenter> extends Fragment {
protected T mPresenter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View mView = inflater.inflate(getLayoutId(), null);
mPresenter = createPresneter();
//关联view
if (mPresenter != null) {
mPresenter.attachView(this);
}
init(mView);
return mView;
}
protected abstract T createPresneter();
/**
* 初始化
*/
protected abstract void init(View view);
/**
* 布局ID
*
* @return
*/
protected abstract int getLayoutId();
@Override
public void onDestroy() {
super.onDestroy();
//解除
if (mPresenter != null) {
mPresenter.detachView();
}
}
}
3.3 实战,登录功能
新建登录视图操作接口,LoginView.java
public interface LoginView {
String getUserName();
String getPassWord();
void toMainActivity();
void showToast(String message);
void finish();
void showWaitDialog(String message);
void hideWaitDialog();
}
新建数据操作接口,LoginModel.java
public interface LoginModel {
void login();
}
新建桥梁连接Model合View, LoginPresenter.java
public class LoginPresenter extends BasePresenter<LoginView> implements LoginModel {
@Override
public void login() {
getView().showWaitDialog("正在登录...");
String userName=getView().getUserName();
String password=getView().getPassWord();
String result="";
if(StringUtils.isEmpty(userName)){
result="用户名不能为空";
getView().hideWaitDialog();
getView().showToast(result);
return;
}
if(StringUtils.isEmpty(password)){
result="密码不能为空";
getView().hideWaitDialog();
getView().showToast(result);
return;
}
RetrofitClient.getmInstance().postMapLogin(userName, password, new BaseSubscriber<HttpResponse<UserInfo>>() {
@Override
public void onError(ExceptionHandle.ResponeThrowable e) {
getView().hideWaitDialog();
getView().showToast(e.message);
}
@Override
public void onNext(HttpResponse<UserInfo> userInfoHttpResponse) {
if (userInfoHttpResponse.getStatus() == 1) {
UserInfo userInfo = userInfoHttpResponse.getReturnX();
getView().hideWaitDialog();
getView().showToast("登录成功");
getView().toMainActivity();
getView().finish();
} else {
getView().hideWaitDialog();
getView().showToast(userInfoHttpResponse.getInfo());
}
}
});
}
}
新建登录页面LoginActivity.java
public class LoginActivity extends BaseActivity implements LoginView {
private MaterialEditText metName;
private MaterialEditText metPassword;
private Button btnLogin;
LoginPresenter loginPresenter;
@Override
protected int getLayoutId() {
return R.layout.activity_login;
}
@Override
protected BasePresenter createPresneter() {
loginPresenter = new LoginPresenter();
return loginPresenter;
}
@Override
protected void init() {
metName = (MaterialEditText) findViewById(R.id.met_name);
metPassword = (MaterialEditText) findViewById(R.id.met_password);
btnLogin = (Button) findViewById(R.id.btn_login);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
loginPresenter.login();
}
});
}
@Override
public String getUserName() {
return metName.getText().toString();
}
@Override
public String getPassWord() {
return metPassword.getText().toString();
}
@Override
public void toMainActivity() {
startActivity(new Intent(this,MainActivity.class));
}
@Override
public void showToast(String message) {
ToastUtils.show(this,message);
}
@Override
public void finish() {
}
@Override
public void showWaitDialog(String message) {
}
@Override
public void hideWaitDialog() {
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
四 MVVM
4.1 概念MVVM(Model-View-ViewModel)
- 模型层 (Model):负责从各种数据源中获取数据;
- 视图层 (View):在 Android 中对应于 Activity 和 Fragment,用于展示给用户和处理用户交互,会驱动 ViewModel 从 Model 中获取数据;
- ViewModel 层:用于将 Model 和 View 进行关联,我们可以在 View 中通过 ViewModel 从 Model 中获取数据;当获取到了数据之后,会通过自动绑定,比如 DataBinding,来将结果自动刷新到界面上。
4.2 Android中使用ViewModel + LiveData + DataBinding来快速实现MVVM架构的搭建,
ViewModel:① 绑定Activity ,② 页面布局绘制,③ 实现业务逻辑如登录功能
LiveData:① 可修改数据 ,② 数据观察
DataBinding:① 单向绑定 ,② 双向绑定
4.2 封装MVVM基类
在Model级build.gradle文件里开启dataBinding支持
android {
compileSdk 32
defaultConfig {
applicationId "com.bob.diary"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
dataBinding {
enabled = true
}
}
创建ViewModel基类,绑定Activity,BaseViewModel.java,BaseActivity.java
public abstract class BaseViewModel extends ViewModel implements DefaultLifecycleObserver {
public Activity activity;
public void setActivity(Activity activity) {
this.activity = activity;
}
}
创建Activity基类,activity_base.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/layout_title_bar"
android:id="@+id/layout_title_root"/>
<FrameLayout
android:id="@+id/fl_content_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_title_root"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
public abstract class BaseActivity<VM extends BaseViewModel, DB extends ViewDataBinding>
extends AppCompatActivity {
public DB mDataBinding;
protected VM mViewModel;
protected ActivityBaseBinding activityBaseBinding;
protected Activity mContext;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mContext=this;
super.onCreate(savedInstanceState);
activityBaseBinding = DataBindingUtil.setContentView(this, R.layout.activity_base);
mDataBinding = DataBindingUtil.inflate(getLayoutInflater(), getLayoutResId(),
activityBaseBinding.flContentContainer, true);
initViewModel();
bindViewModel();
if (mDataBinding != null) {
mDataBinding.setLifecycleOwner(this);
}
//ViewModel订阅生命周期事件
if (mViewModel != null) {
getLifecycle().addObserver(mViewModel);
mViewModel.setActivity(this);
}
init();
}
/**
* 获取当前页面的布局资源ID
*
* @return 布局资源ID
*/
protected abstract int getLayoutResId();
/**
* 初始化ViewModel
*/
protected abstract void initViewModel();
/**
* 绑定ViewModel
*/
protected abstract void bindViewModel();
/**
* 初始化
*/
protected abstract void init();
/**
* 设置标题
*/
public void setTitle(String title) {
UltimateBarXUtils.setAppBar(this, true, R.color.white);
ConstraintLayout layoutTitleRoot = findViewById(R.id.layout_title_root);
layoutTitleRoot.setVisibility(View.VISIBLE);
ImageView ivBack = findViewById(R.id.iv_back);
TextView tvTitleMiddle = findViewById(R.id.tv_title_middle);
ivBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
tvTitleMiddle.setText(title);
}
}
使用,以登录为例,新建登录model,LoginModel.java
public class LoginModel extends BaseViewModel {
//LiveData监听器
public MutableLiveData<List<User>> userLiveData;
public LoginModel() {
userLiveData = new MutableLiveData<>();
}
//查询用户
public void daoQueryAllUser() {
//通知数据变化
List<User> userList = DaoUserUtils.getInstance().daoQueryAllUser();
userLiveData.postValue(userList);
}
}
新建登录页面,activity_login.xml和LoginActivity.java
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_faf1ed"
android:gravity="center_vertical"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_login_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center_vertical"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/ll_policy_layout"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-42dp"
android:orientation="vertical"
android:paddingLeft="@dimen/dp_32"
android:paddingRight="@dimen/dp_32">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login_welcome_login"
android:textColor="#E9756E"
android:textSize="@dimen/sp_24"
android:textStyle="bold" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_marginTop="@dimen/dp_30">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="@mipmap/login_account"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_account"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/dp_30"
android:background="@drawable/bg_orange_border_bottom"
android:hint="@string/register_enter_account"
android:maxLength="15"
android:textColor="@color/color_e9756e"
android:textColorHint="@color/color_e9756e"
android:textSize="@dimen/sp_16" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_marginTop="@dimen/dp_30">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="@mipmap/login_password"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/dp_30"
android:background="@drawable/bg_orange_border_bottom"
android:hint="@string/register_enter_password"
android:inputType="textPassword"
android:maxLength="15"
android:textColor="@color/color_e9756e"
android:textColorHint="@color/color_e9756e"
android:textSize="@dimen/sp_16" />
<ImageView
android:id="@+id/iv_see"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:paddingLeft="@dimen/dp_10"
android:paddingRight="@dimen/dp_10"
android:src="@mipmap/login_look_off" />
</RelativeLayout>
<TextView
android:id="@+id/tv_register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:padding="@dimen/dp_10"
android:text="@string/login_register_account"
android:textColor="#E9756E"
android:textSize="@dimen/sp_12" />
</LinearLayout>
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
android:layout_marginLeft="@dimen/dp_15"
android:layout_marginTop="@dimen/dp_70"
android:layout_marginRight="@dimen/dp_15"
android:background="@drawable/bg_orange_10"
android:gravity="center"
android:text="@string/login_to"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/ll_login_layout" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_policy_layout"
android:layout_width="0dp"
android:layout_height="@dimen/dp_60"
android:layout_marginLeft="@dimen/dp_15"
android:layout_marginRight="@dimen/dp_15"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/ll_login_layout">
<CheckBox
android:id="@+id/cb_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:button="@drawable/selector_checked"
android:paddingTop="@dimen/dp_5"
android:paddingBottom="@dimen/dp_5"
android:paddingLeft="@dimen/dp_5"
android:paddingRight="@dimen/dp_3"
android:textColor="@color/color_3d3d3d"
android:textSize="@dimen/sp_12" />
<TextView
android:id="@+id/tv_policy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left|top"
android:orientation="horizontal"
android:text="@string/login_agreement_agree"
android:textColor="@color/color_3d3d3d"
android:textSize="@dimen/sp_12" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
public class LoginActivity extends BaseActivity<LoginModel, ActivityLoginBinding> {
private boolean canSee;
@Override
protected int getLayoutResId() {
return R.layout.activity_login;
}
@Override
protected void initViewModel() {
mViewModel = ViewModelProviders.of(this).get(LoginModel.class);
}
@Override
protected void bindViewModel() {
//监听数据变化
mViewModel.userLiveData.observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
User user = users.get(0);
if (user == null) {
ToastHelp.showToast("用户不存在");
return;
}
startActivity(MainActivity.class);
finish();
}
});
}
@Override
protected void init() {
//登录点击事件
mDataBinding.tvConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String account = mDataBinding.etAccount.getText().toString();
String password = mDataBinding.etPassword.getText().toString();
if (TextUtils.isEmpty(account)) {
ToastHelp.showToast(getResources().getString(R.string.register_enter_account));
return;
}
if (TextUtils.isEmpty(password)) {
ToastHelp.showToast(getResources().getString(R.string.register_enter_password));
return;
}
mViewModel.daoQueryAllUser();
}
});
}
}
4.3 BaseFragment封装
public abstract class BaseFragment<VM extends BaseViewModel, DB extends ViewDataBinding>
extends Fragment {
protected DB mDataBinding;
protected VM mViewModel;
private FragmentBaseBinding fragmentBaseBinding;
protected Activity mContext;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext=getActivity();
initViewModel();
// ViewModel订阅生命周期事件
if (mViewModel != null) {
getLifecycle().addObserver(mViewModel);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
fragmentBaseBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_base, container, false);
mDataBinding = DataBindingUtil.inflate(inflater, getLayoutResId(),
fragmentBaseBinding.flContentContainer, true);
bindViewModel();
mDataBinding.setLifecycleOwner(this);
init();
return fragmentBaseBinding.getRoot();
}
/**
* 获取当前页面的布局资源ID
*
* @return 布局资源ID
*/
protected abstract int getLayoutResId();
/**
* 初始化ViewModel
*/
protected abstract void initViewModel();
/**
* 绑定ViewModel
*/
protected abstract void bindViewModel();
/**
* 初始化
*/
protected abstract void init();
protected boolean isVisible;
/**
* 在这里实现Fragment数据的缓加载.
*
* @param isVisibleToUser
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
protected void onVisible() {
lazyLoad();
}
protected void lazyLoad() {
}
protected void onInvisible() {
}
}
BaseFragment的使用,以首页为例,新建HomeModel.java
public class HomeModel extends BaseViewModel {
//LiveData监听器
public MutableLiveData<List<User>> userLiveData;
public LoginModel() {
userLiveData = new MutableLiveData<>();
}
//查询用户
public void daoQueryAllUser() {
//通知数据变化
List<User> userList = DaoUserUtils.getInstance().daoQueryAllUser();
userLiveData.postValue(userList);
}
}
新建HomeFragment.java
public class HomeFragment extends BaseFragment<HomeModel, FragmentHomeBinding> implements View.OnClickListener {
@Override
protected boolean isEventBus() {
return true;
}
@Override
protected int getLayoutResId() {
return R.layout.fragment_home;
}
@Override
protected void initViewModel() {
mViewModel = ViewModelProviders.of(getActivity()).get(HomeModel.class);
}
@Override
protected void bindViewModel() {
mDataBinding.setModel(mViewModel);
}
@Override
protected void init() {
mViewModel.daoQueryAllUser();
//监听数据变化
mViewModel.userLiveData.observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
//加载用户信息
if(users.size()>0){
User user=users.get(0);
GlideUtil.loadImageView(mContext,user.getAvatar(),mDataBinding.ivUser,R.mipmap.default_user);
mDataBinding.tvNickName.setText(user.getName());
}
}
});
}
}
五 总结,三种架构各自的特点
5.1 MVC特点
- 优势:简单易用,View接收用户操作,通过Controller去处理业务逻辑,并通过Model去获取/更新数据,然后Model层又将最新的数据传回View层进行页面展示。
- 劣势:由于XML布局能力弱,我们的View层的很多操作都是写在Activity/Fragment中,同时,Controller、Model层的代码也大都写在Activity/Fragment中,这就会导致一个问题,当业务逻辑比较复杂时,Activity/Fragment中的代码量会很大,其违背了类单一职责,不利于后续扩展及维护
5.2 MVP特点
- View层接收用户操作,并通过持有的Presenter去处理业务逻辑,请求数据;接着Presenter层通过Model去获取数据,然后Model又将最新的数据传回Presenter层,Presenter层又持有View层的引用,进而将数据传给View层进行展示
- 与MCVC相比,View层与Model层不再交互,而是通过Presenter去进行联系
- MVP是面向接口编程,Model/View/Presenter每层的职责分工明确,当业务复杂时,整个流程逻辑也是很清晰的
- Presenter会被抽象成IPresenter接口及其一些列方法,每当实现一个功能时,都需要编写多个接口及其对应的方法,实现起来相对比较繁琐,而且每次有改动时,对应的接口方法也基本都会再去改动
- View层与Presenter层相互持有,当View层关闭时,由于Presenter层不是生命周期感知的,可能会导致内存泄漏甚至是崩溃。如果你的项目中使用了RxJava,可以使用 配合Rxjava自动解绑
5.3 MVVM特点
- View层接收用户操作,并通过持有的ViewModel去处理业务逻辑,请求数据
- ViewModel层通过Model去获取数据,然后Model又将最新的数据传回ViewModel层,这里ViewModel与Presenter所做的事基本是一样的
- View层会通过观察者模式监听ViewModel层的数据变化,当有新数据时,View层能自动收到新数据并刷新界面,所以ViewModel不会也不能持有View层的引用。