MVP_用户登录实例

以一个用户登录实例,学习MVP 架构。

其中包括登录界面内容主页界面, 这两个都使用了MVP 实现,思路是一样的。最后给出了UML 类图及总结。

最好是自己对着例子写一遍,加深印象和理解。

目录

1. 简单概述

(1) 用户登录界面, 可以输入账号 和密码, 点击登录后进行验证, 并显示进度条。

(2) 验证结果分为:账号或密码为空,则提示; 其它情况视为验证通过

(3) 验证通过后, 模拟加载页面并显示内容

(4) 点击内容提示 (Toast 提示,省略截图)

2. 实现代码

2.1 登录界面

2.1.1 抽象登录界面接口(View)

2.1.2  模拟服务器验证 (Model)

2.1.3  Presenter 持有登录界面接口(View)的引用,并实现Model 定义的接口

2.1.4  Activity 实现LoginView 接口,创建并持有Presenter对象

2.2 内容主页

2.2.1 抽象内容主页操作接口(View)

2.2.2  模拟向服务器获取主页数据 (Model)

2.2.3  Presenter 持有主页内容定义的接口(View)的引用,并实现Model 定义的接口

2.2.4  Activity 实现SimpleContentView 接口,创建并持有Presenter对象

3. UML 类图及总结


1. 简单概述

(1) 用户登录界面, 可以输入账号 和密码, 点击登录后进行验证, 并显示进度条。

(2) 验证结果分为:账号或密码为空,则提示; 其它情况视为验证通过

(3) 验证通过后, 模拟加载页面并显示内容

(4) 点击内容提示 (Toast 提示,省略截图)

2. 实现代码

2.1 登录界面

2.1.1 抽象登录界面接口(View)

public interface LoginView {
    void showProgress();

    void hideProgress();

    void setUserNameError();

    void setPasswordError();

    void navigateToHome();
}

提供显示、隐藏进度条, 设置用户名、密码错误提示,验证通过后进入内容主页操作

2.1.2  模拟服务器验证 (Model)

public class LoginIterator {
    public interface OnLoginFinishedListener{
        void onUsernameError();

        void onPasswordError();

        void onSuccess();
    }

    public void login(final String username, final String password, final OnLoginFinishedListener listener){
        new Handler().postDelayed(()-> {
            if(TextUtils.isEmpty(username)) {
                listener.onUsernameError();
                return;
            }
            if(TextUtils.isEmpty(password)) {
                listener.onPasswordError();
                return;
            }
            listener.onSuccess();
        }, 2000);
    }
}

模拟对用户名 和 密码进行验证操作, 并且通过接口返回验证结果

2.1.3  Presenter 持有登录界面接口(View)的引用,并实现Model 定义的接口

//模拟向服务器验证用户名和密码,并把验证结果转发给View
public class LoginPresenter implements LoginIterator.OnLoginFinishedListener {
    private  LoginView mLoginView;
    private final LoginIterator mLoginInterator;

    public LoginPresenter(LoginView loginView, LoginIterator loginIterator) {
        mLoginView = loginView;
        mLoginInterator = loginIterator;
    }

    public void verify(String username, String password) {
        if (mLoginView != null) {
            mLoginView.showProgress();
        }

        mLoginInterator.login(username, password, this);
    }

    public void onDestroy(){
        mLoginView = null;
    }

    @Override
    public void onUsernameError() {
        if (mLoginView != null) {
            mLoginView.setUserNameError();
            mLoginView.hideProgress();
        }
    }

    @Override
    public void onPasswordError() {
        if (mLoginView != null) {
            mLoginView.setPasswordError();
            mLoginView.hideProgress();
        }
    }

    @Override
    public void onSuccess() {
        if (mLoginView != null) {
            mLoginView.navigateToHome();
        }
    }
}

实际上传入的对象 loginView 是LoginView 接口的具体实现类。

2.1.4  Activity 实现LoginView 接口,创建并持有Presenter对象

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;

import androidx.appcompat.app.AppCompatActivity;

import com.example.mvplogindemo.model.LoginIterator;
import com.example.mvplogindemo.presenter.LoginPresenter;
import com.example.mvplogindemo.ui.LoginView;
import com.example.mvplogindemo.ui.SimpleContentActivity;

public class LoginActivity extends AppCompatActivity implements LoginView {
    private EditText mUserNameEt;
    private EditText mPasswordEt;
    private Button mLoginBtn;
    private ProgressBar mLoginProgress;
    private LoginPresenter mPresenter;

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

        mUserNameEt = findViewById(R.id.username);
        mPasswordEt = findViewById(R.id.password);
        mLoginBtn = findViewById(R.id.login_button);
        mLoginProgress = findViewById(R.id.login_progress);

        mLoginBtn.setOnClickListener(v -> verify());

        mPresenter = new LoginPresenter(this, new LoginIterator());
    }

    @Override
    protected void onDestroy() {
        mPresenter.onDestroy();
        super.onDestroy();
    }

    @Override
    public void showProgress() {
        mLoginProgress.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideProgress() {
        mLoginProgress.setVisibility(View.GONE);
    }

    @Override
    public void setUserNameError() {
        mUserNameEt.setError(getResources().getString(R.string.username_error));
    }

    @Override
    public void setPasswordError() {
        mPasswordEt.setError(getResources().getString(R.string.password_error));
    }

    @Override
    public void navigateToHome() {
        startActivity(new Intent(this, SimpleContentActivity.class));
        finish();
    }

    private void verify() {
        mPresenter.verify(mUserNameEt.getText().toString(), mPasswordEt.getText().toString());
    }
}

可以看到,

(1) Activity 中仅对View 进行加载 和更新操作, 逻辑操作会转发Presenter, 并且没有持有Model 的引用, 这也是MVP 的核心思想 (View Model 解耦)。

(2) SimpleContentActivity 是模拟登陆成功后, 主页显示的内容,在下面会介绍。

(3) activity_login.xml 是一个线性布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="250dp"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="16dp"
    android:gravity="center"
    android:orientation="vertical">

    <EditText
        android:id="@+id/username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawablePadding="8dp"
        android:drawableStart="@drawable/ic_username"
        android:gravity="center_vertical"
        android:hint="@string/user_name"
        android:inputType="text"/>

    <EditText
        android:id="@+id/password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawablePadding="8dp"
        android:drawableStart="@drawable/ic_password"
        android:gravity="center_vertical"
        android:hint="@string/password"
        android:inputType="text"/>

    <Button
        android:id="@+id/login_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/login"
        android:layout_marginTop="8dp"/>

    <ProgressBar
        android:id="@+id/login_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:visibility="gone"/>

</LinearLayout>

其中,   android:drawableStart="@drawable/ic_username"   和  android:drawableStart="@drawable/ic_password"   表示输入框的前面的图标, 这里是用 vector标签实现的,Android studio 可以很方便的添加。

ic_username.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      android:pathData="M3,5v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2L5,3c-1.11,0 -2,0.9 -2,2zM15,9c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3zM6,17c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1L6,18v-1z"/>
</vector>

ic_password.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      android:pathData="M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
</vector>

至此,关于登录界面的实现,已经是使用了MVP 去实现的。 

可以看出,Presenter 是核心类, 使得View  和Model 分离, 并且Presenter 到 View /  Model 到Presenter 是使用接口调用的, 使得代码更加利于管理和维护(即类间的联系关注接口 和它的实现即可)

登录后的内容也可以使用MVP 实现。

2.2 内容主页

同样是按上面的思路, 先对View 的操作进行抽象,再模拟Model 提供数据,接着Presenter 持有View 的接口实现类和Model对象, 最后Activity 实现View接口并创建Presenter.

这里显示的内容数据使用到RecyclerView, 对应会有RecyclerViewAdapter.

2.2.1 抽象内容主页操作接口(View)

import java.util.List;

public interface SimpleContentView {
    void showProgress();

    void hideProgress();

    void setItems(List<String> items);

    void showMessage(String message);
}

其中包含显示、隐藏进度条, 设置RecyclerView的数据(setItems), 以及显示点击消息(showMessage)

2.2.2  模拟向服务器获取主页数据 (Model)

import android.os.Handler;

import java.util.Arrays;
import java.util.List;

public class FindItemsIterator {
    public interface OnFinishedListener {
        void onFinished(List<String> items);
    }

    public void findItems(final OnFinishedListener listener) {
        new Handler().postDelayed(() -> listener.onFinished(createContentList()), 2000);
    }

    private List<String> createContentList() {
        return Arrays.asList(
                "Item 1",
                "Item 2",
                "Item 3",
                "Item 4",
                "Item 5",
                "Item 6",
                "Item 7",
                "Item 8",
                "Item 9"
        );
    }
}

其中

(1) findItems 为提供获取数据的接口,供Presenter 调用。 此处使用Handler 延迟两秒返回数据。

(2) OnFinishedListener 接口用于获取数据后,回调通知调用者(即Presenter需要实现这个接口)

(3) createContentList 模拟了返回的数据

2.2.3  Presenter 持有主页内容定义的接口(View)的引用,并实现Model 定义的接口

import com.example.mvplogindemo.model.FindItemsIterator;
import com.example.mvplogindemo.ui.SimpleContentView;

import java.util.List;

public class SimpleContentPresenter {

    private SimpleContentView mSimpleContentView;
    private final FindItemsIterator mFindItemsIterator;

    public SimpleContentPresenter(SimpleContentView simpleContentView, FindItemsIterator findItemsIterator){
        mSimpleContentView = simpleContentView;
        mFindItemsIterator = findItemsIterator;
    }

    public void onResume(){
        if (mSimpleContentView != null) {
            mSimpleContentView.showProgress();
        }

        mFindItemsIterator.findItems(this::onFinished);
    }

    public void onFinished(List<String> items){
        if (mSimpleContentView != null) {
            mSimpleContentView.hideProgress();
            mSimpleContentView.setItems(items);
        }
    }

    public void onDestroy() {
        mSimpleContentView = null;
    }
}

其中,

(1) onResume/ onDestroy 方法供Activity 生命周期的调用,   onResume 时显示进度条, 并且向Model 发起获取数据请求

(2) onFinished 为Model 的接口回调函数, 传回来数据,Presenter 再把它转发给Activity(View)

(3) 注意解除view的引用:  mSimpleContentView = null;

2.2.4  Activity 实现SimpleContentView 接口,创建并持有Presenter对象

package com.example.mvplogindemo.ui;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.example.mvplogindemo.R;
import com.example.mvplogindemo.model.FindItemsIterator;
import com.example.mvplogindemo.presenter.SimpleContentPresenter;

import java.util.List;


public class SimpleContentActivity extends AppCompatActivity implements SimpleContentView{
    private RecyclerView mRecyclerView;
    private ProgressBar mProgressBar;
    private ContentRecyclerViewAdapter mContentRecyclerViewAdapter;
    private SimpleContentPresenter mPresenter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple_content);
        mRecyclerView = findViewById(R.id.content_recyclerview);
        mProgressBar = findViewById(R.id.content_progress);

        initRecyclerView();
        mPresenter = new SimpleContentPresenter(this, new FindItemsIterator());
    }

    @Override
    protected void onDestroy() {
        mPresenter.onDestroy();
        super.onDestroy();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mPresenter.onResume();
    }

    private void initRecyclerView() {
        mContentRecyclerViewAdapter = new ContentRecyclerViewAdapter();
        //1. layoutManager
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);
        //2. adapter
        mRecyclerView.setAdapter(mContentRecyclerViewAdapter);
        //3. listener
        mContentRecyclerViewAdapter.setOnItemClickListener(new ContentRecyclerViewAdapter.onItemClickListener() {
            @Override
            public void onItemClick(String title) {
                showMessage(title);
            }
        });
    }

    @Override
    public void showProgress() {
        mProgressBar.setVisibility(View.VISIBLE);
        mRecyclerView.setVisibility(View.INVISIBLE);
    }

    @Override
    public void hideProgress() {
        mProgressBar.setVisibility(View.INVISIBLE);
        mRecyclerView.setVisibility(View.VISIBLE);
    }

    @Override
    public void setItems(List<String> items) {
        mContentRecyclerViewAdapter.setItems(items);
    }

    @Override
    public void showMessage(String message) {
        Toast.makeText(this, message + " clicked", Toast.LENGTH_SHORT).show();
    }
}

其中,

(1) Activity 中仍是不会持有Model 引用, 仅是通过Presenter 交互

(2) 布局文件 activity_simple_content.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.SimpleContentActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/content_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <ProgressBar
        android:id="@+id/content_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone"/>

</FrameLayout>

相当简单, 仅包含一个RecyclerView 和一个 ProgressBar

需要注意的是,使用RecyclerView时,需要在配置文件(app 目录的build.gradle) 中添加依赖包, 否则编译器会报错

apply plugin: 'com.android.application'
dependencies {
    //省略其它的
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

(3) RecyclerView 对应的RecyclerViewAdapter 适配器:

import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.mvplogindemo.R;

import java.util.ArrayList;
import java.util.List;

public class ContentRecyclerViewAdapter extends RecyclerView.Adapter<ContentRecyclerViewAdapter.InnerViewHolder> {
    private List<String> mItems;
    private onItemClickListener mOnItemClickListener;

    public ContentRecyclerViewAdapter(){
        mItems = new ArrayList<>();
    }

    public void clearItems(){
        mItems.clear();
    }

    public void setItems(List<String> items){
        clearItems();
        mItems.addAll(items);
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public ContentRecyclerViewAdapter.InnerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = View.inflate(parent.getContext(), R.layout.content_item_view, null);
        InnerViewHolder holder = new InnerViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ContentRecyclerViewAdapter.InnerViewHolder holder, int position) {
        final String title = mItems.get(position);
        holder.setData(title);
        holder.titleTv.setOnClickListener(v -> {
            if (mOnItemClickListener!=null) {
                mOnItemClickListener.onItemClick(title);
            }
        });
    }

    @Override
    public int getItemCount() {
        return mItems == null ? 0 : mItems.size();
    }

    class InnerViewHolder extends RecyclerView.ViewHolder{
        public TextView titleTv;

        public InnerViewHolder(@NonNull View itemView) {
            super(itemView);
            titleTv = itemView.findViewById(R.id.title);
        }

        public void setData(String title) {
            titleTv.setText(title);
        }
    }

    public interface onItemClickListener {
        void onItemClick(String title);
    }

    public void setOnItemClickListener(onItemClickListener listener) {
        mOnItemClickListener = listener;
    }
}

这里无非就是 Adapter 的使用,存储数据、创建ViewHolder、绑定数据、返回条码数量、提供点击回调接口 等等, 可以参考之前的RecyclerView 的使用介绍。

需要注意的是setItems方法, Activity 在收到Presenter的转发后(也是setItems), 会调用这个方法,进行数据的更新, 并且 要调用父类 RecyclerView.Adapter 的 notifyDataSetChanged去更新数据和刷新界面。

item view 的布局很简单(content_item_view.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="50dp"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="item "
        android:gravity="center"
        android:textSize="20sp"/>

</LinearLayout>

至此, 内容主页的代码也完成,和登录界面的思想是类似的, 为了对View 和 Model 解耦。

此外, Presenter 也可抽象为一个公用的接口,抽象一些公用的方法,如Activity生命周期相关的函数,并且可以持有一个View的泛型接口或泛型抽象类, 增强通用性。

3. UML 类图及总结

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我给您举一个 Android MVP 框架实例。 假设我们要开发一个简单的登录界面,包含一个用户名输入框、一个密码输入框和一个登录按钮。我们可以按照 MVP 框架的方式来组织代码。 首先,我们定义一个 View 接口,包含了用户界面的一些方法,例如显示错误信息、显示登录成功等。 ```java public interface LoginView { void showProgress(); void hideProgress(); void setUsernameError(); void setPasswordError(); void navigateToHome(); } ``` 然后,我们定义一个 Presenter 接口,包含了处理用户输入的一些方法,例如验证用户名密码、进行登录等。 ```java public interface LoginPresenter { void validateCredentials(String username, String password); void onDestroy(); } ``` 接下来,我们实现 Presenter 接口,并在其中处理逻辑。 ```java public class LoginPresenterImpl implements LoginPresenter { private LoginView view; public LoginPresenterImpl(LoginView view) { this.view = view; } @Override public void validateCredentials(String username, String password) { if (TextUtils.isEmpty(username)) { view.setUsernameError(); return; } if (TextUtils.isEmpty(password)) { view.setPasswordError(); return; } // 调用模型层的方法验证用户名密码 boolean isValid = UserModel.validateUser(username, password); if (isValid) { view.showProgress(); view.navigateToHome(); } else { view.hideProgress(); view.setUsernameError(); view.setPasswordError(); } } @Override public void onDestroy() { view = null; } } ``` 最后,我们实现 Activity 中的 View 接口,并将 Presenter 和 View 进行绑定。 ```java public class LoginActivity extends AppCompatActivity implements LoginView { private EditText etUsername; private EditText etPassword; private Button btnLogin; private ProgressBar progressBar; private LoginPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); etUsername = findViewById(R.id.et_username); etPassword = findViewById(R.id.et_password); btnLogin = findViewById(R.id.btn_login); progressBar = findViewById(R.id.progress_bar); presenter = new LoginPresenterImpl(this); btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String username = etUsername.getText().toString(); String password = etPassword.getText().toString(); presenter.validateCredentials(username, password); } }); } @Override public void showProgress() { progressBar.setVisibility(View.VISIBLE); } @Override public void hideProgress() { progressBar.setVisibility(View.GONE); } @Override public void setUsernameError() { etUsername.setError("用户名不能为空"); } @Override public void setPasswordError() { etPassword.setError("密码不能为空"); } @Override public void navigateToHome() { Intent intent = new Intent(this, HomeActivity.class); startActivity(intent); finish(); } @Override protected void onDestroy() { super.onDestroy(); presenter.onDestroy(); } } ``` 以上就是一个简单的 Android MVP 框架实例,其中 View 接口定义了用户界面的一些方法,Presenter 接口定义了处理用户输入的一些方法,Presenter 实现了 Presenter 接口中的方法并在其中处理逻辑,Activity 实现了 View 接口中的方法并将 Presenter 和 View 进行了绑定。通过这种方式,我们可以将不同层的代码分离开来,使得代码更加清晰、易于维护和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值