EditText 中の任意の文字列に対してハイライト操作を行う
EditText に入力された文字列が存在したとして,任意の区間の文字列に対してハイライト表示したいケースを考えます. Twitterの文字数制限のUIみたいな感じです.
TextView に対してハイライト処理するサンプルはいくつもありましたが, EditText に関するサンプルが少なく,少しハマったので紹介します.
まずはコードです.
入力可能な文字列の長さが INPUT_LIMIT 以内であれば,SUBMIT ボタンが押下可能になり,
INPUT_LIMIT を超えた場合は,超えた分の文字列をハイライトし,SUBMIT ボタンが押せなくなります.
class MainActivity : AppCompatActivity() { private val binding by lazy { DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) } private val overLimitColorSpan = BackgroundColorSpan(Color.GRAY) private val defaultColorSpan = BackgroundColorSpan(Color.TRANSPARENT) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) with(binding) { submit.setOnClickListener { editText.text.clear() } editText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable?) { if (s == null) return Spannable.Factory.getInstance().newSpannable(editText.text).apply { if (s.length <= INPUT_LIMIT) { removeSpan(overLimitColorSpan) setSpan(defaultColorSpan, 0, s.length, getSpanFlags(defaultColorSpan)) } else { removeSpan(defaultColorSpan) setSpan(overLimitColorSpan, INPUT_LIMIT, s.length, getSpanFlags(overLimitColorSpan)) } }.also { val selectionStart = editText.selectionStart val selectionEnd = editText.selectionEnd editText.removeTextChangedListener(this) editText.setText(it, TextView.BufferType.SPANNABLE) editText.setSelection(selectionStart, selectionEnd) editText.addTextChangedListener(this) } updateSubmit(editText.text) } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} }) } updateSubmit("") } private fun updateSubmit(inputText: CharSequence) { with(binding) { val textDiff = INPUT_LIMIT - inputText.length val canSubmit = textDiff >= 0 textCount.setTextColor(if (canSubmit) Color.BLACK else Color.RED) textCount.text = textDiff.toString() submit.isEnabled = canSubmit } } companion object { private const val INPUT_LIMIT = 10 } }
TextWatcher は,EditText に入力されている(された / あるいはされる前の)文字列の状態を監視するクラスです.今回は 入力後の文字列の状態を把握したいので,afterTextChanged に処理を書いています.
このコードでのポイントは,setText する前に removeTextChangedListener() を呼び出していることです.
TextWatcher クラスメソッド内部で setText を呼び出すと,キーボード入力した時と同様に再び ~Changed() メソッドが呼ばれてしまいループ状態に陥ってしまうので,setText する直前に一旦自分自身の Listener を削除しておき,setText 完了後に再び addTextChangedListener() で自分自身を登録します.
BackgroundColorSpan() は文字列の背景色を変えますが,ForegroundColorSpan() を使えば文字色を変更できたりします.
簡単な サンプルプロジェクト を用意しました. 参考になれば幸いです.