Android之 MVC到MVVM架构发展和封装

news2024/11/27 7:38:57

一  简介

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层的引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/573364.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机组成原理实验四 微程序控制器实验报告

我班算是几乎最后一个做实验的班级了&#xff0c;报告参考了一些朋友提供的数据加上一些自己的主观拙见&#xff0c;本人水平有限加之制作仓促难免有错误&#xff0c;望大家批评指正。 4.1 微程序控制器实验 一、实验目的 (1) 掌握微程序控制器的组成原理。 (2) 掌握微程序的…

【蓝桥杯计算思维题】少儿编程 蓝桥杯青少组计算思维真题及详细解析第5套

少儿编程 蓝桥杯青少组计算思维真题及详细解析第5套 1、北京冬奥会经历 17( ),中国体育代表团收获的金牌数和奖牌数均创历史新高 A、年 B、月 C、天 D、小时 答案:C 考点分析:主要考查小朋友们对时事的了解,北京冬奥会总共经历了17天,所以答案C 2、下面图形的周长是…

Python系列模块之标准库json详解

感谢点赞和关注 &#xff0c;每天进步一点点&#xff01;加油&#xff01; 目录 一、Json介绍 二、JSON 函数 2.1 json.dumps 2.2 json.loads 2.3 实战案例&#xff1a;钉钉消息发送 一、Json介绍 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它使得人们…

2023年21个最佳的Ruby测试框架

作者 | Veethee Dixit 测试人员总是在寻找最好的自动化测试框架&#xff0c;它能提供丰富的功能&#xff0c;并且语法简单、兼容性好、执行速度快。如果你选择将Ruby与Selenium结合起来进行web测试&#xff0c;那么可能需要搜索基于Ruby的测试框架进行web应用程序测试。 Ruby…

【Python】函数式编程第二弹

知识目录 一、写在前面✨二、最小公倍数三、移除数字四、总结撒花&#x1f60a; 一、写在前面✨ 大家好&#xff01;我是初心&#xff0c;希望我们一路走来能坚守初心&#xff01; 今天跟大家分享的文章是 Python函数式编程第二弹&#xff0c;再次以两个简单的例子带大家更好…

selenium UI自动化中文件上传的两种方式

前言 文件上传是自动化中很常见的一个功能&#xff0c;那么对于文件上传你又有多少了解呢&#xff1f;请往下看 1、被测产品中文件上传的功能非常普遍&#xff0c;一般情况下需要将准备好的文件放在预定的路径下&#xff0c;然后在自动化测试的脚本中&#xff0c;去预置的路径…

国内可以免费使用的GPT

一、wetab新标签页 教程&#xff1a;https://diwlwltzssn.feishu.cn/docx/MnHhdvxATomBnMxfas2cm8wWnVd 装GPT界面&#xff1a;https://microsoftedge.microsoft.com/addons/detail/wetab%E5%85%8D%E8%B4%B9chatgpt%E6%96%B0%E6%A0%87%E7%AD%BE%E9%A1%B5/bpelnogcookhocnaokfp…

TeX Live和TeX studio安装

最近想要研究一下Letex怎么写论文&#xff0c;然后就查阅资料了解了一下&#xff0c;先安装上两个软件&#xff0c;怎么用在研究研究&#xff0c;这里记录一下软件安装过程&#xff0c;方便以后查阅。 TeX Live和TeX studio安装 Latex介绍TexLive安装下载TexLive的安装包安装Te…

C++知识第三篇之继承

C继承 继承是面向对象编程的重要特征&#xff0c;是对类设计层次的复用 文章目录 C继承一.介绍1.继承定义2.继承方式3.class与struct 二.作用域1.成员变量2.成员函数 三.赋值转换1.给基类对象赋值2.给基类对象指针赋值 四.派生类的默认函数五. 其他1.友元2.静态 六.继承1.单继承…

Android车载学习笔记1——车载整体系统简介

一、汽车操作系统 汽车操作系统包括安全车载操作系统、智能驾驶操作系统和智能座舱操作系统。 1. 安全车载操作系统 安全车载操作系统主要面向经典车辆控制领域&#xff0c;如动力系统、底盘系统和车身系统等&#xff0c;该类操作系统对实时性和安全性要求极高&#xff0c;生态…

VCSA 和ESXi 6.7.0版本升级

1. VCSA升级步骤 1&#xff09;指定升级包的位置 software-packages stage --iso (如果是从vmware下载补丁&#xff0c;使用CD/DVD来映射ISO映像) 或 software-packages stage --url https://vapp-updates.vmware.com/vai-catalog/valm/vmw/8d167796-34d5-4899-be0a-6daade400…

Yolov8涨点神器:注意力机制---多头上下文集成(Context Aggregation)的广义构建模块,助力小目标检测,暴力涨点

🏆🏆🏆🏆🏆🏆Yolov8魔术师🏆🏆🏆🏆🏆🏆 ✨✨✨魔改网络、复现前沿论文,组合优化创新 🚀🚀🚀小目标、遮挡物、难样本性能提升 🍉🍉🍉定期更新不同数据集涨点情况 2.Context Aggregation介绍 论文:https://arxiv.org/abs/2106.01401 仅…

落地页设计的营销心理学(二)

作为一个营销落地页的设计者&#xff0c;最关注的问题&#xff1a;怎么样设计才能让我的落地页有转化&#xff1f; 当然这可能有很多影响因素。但是&#xff0c;你有没有想过在用户打开落地页时&#xff0c;运用心理学概念留住他们&#xff0c;促使他们完成留资、下载、购买等动…

听我一句劝,不要去外包,干了三年,废了....

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

Python实战基础12-闭包

1、函数的嵌套调用 def testB():print(-------testB start------)print(这里是testB函数执行的代码……)print(-------testB end ------)def testA():print(-----testA start-----)testB()print(-----testA end------) testA() 运行结果&#xff1a; 总结&#xff1a; 一个函…

PoseiSwap 参赛,参与斯坦福、Nautilus等联合主办的 Hackathon 活动

近日&#xff0c;由 Stanford Blockchain Accelerator、Zebec Protocol、 Nautilus Chain、Rootz Lab 共同主办的“ Boundless Hackathon Stanford ” 主题的黑客松活动&#xff0c;目前已接受报名。该活动旨在帮助更多的优质开发者参与到 Web3 世界的发展中&#xff0c;推动链…

顶满,阿里P8架构师独家分享的微服务实战笔记

微服务简介 简而言之&#xff0c;微服务架构风格是一种将单个应用程序开发为“一套小型服务”的方法&#xff0c;每个服务“运行在自己的进程中”&#xff0c;并通过轻量级机制(通常是HTTP资源API)进行通信。这些服务“围绕业务功能构建”&#xff0c;并通过全自动部署机制“独…

Cortex-M0的内核架构

目录 Cortex-M 系列产品线 Cortex-M0结构框图 Cortex-M0特性 Cortex-M0工作模式 Cortex-M0工作状态 Cortex-M0的寄存器 Cortex-M0的中断和异常 Cortex-M0的指令集 Cortex-M 系列产品线 Cortex-M系列是由ARM&#xff08;Advanced RISC Machines&#xff09;开发的一系列…

宁德时代,冷暖自知口难言

作者 | 魏启扬 来源 | 洞见新研社 发布可以“上天”的凝聚态电池、落地能量密度160Wh/kg的钠离子电池、量产系统集成度全球最高的麒麟电池…… 宁德时代在上海车展前后密集发声&#xff0c;坚决捍卫着“宁王”的冠冕。 如果再结合不久前的2022年年报&#xff0c;全年307亿的…

六级备考23天|CET-6|写作技巧1|开头段模版

目录 1 考前注意事项 2 真题参考 3 六级作文三段式 第一段 第二段 第三段 4 名言解释型作文 5 开头段的模版 why 型 6 选择型开头模版 7 国内学校/出国读书 8 团队精神和交流 9 作业 10 抽象意志品质类开头 ​ 11 信任的重要性​ 1 考前注意事项 定位错误 常识干扰 拼凑…