引言
本文承接上篇,在實現(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)欄中,這效果并不是很好,所以引入一個開源庫:
依賴開源庫
// 沉浸狀態(tài)欄
implementation 'com.jaeger.statusbarutil:library:1.5.1'
在onCreate中的setContentView之后調(diào)用以下代碼
StatusBarUtil.setTranslucentForImageView(this, null);
來看看效果吧。
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的封裝!
本次源碼