Bài 3.10. Sử dụng TextInputLayout
Nội dung bài học
- Sử dụng TextInputLayout để tạo giao diện
- Liên kết code với giao diện
- Bổ sung các giới hạn nhập liệu
- Tùy chỉnh mặt nạ cho mật khẩu
Sử dụng TextInputLayout để thiết kế giao diện đăng nhập đơn giản
- TextInputLayout là một loại view được build sẵn trong thư viện vật liệu thiết kế của Android.
- Ta sử dụng loại view này để tạo các giao diện đẹp mắt, tiện dụng khi sử dụng các thành phần kết hợp với nhau như gợi ý và label trong nhập liệu văn bản.
- Ta có thể thay thế EditText bởi TextInputLayout.
- Để sử dụng ta chỉ cần kéo phần tử TextInputLayout vào giao diện thiết kế. Tiếp đó chỉnh sửa các thành phần theo mong đợi: id, hint, hint text color, các ràng buộc…
- File strings.xml: chứa các tài nguyên văn bản được sử dụng trong ứng dụng.
<resources>
<string name="app_name">LearnTextInputLayout</string>
<string name="email_hint">Email</string>
<string name="login_title">Login</string>
<string name="password_hint">Password</string>
<string name="action_login">Login</string>
</resources>
- Sau đây là kết quả thiết kế bố cục giao diện trong file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
app:layout_constraintBottom_toTopOf="@+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email_hint"
android:inputType="textEmailAddress"
android:textColorHint="#546E7A"
tools:ignore="TextContrastCheck,VisualLintTextFieldSize" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/tv_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/login_title"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
app:layout_constraintBottom_toTopOf="@+id/textInputLayout2"
app:layout_constraintEnd_toEndOf="@+id/textInputLayout2"
app:layout_constraintStart_toStartOf="@+id/textInputLayout2" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="@+id/textInputLayout2"
app:layout_constraintStart_toStartOf="@+id/textInputLayout2"
app:layout_constraintTop_toTopOf="@+id/guideline"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_hint"
android:inputType="textPassword"
android:textColorHint="#546E7A"
tools:ignore="TextContrastCheck,VisualLintTextFieldSize" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/action_login"
app:layout_constraintEnd_toEndOf="@+id/textInputLayout"
app:layout_constraintStart_toStartOf="@+id/textInputLayout"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout"
tools:ignore="DuplicateSpeakableTextCheck" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.4" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Để mã hóa trường text đại diện cho mật khẩu nhập vào từ người dùng, ta chỉ định thuộc tính inputType trong TextInputEditText là textPassword:
android:inputType="text|textPassword"
- Để hiển thị hình con mắt cho trường nhập mật khẩu, ta chỉ định thuộc tính trong TextInputLayout là passwordToggleEnabled nhận giá trị true:
app:passwordToggleEnabled="true"
- Giao diện khi chạy ứng dụng:
- Sau khi click vào một view, hint của nó biến thành nhãn nhỏ ở trên viền của view đó:
Liên kết code với giao diện
- Ta sử dụng phương thức findViewById() để liên kết phần tử giao diện xml với Kotlin.
- Sau đây là code mẫu triển khai liên kết các view và thực hiện hành động trong MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var editTextEmail: TextInputEditText
private lateinit var editTextPassword: TextInputEditText
private lateinit var btnLogin: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
setupView()
}
private fun setupView() {
editTextEmail = findViewById(R.id.edt_email)
editTextPassword = findViewById(R.id.edt_password)
btnLogin = findViewById(R.id.btn_login)
btnLogin.setOnClickListener {
val email = editTextEmail.text.toString()
val password = editTextPassword.text.toString()
val message = "Welcome $email!nYour password: $password"
val view = findViewById<ConstraintLayout>(R.id.main)
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show()
}
}
}
- Snackbar là một thông báo xuất hiện dưới đáy màn hình để hạn chế làm gián đoạn trải nghiệm người dùng bạn nên sử dụng thay cho Toast.
Các giới hạn nhập liệu
- Nếu ta muốn giới hạn số lượng kí tự cần nhập, đếm số lượng kí tự đã nhập vào… ta có thể thực hiện bằng cách sử dụng thuộc tính counterEnabled kết hợp với thuộc tính counterMaxLengh.
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:counterEnabled="true"
app:counterMaxLength="40"
app:layout_constraintEnd_toEndOf="@+id/textInputLayout2"
app:layout_constraintStart_toStartOf="@+id/textInputLayout2"
app:layout_constraintTop_toTopOf="@+id/guideline"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_hint"
android:inputType="textPassword"
android:maxLength="40"
android:textColorHint="#546E7A"
tools:ignore="TextContrastCheck,VisualLintTextFieldSize" />
</com.google.android.material.textfield.TextInputLayout>
- Kết quả: khi nhập liệu số lượng kí tự nhập vào sẽ được hiển thị trên màn hình.
- Nếu ta nhập quá 40 kí tự, giao diện sẽ hiển thị cảnh báo bằng cách vẽ viền ô nhập liệu màu đỏ:
- Nếu chỉ muốn cho nhập liệu tối đa 40 kí tự, ta thiết lập thuộc tính maxLength=”40″ trong TextInputEditText tương ứng:
<com.google.android.material.textfield.TextInputLayout
...>
<com.google.android.material.textfield.TextInputEditText
...
android:maxLength="40"
... />
</com.google.android.material.textfield.TextInputLayout>
- Project mẫu(chỉ học viên chính thức mới tải được): nhấn vào đây
- Mật khẩu giải nén: nhấn vào đây
Tùy chỉnh mặt nạ cho mật khẩu
- Kết quả:
- Mặt nạ là kí tự dùng để thay thế cho kí tự thực tế khi bạn nhập dữ liệu mật khẩu để đảm bảo mật khẩu của bạn an toàn hơn.
- Khi muốn tùy chỉnh mặt nạ cho mật khẩu nhập liệu trong TextInputLayout, ta triển khai như sau.
- Đầu tiên tạo một lớp mặt nạ cá nhân hóa:
class PasswordMask private constructor() : PasswordTransformationMethod() {
override fun getTransformation(source: CharSequence, view: View?): CharSequence {
return PasswordCharSequence(source)
}
class PasswordCharSequence(private val source: CharSequence) : CharSequence {
override val length: Int
get() = source.length
override fun get(index: Int): Char {
return 'x'
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return source.subSequence(startIndex, endIndex)
}
}
companion object {
private val instance = PasswordMask()
fun getInstance(): PasswordMask {
return instance
}
}
}
- Bắt sự kiện icon cuối trong TextInputLayout được click:
fun TextInputLayout.addEndIconOnClickListener() {
var isVisible = false
this.setEndIconOnClickListener {
if (isVisible) {
isVisible = false
editText?.transformationMethod = PasswordMask.getInstance()
} else {
isVisible = true
editText?.transformationMethod = HideReturnsTransformationMethod()
}
val length = editText?.text?.length ?: 0
editText?.setSelection(length)
}
}
- Cập nhật lại file MainActivity như sau:
class MainActivity : AppCompatActivity() {
// ...
private lateinit var textInputLayoutPassword: TextInputLayout
// ...
private fun setupView() {
// ...
textInputLayoutPassword = findViewById(R.id.til_password)
// thiết lập transformation method cho EditText của TextInputLayout
editTextPassword.transformationMethod = PasswordMask.getInstance()
// bắt sự kiện click vào biểu tượng ẩn hiện mật khẩu
textInputLayoutPassword.addEndIconOnClickListener()
// ...
}
}
- Project hoàn chỉnh(Chỉ học viên chính thức mới tải được): nhấn vào đây
- Mật khẩu giải nén: nhấn vào đây





