Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách giới hạn ký tự nhập vào EditText bằng InputFilter trên Android

Giới thiệu

InputFilter là cơ chế Android để lọc/chỉnh sửa text nhập vào EditText real-time. Phổ biến nhất là InputFilter.LengthFilter giới hạn độ dài, nhưng có thể tạo custom filter cho: chỉ cho phép số, chữ cái, regex, block ký tự đặc biệt, format số điện thoại...

Các bước thực hiện

Bước 1: Tạo dự án mới

Android Studio → File → New Project → Empty View Activity.

Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Giới hạn 15 ký tự (LengthFilter)"
        android:textSize="16sp"
        android:layout_marginBottom="8dp" />

    <EditText
        android:id="@+id/etLimited"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Tối đa 15 ký tự"
        android:inputType="text"
        android:padding="12dp"
        android:background="@drawable/edittext_rounded" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:layout_marginBottom="8dp"
        android:text="Chỉ cho phép số (Custom Filter)"
        android:textSize="16sp" />

    <EditText
        android:id="@+id/etNumbersOnly"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Chỉ nhập số"
        android:inputType="number"
        android:padding="12dp"
        android:background="@drawable/edittext_rounded" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:layout_marginBottom="8dp"
        android:text="Block ký tự đặc biệt (Regex Filter)"
        android:textSize="16sp" />

    <EditText
        android:id="@+id/etNoSpecialChars"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Không ký tự đặc biệt"
        android:inputType="text"
        android:padding="12dp"
        android:background="@drawable/edittext_rounded" />
</LinearLayout>

Bước 3: Custom InputFilter classes

1. LengthFilter (có sẵn):

// Giới hạn 15 ký tự
editText.filters = arrayOf(InputFilter.LengthFilter(15))

2. Chỉ cho phép số (0-9):

object NumbersOnlyFilter : InputFilter {
    override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
        val input = source.subSequence(start, end).toString()
        return if (input.matches(Regex("^[0-9]*$"))) null else ""
    }
}

// Sử dụng
etNumbersOnly.filters = arrayOf(NumbersOnlyFilter, InputFilter.LengthFilter(10))

3. Block ký tự đặc biệt (chỉ cho chữ, số, space):

object NoSpecialCharsFilter : InputFilter {
    private val allowedRegex = Regex("^[a-zA-Z0-9\\s]*$")
    
    override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
        val input = source.subSequence(start, end).toString()
        return if (allowedRegex.matches(input)) null else ""
    }
}

4. Chỉ cho phép chữ cái (không số):

object LettersOnlyFilter : InputFilter {
    private val allowedRegex = Regex("^[a-zA-Z\\s]*$")
    
    override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
        val input = source.subSequence(start, end).toString()
        return if (allowedRegex.matches(input)) null else ""
    }
}

5. Format số điện thoại (ví dụ: 0xxx-xxx-xxx):

class PhoneNumberFilter : InputFilter {
    override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
        val currentText = dest.toString()
        val newText = currentText.substring(0, dstart) + source.subSequence(start, end) + currentText.substring(dend)
        
        // Chỉ cho phép số và tự động format
        val digitsOnly = newText.replace(Regex("[^0-9]"), "")
        
        // Logic format ở đây (ví dụ thêm dấu gạch ngang)
        // Trả về null để cho phép, hoặc trả về string đã format
        return null // Để EditText tự handle, hoặc xử lý phức tạp hơn
    }
}

Bước 4: Áp dụng trong MainActivity.kt

package com.example.inputfilterdemo

import android.os.Bundle
import android.text.InputFilter
import android.text.Spanned
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.inputfilterdemo.databinding.ActivityMainBinding
import java.util.regex.Regex

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 1. LengthFilter - giới hạn 15 ký tự
        binding.etLimited.filters = arrayOf(InputFilter.LengthFilter(15))
        binding.etLimited.setOnKeyListener { _, _, _ ->
            if (binding.etLimited.length() >= 15) {
                Toast.makeText(this, "Đã đạt tối đa 15 ký tự", Toast.LENGTH_SHORT).show()
            }
            false
        }

        // 2. Chỉ số
        binding.etNumbersOnly.filters = arrayOf(NumbersOnlyFilter, InputFilter.LengthFilter(10))

        // 3. Không ký tự đặc biệt
        binding.etNoSpecialChars.filters = arrayOf(NoSpecialCharsFilter, InputFilter.LengthFilter(50))
    }
}

// Custom Filters
object NumbersOnlyFilter : InputFilter {
    override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
        val input = source.subSequence(start, end).toString()
        return if (input.matches(Regex("^[0-9]*$"))) null else ""
    }
}

object NoSpecialCharsFilter : InputFilter {
    private val allowedRegex = Regex("^[a-zA-Z0-9\\sÀ-ỹà-ỹĐđ]*$") // Hỗ trợ tiếng Việt
    
    override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
        val input = source.subSequence(start, end).toString()
        return if (allowedRegex.matches(input)) null else ""
    }
}

InputFilter vs TextWatcher

Đặc điểmInputFilterTextWatcher
Thời điểm chạy
Trước khi text thay đổi (pre-filter)Sau khi text đã thay đổi
Có thể sửa textCó (trả về CharSequence mới)Có (nhưng dễ gây loop vô tận)
PerformanceTốt (chạy trên UI thread, nhanh)Tốt
Dùng choValidation, format, block charReact to change, live search, count chars

XML Attribute: android:maxLength

Đơn giản nhất cho giới hạn độ dài (không cần code):

<EditText
    android:maxLength="15"
    android:inputType="text" />
  • Tự động enforce tại framework level.
  • Không cho phép custom logic (chỉ số, regex...).
  • Không hiển thị toast warning khi đạt giới hạn.

Lưu ý quan trọng

  • InputFilter chạy trên **UI thread** - code trong filter phải **rất nhanh**, tránh regex phức tạp, I/O, network.
  • Trả về null = cho phép input gốc. Trả về "" (empty) = block input. Trả về string khác = replace input.
  • Kết hợp nhiều filter: editText.filters = arrayOf(filter1, filter2, LengthFilter(10)) - chạy theo thứ tự.
  • Copy/Paste: InputFilter cũng filter nội dung paste vào.
  • Emoji: Regex \\s không match emoji. Cần handle riêng nếu cho phép/block emoji.
  • Tiếng Việt: Regex [a-zA-Z] không bao gồm chữ có dấu. Dùng [\\p{L}] (Unicode letter) hoặc range À-ỹà-ỹĐđ.