負責顯示介面和處理使用者的互動行為(像是顯示資料、監聽按鈕點擊等)。
它本身不處理業務邏輯,邏輯會交給 Presenter 來做。
負責所有的邏輯判斷和處理。
它接收 View 發來的操作指令,去向 Model 取得或修改資料,並把結果回傳給 View,決定下一步畫面要怎麼呈現。
可以想像成溝通的中介者,控制整個流程和業務規則。
負責管理和存取資料,像是用戶資料、帳號密碼或從資料庫撈資料。
Model 像是資料的倉庫,Presenter 需要資料時就會向它取用。
是一組用來定義 View、Presenter 和 Model 之間溝通方法的接口(interface)。
透過 Contract,三者能清楚知道彼此提供和要求哪些功能,降低耦合度,也方便多人協作和維護。
ui介面,這頁是登入畫面,用來填寫帳號密碼的,其他的邏輯判斷並不寫在這一頁
public class MainActivity extends AppCompatActivity implements MainContract.view {
private EditText accountEditText, passwordEditText;
private Button loginButton;
private MainPresenter presenter;
//設定變數
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
accountEditText = findViewById(R.id.main_account_et);
passwordEditText = findViewById(R.id.main_password_et);
loginButton = findViewById(R.id.main_login_btn);
//New出Presenter,並把自己丟入。
presenter = new MainPresenter(this);
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//將accountEt和passwordEt裡的內容轉成字串並丟給設定的變數
String account = accountEditText.getText().toString();
String password = passwordEditText.getText().toString();
//再將兩個變數丟給presenter去處理
presenter.Login(account, password);
}
});
}
@Override
public void LoginSuccess() {
Toast.makeText(this, "登入成功", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
@Override
public void LoginError() {
Toast.makeText(this, "登入失敗,請檢查帳號或密碼", Toast.LENGTH_SHORT).show();
}
}
presenter則負責處理邏輯判斷等工作,像這頁是判斷輸入的帳號與密碼是否與 Model 中預設的帳密相符,若相符則呼叫 View 的 LoginSuccess(),不相符則呼叫 View 的 LoginError(),另外,MainPresente、MainActivity、MainModle之間要連接呼叫都必須透過MainContract
public class MainPresenter implements MainContract.presenter {
//用來與 MainActivity(View)溝通的介面
//MainPresenter 不能直接呼叫 MainActivity,必須透過 MainContract.view 的介面來操作。
private MainContract.view view;
private MainModel model;
public MainPresenter(MainContract.view view) {
this.view = view;
//接收view
model = new MainModel(this);
//初始化Model並把自己丟入
}
@Override
public void Login(String username, String password) {
//從model調正確資料過來
//判斷MainActivity傳送過來的帳密與MainModel裡設定的帳密是否相同
boolean isSuccess = model.LoginData(username, password);
if (isSuccess) {
view.LoginSuccess();
//通知view顯示登入成功並執行登入成功後的操作
} else {
view.LoginError();
//通知view顯示登入失敗並執行登入失敗後的操作
}
}
}
modle它有些類似資料庫,負責管理和存取資料,當接收到MainPresenter查詢正確資料的請求後,它會將正確資料回傳給MainPresenter,供MainPresenter比對
public class MainModel implements MainContract.model {
private MainContract.presenter presenter;
public MainModel(MainContract.presenter presenter) {
this.presenter = presenter;
//接收presenter
}
public boolean LoginData(String username, String password) {
return username.equals("admin") && password.equals("1234");
//回傳正確的帳號是admin且正確的密碼為1234
}
}
MainContract 是在整個 MVP 架構中,作為View、Presenter、Model 三者溝通的橋樑,並定義介面合約,其他三頁之間的互動及資料往來都需要透過Contract做為橋樑
public interface MainContract {
interface view{
//登入成功時由 Presenter 呼叫,讓 View 做相對應的 UI 操作(如跳轉頁面、顯示提示)
void LoginSuccess();
void LoginError();
}
interface presenter{
void Login(String username, String password);
}
interface model{
boolean LoginData(String username, String password);
}
}
下面是這兩個Activity完整的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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:layout_weight="1">
<TextView
android:id="@+id/main_title_tv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.1"
android:gravity="center"
android:text="Login"
android:textSize="25dp"
android:textStyle="bold" />
<EditText
android:id="@+id/main_account_et"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.1"
android:hint="帳號"
android:inputType="text"/>
<EditText
android:id="@+id/main_password_et"
android:layout_width="match_parent"
android:layout_height="0dp"
android:hint="密碼"
android:layout_weight="0.1"
android:inputType="textPassword" />
<Button
android:id="@+id/main_login_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.1"
android:text="登入" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<TextView
android:id="@+id/login_success_tv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2"
android:gravity="center"
android:textSize="50dp"
android:textStyle="bold"
android:text="登入成功" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>