在前兩篇學會MDC Components的Button和Text Field,所以已經可以來實作一個「登錄頁面」。
因為接下來的程式碼會全部使用ViewBinding取得對應view的 ID引用。
Kotlin 有個非常便捷的特性,無需再使用 findViewById() 對應view的 ID引用
build.gradle (Module: MDCSubject.app) 新增
但為什麼這麼方便我還要使用ViewBinding,因為官方已經對Kotlin Android Extensions 已提出棄用,通常棄用也不是不能用還是可以用到官方說不能用為止。
過去使用Kotlin Android Extensions的經驗,使用上必須注意class上面的import所引用的layout是否正確,因為有可能import到不正確的layout,這個IDE不會提示錯誤,必須靠人來檢查,所以建議使用ViewBinding。
XML layout 自動生成Binding class,對view的 ID 引用,在class 的 onCreate 時透過使用生成的class.inflate(layoutInflater),且每個binging class還包含一個 getRoot() 方法,為相應layout的root的view提供直接引用
設定啟用:build.gradle (Module: MDCSubject.app)
buildFeatures{
viewBinding = true
}
android:hint
屬性中指定提示文字,提示文字是當點擊時會自動縮小往上跑。<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInput_username"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_hint_username">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<!--password-->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInput_password"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_hint_password">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
...
app:endIconMode="password_toggle"
...>
<com.google.android.material.textfield.TextInputEditText
...
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
...
app:counterEnabled="true"
app:counterMaxLength="8">
...
</com.google.android.material.textfield.TextInputLayout>
在Password TextInputLayout元素上將app:errorEnabled屬性設定為true。 這將為文字欄位下方的錯誤訊息新增的錯提示。
<com.google.android.material.textfield.TextInputLayout
...
app:errorEnabled="true">
...
</com.google.android.material.textfield.TextInputLayout>
<Button
style="@style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Filled button" />
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/img_person"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintBottom_toTopOf="@id/username_input_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_person" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login_title"
app:layout_constraintBottom_toTopOf="@+id/username_input_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/img_person" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_input_text"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="50dp"
android:layout_marginVertical="30dp"
android:hint="@string/login_hint_username"
app:errorEnabled="true"
app:layout_constraintBottom_toTopOf="@id/password_input_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_input_text"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="50dp"
android:hint="@string/login_hint_password"
app:counterEnabled="true"
app:counterMaxLength="8"
app:endIconMode="password_toggle"
app:endIconDrawable="@mipmap/icon_closed_eye"
app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/username_input_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_login"
style="@style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
android:text="@string/button_text_login"
app:layout_constraintEnd_toEndOf="@id/password_input_text"
app:layout_constraintTop_toBottomOf="@id/password_input_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
點擊按鈕判斷是否顯示Error提示情境
當用戶在未輸入時點擊登入按鈕,此時顯示Error提示訊息警示,用戶有輸入時透過doOnTextChange
動態偵測用戶輸入關閉Error警示提示。
TextInputLayout
的ID.error方法設定自定義的提示訊息,清除error提示訊息時直接設定null
// Set error text
passwordInputText.error = getString(R.string.error)
// Clear error text
passwordInputText.error = null
doOnTextChange
動態偵測用戶輸入關閉error 警示提示,這個方法點進去看源碼實作其實是封裝TextWatcher的實作方法。
// Get input text
val passwordEditText = binding?.passwordEditText
passwordEditText.editText?.doOnTextChanged { text, _, _, _ ->
// Respond to input text change
}
override fun onResume() {
super.onResume()
usernameEditText?.doOnTextChanged { _, _, _, count ->
if (count > 0) {
usernameInputText?.error = null
}
}
passwordEditText?.doOnTextChanged { text, _, _, _ ->
if ( text != null && text.length >= 8) {
passwordInputText?.error = null
}
}
}
private fun inputTextValid() {
val btnLogin = binding?.btnLogin
val usernameEditText = binding?.usernameEditText
val usernameInputText = binding?.usernameInputText
val passwordEditText = binding?.passwordEditText
val passwordInputText = binding?.passwordInputText
btnLogin?.setOnClickListener {
if (isUserNameValid(usernameEditText?.text) && isPasswordValid(passwordEditText?.text)) {
// 都有輸入
usernameInputText?.error = null
passwordInputText?.error = null
(activity as NavigationHost).navigateTo(ProductGridFragment(), false)
} else {
when {
isUserNameValid(usernameEditText?.text) -> {
// 只有輸入UserName
passwordInputText?.error = "請輸入長度8碼的密碼"
}
isPasswordValid(passwordEditText?.text) -> {
// 只有輸入Password
usernameInputText?.error = "請輸入最少一個字的名字"
}
else -> {
// 都沒有輸入
usernameInputText?.error = "請輸入最少一個字的名字"
passwordInputText?.error = "請輸入長度8碼的密碼"
}
}
}
}
}
// 判斷Password欄位是否有輸入指定長度
private fun isPasswordValid(text: Editable?): Boolean {
return text != null && text.length >= 8
}
// 判斷UserNamex欄位文字不為null
private fun isUserNameValid(text: Editable?): Boolean {
return text != null && text.isNotEmpty()
}
官方預設的顯示密碼、屏蔽密碼的icon切換很奇怪,所以稍微自己改一下,讓眼睛打開時密碼明文,閉起來時密碼密文顯示
app:endIconDrawable="自定義"
<com.google.android.material.textfield.TextInputLayout
...
app:endIconMode="password_toggle"
app:endIconDrawable="@mipmap/icon_closed_eye"
...>
<com.google.android.material.textfield.TextInputEditText
...
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
,和指定顯示密碼的icon 的設定passwordInputText?.endIconDrawable
setEndIconOnClickListener
監聽點擊icon檢查PasswordTransformationMethod
方法是否密文轉換,後續判斷getTransformation
方法回傳明文為null張開眼睛icon,密文時閉眼icon,passwordInputText設定endIconDrawable
讓icon進行切換。 private fun passwordEyeVisibility() {
// // 預設顯示密碼明文
// passwordEditText?.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
// passwordInputText?.endIconDrawable = context?.let { AppCompatResources.getDrawable(it, R.drawable.ic_open_eye) }
passwordInputText?.setEndIconOnClickListener {
// 檢查是否有 PasswordTransformationMethod 密碼轉換方法
val hasPasswordTransformation = passwordEditText?.transformationMethod is PasswordTransformationMethod
if (hasPasswordTransformation) {
passwordEditText?.transformationMethod = null
} else {
passwordEditText?.transformationMethod = PasswordTransformationMethod.getInstance()
}
// 取得密碼文字來設定字定義 endIconDrawable
setCustomizeEndIcon(passwordEditText)
}
}
private fun setCustomizeEndIcon(editText: EditText?) {
// getTransformation方法回傳明文為null張開眼睛icon,密文時閉眼icon
val passwordSource = editText?.transformationMethod?.getTransformation(editText.text, editText)
if (passwordSource == null) {
// 密碼明文
passwordInputText?.endIconDrawable = context?.let {
AppCompatResources.getDrawable(it, R.drawable.ic_open_eye)
}
} else {
// 密碼密文
passwordInputText?.endIconDrawable = context?.let {
AppCompatResources.getDrawable(it, R.mipmap.icon_closed_eye)
}
}
}
這樣就完成一般的登入頁面的檢核邏輯囉~
歡迎下載程式碼
感謝您看到這邊
明天就來介紹常用的 Icon buttons。
參考資料
官方提供練習和跟著做 MDC-101 Android:Material Components (MDC) Basics (Kotlin)