33

Android 之路 (2) - 对登陆进行 MVP 改造

 5 years ago
source link: http://fullscreendeveloper.cn/articles/2018/10/09/1539084359439.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

引言

本文承接上篇,在实现了简单登录后,本章更近一步,先实现UI效果,再将架构模式更改为 MVP .

正文

关于MVP模式,我推荐的是谷歌官方的例子 android-architecture ,这个库基本包含了所有的架构模式,是不错的入门和深入的教程。

需要注意的是,架构模式没有最好,只有最适合,不要盲目的追求架构而忽略了编码。本文所使用的MVP模式是最简单的一种模式,将来会在MVP的基础上,演变成为属于自己的MVP变种。

实现上章遗留的登陆UI

新建输入框背景

输入框的背景色太过于难看了,我们新建一个selector来写样式。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!--没有获取焦点的时候-->
    <item android:state_focused="false">
        <shape android:shape="rectangle">
            <corners android:radius="2dp" />
            <solid android:color="#fff" />
            <stroke android:width="1dp" android:color="#dedede" />

        </shape>
    </item>
    <!--获取到焦点之后-->
    <item android:state_focused="true">
        <shape android:shape="rectangle">
            <corners android:radius="2dp" />
            <solid android:color="#fff" />
            <stroke android:width="1dp" android:color="@color/colorPrimary" />
        </shape>
    </item>
</selector>

在xml中进行引用。

新建按钮背景

同上,新建一个selector作为登陆按钮的背景。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!--没有按下的时候-->
    <item android:state_pressed="false">
        <shape android:shape="rectangle">
            <corners android:radius="2dp" />
            <solid android:color="@color/colorPrimary" />
        </shape>
    </item>
    <!--按下之后-->
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <corners android:radius="2dp" />
            <solid android:color="@color/colorPrimaryDark" />
        </shape>
    </item>
</selector>

在布局中引用

效果

为了美观,增加了一些样式上的调整。

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".moudle.user.LoginActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="270dp"
        android:scaleType="fitXY"
        android:src="@mipmap/login_top_bg" />

    <EditText
        android:id="@+id/et_account"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_margin="10dp"
        android:background="@drawable/bg_edit_border"
        android:hint="帐号"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:singleLine="true" />

    <EditText
        android:id="@+id/et_pass"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_margin="10dp"
        android:background="@drawable/bg_edit_border"
        android:hint="密码"
        android:inputType="textPassword"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:singleLine="true" />

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_margin="10dp"
        android:background="@drawable/bg_login_botton"
        android:text="登录"
        android:textColor="#fff"
        android:textSize="15sp" />

</LinearLayout>

运行起来看看效果吧。

3IzYbmA.png!web

沉浸式

上面已经完成了页面的调整,但是剩余顶部的图片并没有沉浸到状态栏中,这效果并不是很好,所以引入一个开源库:

依赖开源库

// 沉浸状态栏
implementation 'com.jaeger.statusbarutil:library:1.5.1'

在onCreate中的setContentView之后调用以下代码

StatusBarUtil.setTranslucentForImageView(this, null);

来看看效果吧。

JBJRVfV.png!web

MVP

好了,页面效果我们已经实现了,下一步就是开始更改代码结构为MVP模式了。

新建合约类

新建合约类,确定View层的接口和P层的抽象接口。

/**
 * 登陆合约类
 */
public interface LoginContract {
    interface View {
        /**
         * 登陆成功
         *
         * @param loginDto 成功回调的信息
         */
        void loginSuccess(LoginDto loginDto);

        /**
         * 登陆失败
         *
         * @param message 失败消息
         */
        void loginFailure(String message);
    }

    abstract class Presenter {
        /**
         * 持有View层
         */
        protected View view;

        public Presenter(View view) {
            this.view = view;
        }

        /**
         * 登陆
         *
         * @param userName 用户名
         * @param password 密码
         */
        public abstract void login(String userName, String password);
    }
}

继承实现Presenter

创建LoginPresenter继承LoginContract.Presenter,将Activity中的请求部分代码转移到Presenter。

/**
 * 登陆Presenter
 */
public class LoginPresenter extends LoginContract.Presenter {

    private String TAG = "LoginPresenter";

    /**
     * 用户服务
     */
    private UserService mUserService;


    public LoginPresenter(LoginContract.View view) {
        super(view);
        // 日志拦截器,可以打印出所有的请求过程中的信息
        // 如:请求体、参数、响应体等
        HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor();
        logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addInterceptor(logInterceptor);

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://olrt5mymy.bkt.clouddn.com/")//请求url
                //增加转换器,这一步能直接Json字符串转换为实体对象
                .addConverterFactory(CustGsonConverterFactory.create())
                //加入 RxJava转换器
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(builder.build())
                .build();

        mUserService = retrofit.create(UserService.class);
    }

    @Override
    public void login(String userName, String password) {
        // 开始请求
        Subscriber subscriber = mUserService.login(userName, password)
                .subscribeOn(Schedulers.io())//运行在io线程
                .observeOn(AndroidSchedulers.mainThread())//回调在主线程
                .subscribeWith(new ResourceSubscriber<LoginDto>() {
                    @Override
                    public void onNext(LoginDto loginDto) {
                        //结果回调
                        Log.e(TAG, "onNext: " + loginDto);
                        if (loginDto.getCode() == 200) {
                            view.loginSuccess(loginDto);
                        } else {
                            view.loginFailure(loginDto.getMessage());
                        }

                    }

                    @Override
                    public void onError(Throwable t) {
                        Log.e(TAG, "onError: ");
                        view.loginFailure("登陆失败:" + t.getMessage());
                    }

                    @Override
                    public void onComplete() {
                        Log.e(TAG, "onComplete: ");
                    }
                });
    }
}

Activity中使用Presenter

将Activity中的请求代码网络请求代码移除,换成持有LoginPresenter进行登陆。

public class LoginActivity extends AppCompatActivity implements LoginContract.View {

    private String TAG = "LoginActivity";

    private LoginPresenter mLoginPresenter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        mLoginPresenter = new LoginPresenter(this);
        initEvent();
    }


    private void initEvent() {
        //获取帐号输入框
        final EditText etAccount = findViewById(R.id.et_account);
        //获取密码输入框
        final EditText etPassword = findViewById(R.id.et_pass);

        //获取登录按钮 设置点击事件
        findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
            @SuppressLint("CheckResult")
            @Override
            public void onClick(View v) {
                //获取帐号
                String account = etAccount.getText().toString();
                //获取密码
                String password = etPassword.getText().toString();
                //登录
                Toast.makeText(LoginActivity.this, "正在登陆", Toast.LENGTH_SHORT).show();
                mLoginPresenter.login(account, password);

            }
        });
    }

    @Override
    public void loginSuccess(LoginDto loginDto) {
        Toast.makeText(this, "登陆成功:" + loginDto.toString(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginFailure(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
}

可以看到,现在的代码结构非常的清晰明了,也不是那么臃肿冗余了,接下来运行起来看看效果吧。

登陆效果

fyAFJbm.gif

最后

本章完成了 MVP 模式的迁移,但是我们挖的坑也越多了,敬请期待下篇:对Retrofit的封装!

本次 源码

软广

一个痴心妄想想成为一个全屏(栈)工程师的程序猿。

来来,关注一下吧!

RnA7Vra.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK