Android MVC,MVP,MVVM模式入門——重構登錄註冊功能

一  MVC模式:android

M:model,業務邏輯git

V:view,對應佈局文件github

C:Controllor,對應Activity微信

項目框架:網絡

 

代碼部分:mvc

  layout文件(適用於MVC和MVP兩個Demo):app

<?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">

    <EditText
        android:id="@+id/edit_username"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:hint="username"/>

    <EditText
        android:id="@+id/edit_password"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:inputType="textPassword"
        android:hint="password"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/login"
            android:layout_marginLeft="10dp"
            android:layout_width="120dp"
            android:layout_height="50dp"
            android:text="Login"/>

        <Button
            android:id="@+id/clear"
            android:layout_width="120dp"
            android:layout_height="50dp"
            android:layout_marginLeft="100dp"
            android:text="Clear"/>

    </LinearLayout>

    <ProgressBar
        android:id="@+id/login_progressbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="invisible"
        />
</LinearLayout>

 

  User類,定義成員對象:框架

package com.example.liang.userloginformvc.modle;

/**
 * Created by liang on 2016/8/22.
 */
public class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

  OnLoginListener,j監聽登錄狀態:mvvm

package com.example.liang.userloginformvc.modle;

/**
 * Created by liang on 2016/8/22.
 */
public interface OnLoginListener {
    void loginSuccess(User user);
    void loginFailed();
}

  UserBiz,登錄邏輯:ide

package com.example.liang.userloginformvc.modle;

/**
 * Created by liang on 2016/8/22.
 */
public class UserBiz {

    public void login(final String username,final String password,final OnLoginListener loginListener){
        new Thread(){
            @Override
            public void run() {
                try{
                    Thread.sleep(2000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                //模擬登錄
                if("lqy".equals(username)&&"123".equals(password)){
                    User user=new User();
                    user.setUsername(username);
                    user.setPassword(password);
                    loginListener.loginSuccess(user);
                }else{
                    loginListener.loginFailed();
                }
            }
        }.start();

    }
}

MVC模式總結:

  首先從響應用戶點擊事件,到MainActivity,獲取View中的數據再交給Model層處理,經過返回的數據,MainActivity再改變視圖。

 

 

從MVC到MVP:因爲View和Model之間的依賴仍是太強,但願他們能夠絕對獨立的存在,慢慢的就演化出了MVP

 

二  MVP模式

M:業務邏輯與實體模型

V:負責View的繪製以及用戶交互,對應Activity

P:負責完成View與Modle之間的交換

 

項目框架:

代碼部分:

  user:

package com.example.liang.userloginformvp.bean;

/**
 * Created by liang on 2016/8/22.
 */
public class User{
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

  IUserBiz:

package com.example.liang.userloginformvp.biz;



/**
 * Created by liang on 2016/8/22.
 */
public interface IUserBiz{
    public void login(String username,String password,OnLoginListener onLoginListener);
}

  OnLoginListener:

package com.example.liang.userloginformvp.biz;



/**
 * Created by liang on 2016/8/22.
 */
public interface IUserBiz{
    public void login(String username,String password,OnLoginListener onLoginListener);
}

  UserBiz:

package com.example.liang.userloginformvp.biz;

import com.example.liang.userloginformvp.bean.User;

/**
 * Created by liang on 2016/8/22.
 */
public class UserBiz implements IUserBiz {
    //在這裏重寫IUserBiz中的方法
    @Override
    public void login(final String username, final String password, final OnLoginListener loginListener) {
        //注意,這裏並無new OnLoginListener的對象,因此也沒有重寫方法
        //要留在UserLoginPresenter中重寫,並經過登錄的狀態調用相應的視圖
        new Thread(){
            @Override
            public void run() {
                try{
                    Thread.sleep(2000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                //模擬登錄成功
                if("lqy".equals(username)&&"123".equals(password)){
                    User user=new User();
                    user.setUsername(username);
                    user.setPassword(password);
                    loginListener.loginSuccess(user);
                }else{
                    loginListener.loginFailed();
                }
            }
        }.start();
    }
}

  UserLoginPresenter:

public class UserLoginPresenter {
    private IUserBiz userBiz;
    private IUserLoginView userLoginView;
    private Handler mHandler = new Handler();

    public UserLoginPresenter(IUserLoginView userLoginView) {
        this.userLoginView = userLoginView;

        //給userBiz一個子類的空間,此時方法已經被重寫,登錄邏輯已被判斷
        this.userBiz = new UserBiz();
    }

    public void login() {
        userLoginView.showLoading();
        userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener() {
            @Override
            public void loginSuccess(final User user) {
                //須要在UI線程執行
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        userLoginView.toMainActivity(user);
                        userLoginView.hideLoading();
                    }
                });

            }

            @Override
            public void loginFailed() {
                //須要在UI線程執行
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        userLoginView.showFailedError();
                        userLoginView.hideLoading();
                    }
                });

            }
        });
    }

    public void clear() {
        userLoginView.clearUserName();
        userLoginView.clearPassword();
    }


}

 

  IUserLoginPresenter:

package com.example.liang.userloginformvp.view;

import com.example.liang.userloginformvp.bean.User;

/**
 * Created by liang on 2016/8/22.
 */
public interface IUserLoginView {
    String getUserName();

    String getPassword();

    void clearUserName();

    void clearPassword();

    void showLoading();

    void hideLoading();

    void toMainActivity(User user);

    void showFailedError();
}

  MainActivity:

package com.example.liang.userloginformvp;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.example.liang.userloginformvp.bean.User;
import com.example.liang.userloginformvp.presenter.UserLoginPresenter;
import com.example.liang.userloginformvp.view.IUserLoginView;

public class MainActivity extends ActionBarActivity implements IUserLoginView{

    private EditText usernaem_edit,password_edit;
    private Button login_btn,clear_btn;
    private ProgressBar loading;

    private UserLoginPresenter userLoginPresenter=new UserLoginPresenter(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initViews();
    }

    private void initViews(){
        usernaem_edit=(EditText)findViewById(R.id.edit_username);
        password_edit=(EditText)findViewById(R.id.edit_password);

        login_btn=(Button)findViewById(R.id.login);
        clear_btn=(Button)findViewById(R.id.clear);

        loading=(ProgressBar)findViewById(R.id.login_progressbar);
        loading.setVisibility(View.INVISIBLE);
        login_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                userLoginPresenter.login();
            }
        });

        clear_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                userLoginPresenter.clear();
            }
        });

    }

    @Override
    public String getUserName() {
        return usernaem_edit.getText().toString();
    }

    @Override
    public String getPassword() {
        return password_edit.getText().toString();
    }

    @Override
    public void clearUserName() {
        usernaem_edit.setText("");
    }

    @Override
    public void clearPassword() {
        password_edit.setText("");
    }

    @Override
    public void showLoading() {
        loading.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        loading.setVisibility(View.GONE);
    }

    @Override
    public void toMainActivity(User user) {
        Toast.makeText(this,user.getUsername()+" login success , to MainActivity",Toast.LENGTH_SHORT)
                .show();
    }

    @Override
    public void showFailedError() {
        Toast.makeText(this, "login failed", Toast.LENGTH_SHORT).show();
    }
}

MVP總結:Activity做爲View加載視圖並響應點擊事件(直接想登錄),點擊事件交由Presenter處理,Presenter將登錄邏輯交給Model處理,並根據model處理結果告訴View改返回什麼試圖。

------------------------------------分隔線

 

從MVP到MVVM中:至於MVVM基本上和MVP如出一轍,感受只是名字替換了一下。他的關鍵技術就是今天的主題(Data Binding)。View的變化能夠自動的反應在ViewModel,ViewModel的數據變化也會自動反應到View上。這樣開發者就不用處理接收事件和View更新的工做,框架已經幫你作好了。

 

三  MVVM模式

M:model,業務邏輯

V:加載視圖,和layout通訊,進行數據綁定與更新,對應Activity(因爲代碼是在太少,有時自帶啓動本身的方法)

VM:判斷條件,邏輯實現

項目框架:

 

 

首先配置環境

加一句:

  LoginViewModel

package com.example.liang.userloginformvvm.viewmodel;

import android.content.Context;
import android.databinding.ObservableField;
import android.databinding.ObservableInt;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;

import com.example.liang.userloginformvvm.modle.User;
import com.example.liang.userloginformvvm.view_activity.MainActivity;

/**
 * Created by liang on 2016/8/23.
 */
public class LoginViewModel implements ViewModel {
    private Context context;
    //用於數據刷新的便捷類型:ObservableField,ObservableInt····
    public ObservableField<String> loginMessage;
    public ObservableInt loginMessageVisibility;
    private String editTextUsernameValue = "";
    private String editTextPasswordValue = "";

    public LoginViewModel(Context context) {
        this.context = context;
        this.loginMessage = new ObservableField<>("");
        this.loginMessageVisibility = new ObservableInt(View.INVISIBLE);
    }

    //登錄,實際上這個方法是在layout文件中調用的

    public void loginAuthentication(View view) {
        if ((editTextUsernameValue.equals("lqy")) && (editTextPasswordValue.equals("123"))) {
            loginMessage.set("");
            loginMessageVisibility.set(View.INVISIBLE);
            User user = new User(editTextUsernameValue, editTextPasswordValue);
            context.startActivity(MainActivity.newIntent(context, user));
        } else if ((editTextUsernameValue.equals("")) || (editTextPasswordValue.equals(""))) {
            loginMessage.set("Username or Password can't be empty!");
            loginMessageVisibility.set(View.VISIBLE);
        } else {
            loginMessage.set("Username = lqy \n Password = 123");
            loginMessageVisibility.set(View.VISIBLE);
        }
    }

    //觀察Text變化的TextWatcher
    public TextWatcher getUsernameUpdate() {
        return new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                editTextUsernameValue = charSequence.toString();
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        };
    }

    public TextWatcher getPasswordUpdate() {
        return new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                editTextPasswordValue = charSequence.toString();
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        };
    }


    @Override
    public void destroy() {

    }
}

  MainViewModel:

package com.example.liang.userloginformvvm.viewmodel;

import android.content.Context;

import com.example.liang.userloginformvvm.modle.User;

/**
 * Created by liang on 2016/8/23.
 */
public class MainViewModel {
    private Context context;
    private User user;

    public MainViewModel(Context context, User user) {
        this.context = context;
        this.user = user;
    }

    public String getUsername(){
        return user.username;
    }
    public String getPassword(){
        return user.password;
    }
}

  ViewModel:

package com.example.liang.userloginformvvm.viewmodel;

/**
 * Created by liang on 2016/8/23.
 */
public interface ViewModel {
    void destroy();
}

 

  User:

package com.example.liang.userloginformvvm.modle;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by liang on 2016/8/23.
 */
//實現Parcelable接口,可讓類在網絡或進程中傳遞
public class User implements Parcelable{
    public String username;
    public String password;

    public User(String username,String password){
        this.username=username;
        this.password=password;
    }

    public User(Parcel in) {
        username = in.readString();
        password = in.readString();
    }

    public static final Creator<User> CREATOR=new Creator<User>() {
        @Override
        public User createFromParcel(Parcel parcel) {
            return new User(parcel);
        }

        @Override
        public User[] newArray(int i) {
            return new User[i];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(this.username);
        parcel.writeString(this.password);
    }
}

  

  LoginActivity

package com.example.liang.userloginformvvm.view_activity;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;


import com.example.liang.userloginformvvm.R;
import com.example.liang.userloginformvvm.databinding.ActivityLoginBinding;
import com.example.liang.userloginformvvm.viewmodel.LoginViewModel;

/**
 * Created by liang on 2016/8/23.
 */
public class LoginActivity extends AppCompatActivity{
    //當給佈局指定格式以後會產生相應的Bing類,例如activity_login.xml會產生ActivityLoginBinding類
    ActivityLoginBinding loginBinding;
    LoginViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //指定佈局,沒有XXX.findViewById了
        loginBinding = DataBindingUtil.setContentView(this, R.layout.activity_login);

        viewModel=new LoginViewModel(this);
        loginBinding.setViewmodel(viewModel);
    }
}

  MainActivity

package com.example.liang.userloginformvvm.view_activity;

import android.content.Context;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.example.liang.userloginformvvm.R;
import com.example.liang.userloginformvvm.databinding.ActivityMainBinding;
import com.example.liang.userloginformvvm.modle.User;
import com.example.liang.userloginformvvm.viewmodel.MainViewModel;

/**
 * Created by liang on 2016/8/23.
 */
public class MainActivity extends AppCompatActivity{
    ActivityMainBinding activityMainBinding;
    MainViewModel mainViewModel;

    //傳遞數據的標誌
    private static final String EXTRA_USER = "extra_user";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityMainBinding= DataBindingUtil.setContentView(this, R.layout.activity_main);


        User user=getIntent().getParcelableExtra(EXTRA_USER);
        
        mainViewModel=new MainViewModel(this,user);
        activityMainBinding.setMainviewmodel(mainViewModel);
    }

    //viewmodel喚醒這個Activity的方法
    public static Intent newIntent(Context context, User user){
        Intent intent = new Intent(context, MainActivity.class);
        intent.putExtra(EXTRA_USER,user);
        return intent;
    }
}

 

  下面就是很是重要的layout文件了:

activity_mian.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">

    <data>
        <variable
            name="viewmodel"
            type="com.example.liang.userloginformvvm.viewmodel.LoginViewModel">

        </variable>
    </data>
    <!--先要指定綁定的類-->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".view_activity.LoginActivity">


        <TextView
            android:id="@+id/loginmessage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewmodel.loginMessage}"
            android:textAlignment="center"
            android:textColor="@color/colorAccent"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"/>

        <!--監聽text的變化-->
        <EditText
            android:id="@+id/login_username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:ems="10"
            android:layout_centerHorizontal="true"
            android:layout_below="@+id/loginmessage"
            android:hint="UserName"
            android:layout_marginBottom="10dp"
            app:addTextChangedListener="@{viewmodel.usernameUpdate}"/>

        <EditText
            android:id="@+id/login_password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:hint="Password"
            android:ems="10"
            android:layout_below="@id/login_username"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="10dp"
            app:addTextChangedListener="@{viewmodel.passwordUpdate}" />

        <Button
            android:layout_width="210dp"
            android:layout_height="wrap_content"
            android:text="LOGIN"
            android:layout_below="@id/login_password"
            android:layout_centerHorizontal="true"
            android:onClick="@{viewmodel.loginAuthentication}"/>

    </RelativeLayout>
</layout>

 

activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="mainviewmodel"
            type="com.example.liang.userloginformvvm.viewmodel.MainViewModel"></variable>
    </data>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".view_activity.MainActivity">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:orientation="horizontal">
            <TextView
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:text="Welcome"
                android:layout_weight="1"/>

            <TextView
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="3"
                android:text="@{mainviewmodel.username}"/>
        </LinearLayout>


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:orientation="horizontal">

            <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:text="Password is"
            android:layout_weight="1"/>

            <TextView
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="3"
                android:text="@{mainviewmodel.password}"/>
        </LinearLayout>
    </LinearLayout>

</layout>

MVVM總結:經過View加載佈局,並經過佈局調用ViewModel中的方法,VeiwModel能夠經過調用後的結果決定啓動什麼視圖,同時ViewModel保持和model的通訊。

 

2016.8.24首次截稿 

有什麼錯誤麻煩你們指出來,在線更新。 

 

參考資料:

鴻洋老師的博客: http://blog.csdn.net/lmj623565791/article/details/46596109

胡笛老師的微信推文

還有github上的資源

以及http://my.oschina.net/u/1175007/blog/613889

相關文章
相關標籤/搜索