Почему курсор в EditText — это боль начинающего?
Представьте: вы пишете приложение для заметок или чата. Пользователь открывает уже существующую запись, начинает печатать — и вдруг новый текст вставляется в начало или в середину строки. Знакомая ситуация? Это одна из тех маленьких, но безумно раздражающих проблем, с которыми сталкивается каждый, кто начинает изучать Android-разработку на Kotlin.
На Stack Overflow вопрос «Place cursor at the end of text in EditText» собрал тысячи просмотров и десятки ответов. Почему? Потому что интуитивного способа «просто поставить курсор в конец» в Android SDK нет. Нужно знать несколько хитростей. Сегодня мы разберем их раз и навсегда — просто, понятно и с примерами кода.
Кстати, если вы пишете не только под Android, но и, например, бэкенд на Python — на нашем сайте PythonLib вы найдете много материалов для старта. Но сегодня мы говорим о мобильной разработке.
Классическое решение: setSelection()
Главный инструмент для управления курсором в EditText — это метод setSelection(). Он принимает индекс позиции, куда нужно переместить курсор. Но чтобы поставить курсор в самый конец, нам сначала нужно узнать длину текста.
// Простой способ: берём длину текста и передаём её в setSelection
editText.setText("Привет, мир!")
editText.setSelection(editText.text.length)
Вот и всё! editText.text.length возвращает количество символов. Передаём это число в setSelection() — и курсор оказывается после последнего символа.
Но есть подвох: когда вызывать?
Новички часто пишут этот код сразу после setText() — и удивляются, что ничего не работает. Проблема в том, что EditText может ещё не успеть обновиться. Особенно это актуально, если вы устанавливаете текст программно во фрагменте или активности.
Лучшее решение — использовать post() или слушатель onTextChanged. Вот пример с post() — это гарантирует, что код выполнится после того, как View будет готов:
editText.setText("Длинный текст, который нужно отредактировать")
editText.post {
editText.setSelection(editText.text.length)
}
Теперь курсор точно окажется в конце. Этот метод — ваш спаситель в 90% случаев.
А если текст меняется динамически?
Допустим, вы пишете поле поиска, которое само подставляет подсказки. Или редактор, где пользователь может вставлять шаблоны. В таких ситуациях нужно ставить курсор в конец каждый раз, когда текст меняется программно.
Используйте TextWatcher или, что проще, просто вызывайте setSelection() после каждого изменения:
fun appendText(newText: String) {
val currentText = editText.text.toString()
editText.setText(currentText + newText)
editText.setSelection(editText.text.length)
}
Этот код сначала добавляет новый текст к существующему, а затем перемещает курсор в конец. Работает безотказно.
Хитрость для опытных: Editable и Spannable
Если вы работаете напрямую с Editable (например, получаете его из editText.text), можно использовать SpannableStringBuilder и метод setSpan(). Но для базовой задачи «курсор в конец» это избыточно. Запомните: post { setSelection(length) } — ваш базовый приём.
Почему это не работает в диалогах и фрагментах?
Ещё один частый вопрос на Stack Overflow. Если ваш EditText находится внутри AlertDialog или BottomSheetDialogFragment, код может не сработать, потому что View ещё не прикреплён к окну.
Решение — использовать onResume() или onWindowFocusChanged(). Вот пример для диалога:
// Создаём диалог с EditText
val dialog = AlertDialog.Builder(this)
.setView(editText)
.show()
// Ждём, пока диалог отобразится
editText.postDelayed({
editText.setSelection(editText.text.length)
}, 100) // 100 мс — обычно хватает
postDelayed() — это костыль, но он работает. В современных проектах лучше использовать DialogFragment с правильным жизненным циклом, но для быстрого прототипа — сойдёт.
Современная альтернатива: EditText с autofill
В Android 8+ появилась система Autofill, которая может сбрасывать позицию курсора. Если ваше приложение поддерживает автозаполнение, убедитесь, что вы вызываете setSelection() после того, как автозаполнение завершилось. Иначе пользователь выберет пароль из менеджера — и курсор улетит в начало.
Для этого можно подписаться на OnReceiveContentListener (API 31+) или использовать TextWatcher с проверкой:
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
// Если текст изменился не пользователем — ставим курсор в конец
if (!isUserTyping) {
s?.let { editText.setSelection(it.length) }
}
}
// ... остальные методы
})
Этот подход позволяет отличить программное изменение текста от ручного ввода. Удобно, но требует аккуратности.
Ошибки, которые совершают все новички
Давайте пробежимся по типичным граблям:
- Забывают про
post()— и код выполняется до отрисовки View. Результат: курсор остаётся на месте. - Используют
editText.text.lengthдоsetText()— получают длину старого текста и ставят курсор не туда. - Путают
setSelection()сselectAll()— первый ставит курсор, второй выделяет весь текст. - Пытаются поставить курсор в конец
nullтекста — если текст пустой,lengthравен 0, и курсор встанет в начало. Это нормально, но не всегда ожидаемо.
Запомните эти четыре пункта — и вы сэкономите часы дебага.
Готовый шаблон для реального проекта
Чтобы вы не копировали код по кусочкам, вот полноценная функция-расширение для EditText на Kotlin. Добавьте её в свой проект — и пользуйтесь:
fun EditText.moveCursorToEnd() {
post {
// Убеждаемся, что текст не null
val textLength = text?.length ?: 0
setSelection(textLength)
}
}
// Использование:
editText.setText("Пример текста")
editText.move