Android 之路 (2) - 對登陸進(jìn)行 MVP 改造

引言

本文承接上篇,在實現(xiàn)了簡單登錄后,本章更近一步,先實現(xiàn)UI效果,再將架構(gòu)模式更改為 ==MVP #f44336==

正文

關(guān)于MVP模式,我推薦的是谷歌官方的例子android-architecture,這個庫基本包含了所有的架構(gòu)模式,是不錯的入門和深入的教程。
需要注意的是,架構(gòu)模式?jīng)]有最好,只有最適合,不要盲目的追求架構(gòu)而忽略了編碼。本文所使用的MVP模式是最簡單的一種模式,將來會在MVP的基礎(chǔ)上,演變成為屬于自己的MVP變種。

實現(xiàn)上章遺留的登陸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中進(jìn)行引用。

新建按鈕背景

同上,新建一個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>

在布局中引用

效果

為了美觀,增加了一些樣式上的調(diào)整。

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

運(yùn)行起來看看效果吧。

效果

沉浸式

上面已經(jīng)完成了頁面的調(diào)整,但是剩余頂部的圖片并沒有沉浸到狀態(tài)欄中,這效果并不是很好,所以引入一個開源庫:

StatusBarUtil
相關(guān)介紹

依賴開源庫

// 沉浸狀態(tài)欄
implementation 'com.jaeger.statusbarutil:library:1.5.1'

在onCreate中的setContentView之后調(diào)用以下代碼

StatusBarUtil.setTranslucentForImageView(this, null);

來看看效果吧。

實現(xiàn)沉浸式

MVP

好了,頁面效果我們已經(jīng)實現(xiàn)了,下一步就是開始更改代碼結(jié)構(gòu)為MVP模式了。

新建合約類

新建合約類,確定View層的接口和P層的抽象接口。

/**
 * 登陸合約類
 */
public interface LoginContract {
    interface View {
        /**
         * 登陸成功
         *
         * @param loginDto 成功回調(diào)的信息
         */
        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);
    }
}

繼承實現(xiàn)Presenter

創(chuàng)建LoginPresenter繼承LoginContract.Presenter,將Activity中的請求部分代碼轉(zhuǎn)移到Presenter。

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

    private String TAG = "LoginPresenter";

    /**
     * 用戶服務(wù)
     */
    private UserService mUserService;


    public LoginPresenter(LoginContract.View view) {
        super(view);
        // 日志攔截器,可以打印出所有的請求過程中的信息
        // 如:請求體、參數(shù)、響應(yīng)體等
        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
                //增加轉(zhuǎn)換器,這一步能直接Json字符串轉(zhuǎn)換為實體對象
                .addConverterFactory(CustGsonConverterFactory.create())
                //加入 RxJava轉(zhuǎn)換器
                .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())//運(yùn)行在io線程
                .observeOn(AndroidSchedulers.mainThread())//回調(diào)在主線程
                .subscribeWith(new ResourceSubscriber<LoginDto>() {
                    @Override
                    public void onNext(LoginDto loginDto) {
                        //結(jié)果回調(diào)
                        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中的請求代碼網(wǎng)絡(luò)請求代碼移除,換成持有LoginPresenter進(jìn)行登陸。

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);

        //獲取登錄按鈕 設(shè)置點擊事件
        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();
    }
}

可以看到,現(xiàn)在的代碼結(jié)構(gòu)非常的清晰明了,也不是那么臃腫冗余了,接下來運(yùn)行起來看看效果吧。

登陸效果

演示

最后

本章完成了 MVP 模式的遷移,但是我們挖的坑也越多了,敬請期待下篇:對Retrofit的封裝!

本次源碼

微信公眾號

AndroidRookie
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容