Android view above keyboard

davidwparker / layout.xml

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

RelativeLayout
xmlns:android= » http://schemas.android.com/apk/res/android «
android:orientation= » vertical «
android:layout_height= » fill_parent «
android:layout_width= » fill_parent » >
ScrollView
android:id= » @+id/scrollview «
android:layout_height= » fill_parent «
android:layout_width= » fill_parent «
android:layout_above= » @+id/m_table_menu » >
. stuff here .
ScrollView >
TableLayout
android:id= » @+id/m_table_menu «
android:layout_height= » wrap_content «
android:layout_width= » wrap_content «
android:layout_alignParentBottom= » true «
android:padding= » 0dp «
android:background= » #000000 «
android:stretchColumns= » * » >
. bottom row floating stuff here .
TableLayout >
RelativeLayout >

You can’t perform that action at this time.

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.

Источник

Animating your keyboard (part 1)

New WindowInsets APIs for checking the keyboard (IME) visibility and size

New in Android 11 is the ability for apps to create seamless transitions between the on screen keyboard being opened and closed, and it’s all powered by lots of improvements to the WindowInsets APIs in Android 11.

Here you are two examples of it in action on Android 11. It has been integrated into the Google Search app, as well as the Messages app:

So let’s take a look at how you can add this sort of experience to your apps. There are three steps:

  1. First, we need to go edge-to-edge.
  2. The second step is for apps to start reacting to inset animations.
  3. And the third step is by apps taking control of and driving inset animations, if it makes sense for your app.

Each of these steps follow on from each other, so we’ll cover each in separate blog posts. In this first post, we’ll cover going edge-to-edge, and the related API changes in Android 11.

Going edge-to-edge

Last year we introduced the concept of going edge to edge, as a way for apps to make the most of the new gestural navigation in Android 10:

Gesture Navigation: Going edge-to-edge (I)

With Android Q, a new system navigation mode has been added, allowing the user to navigate back, and to the home screen…

As a quick re-cap, going edge to edge results in your app drawing behind the system bars, like you can see on the left.

To quote myself from last year:

By going edge-to-edge, apps will instead be laid out behind the system bars. This is to allow your app content to shine through to create a more immersive experience for your users.

So what has going edge to edge got to do with the keyboard?

Well going edge to edge is actually more than just drawing behind the status and navigation bars. It’s apps taking responsibility for handling those pieces of system UI which might overlap with the app.

The two obvious examples being the status bar and navigation bar, which we mentioned earlier. Then we have the on-screen-keyboard, or IME as it is sometimes referred to; it’s just another piece of system UI to be aware of.

How do apps go edge to edge?

If we flash back to our guidance from last year, going edge to edge is made up of 3 tasks:

  1. Change system bar colors
  2. Request to be laid out fullscreen
  3. Handle visual conflicts

We’re going to skip the first task, because nothing has changed there since last year. The guidance for steps 2 & 3 has been updated with some changes in Android 11. Let’s take a look.

#2: Request to be laid out fullscreen

For the second step, apps needed to use the systemUiVisibility API with a bunch of flags, to request to be laid out fullscreen:

If you were using this API and have updated your compile SDK version to 30, you’ll have seen that all of these APIs are now deprecated.

They’ve been replaced with a single function on Window called setDecorFitsSystemWindows() :

Instead of the many flags, you now pass in a boolean: false if apps want to handle any system window fitting (and thus go fullscreen).

Читайте также:  Роад раш для андроид

We also have a Jetpack version of the function available in WindowCompat , which was released recently in androidx.core v1.5.0-alpha02 .

So that’s the 2nd step updated.

#3: Handling visual conflicts

Now let’s look at the third step: avoiding overlaps with the system UI, which can be summarised as using the window insets to know where to move content to, to avoid conflicts with the system UI. On Android, insets are represented by the WindowInsets class, and WindowInsetsCompat in AndroidX

If we take a look at WindowInsets before the updates from API 30, the most common inset type to use is the system window insets. These cover the status and navigation bars, and also the keyboard when it is open.

To use WindowInsets , you would typically add an OnApplyWindowInsetsListener to a view, and handle any insets which are passed to it:

Here we’re fetching the system window insets, and then updating the view’s padding to match, which is a very common use case.

There are a number of other inset types available, including the recently added gesture insets from Android 10:

Similar to the systemUiVisibility API, much of the WindowInsets APIs have been deprecated, in favor of new functions to query the insets for different types:

  • getInsets(type: Int) which will return the visible insets for the given types.
  • getInsetsIgnoringVisibility(type: Int) which returns the insets, regardless of whether they’re visible or not.
  • isVisible(type: Int) which returns true if the given type is visible.

We just mentioned ‘types’ a lot there. These are defined in the WindowInsets. Type class as functions, each returning an integer flag. You can combine multiple types, using a bitwise OR to query for combined types, which we’ll see in a minute.

All of these APIs have been backported to WindowInsetsCompat in AndroidX Core, so you can safely use them back to API 14 (see the release notes for more information).

So if we go back to our example from before, to update it to the new APIs, they become:

The IME type ⌨️

Now the keen eyed 👀 among may have been looking at this list of types, and been looking at one type in particular: the IME type.

Well we can finally answer this StackOverflow question, from over 10 years ago (fashionably late), about how to check the visibility of the keyboard. 🎉

How to check visibility of software keyboard in Android?

To get the current keyboard visibility, we can fetch the root window insets, and then call the isVisible() function, passing in the IME type.

Similarly if we want to find out the height, we can do that too:

If we need to listen to changes to the keyboard, we can use the normal OnApplyWindowInsetsListener , and use the same functions:

Hiding/showing the keyboard

Since we’re on a roll of answering StackOverflow questions, how about this one from 11 years ago, of how to close the keyboard.

How do you close/hide the Android soft keyboard?

Here we are going to introduce another new API in Android 11, called WindowInsetsController .

Apps can get access to a controller from any view, and then show or hide the keyboard by calling either show() or hide() , passing in the IME type:

But hiding and showing the keyboard isn’t all that the controller can do…

WindowInsetsController

Earlier we said that some of the View.SYSTEM_UI_* flags have been deprecated in Android 11, replaced with a new API. Well there were a number of other View.SYSTEM_UI flags available, related to changing the system UI appearance or visibility, including:

  • View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
  • View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  • View.SYSTEM_UI_FLAG_LAYOUT_STABLE
  • View.SYSTEM_UI_FLAG_LOW_PROFILE
  • View.SYSTEM_UI_FLAG_FULLSCREEN
  • View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
  • View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
  • View.SYSTEM_UI_FLAG_IMMERSIVE
  • View.SYSTEM_UI_FLAG_VISIBLE
  • View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
  • View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR

Similar to the others, these have also been deprecated too in API 30, replaced with APIs in WindowInsetsController .

Instead of going through the migration for all of these flags, we’ll cover a few common scenarios and see how to update them:

Immersive modes

Here you can see a drawing app, which hides the System UI to maximise the space available for drawing:

To implement that using WindowInsetsController we use the hide() and show() functions like before, but this time we pass in the system bars type:

The app also uses immersive mode, allowing the user to swipe the system bars back in once hidden. To implement this using WindowInsetsController we change the hide and show behaviour to BEHAVIOR_SHOW_BARS_BY_SWIPE :

Similarly, if you were using sticky immersive mode, this is implemented using the BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE instead:

Status bar content color

The next scenario is around the status bar content color. Here you see two apps:

On the left the app has a dark status bar background, with light content like the time and icons. But if we instead want a light status bar background with dark content, like the right, we can use WindowInsetsController too.

To do that, we can use the setSystemBarsAppearance() function, passing in the APPEARANCE_LIGHT_STATUS_BARS value:

If you instead want to set a dark status bar, by passing in 0 instead to clear the value.

Note: you could implement this in your theme instead, by setting the android:windowLightStatusBar attribute. This might be preferable if you know the value won’t change.

Similarly, the APPEARANCE_LIGHT_NAVIGATION_BARS flag is available which provides the same functionality for the navigation bars.

Читайте также:  Что делать если синтаксическая ошибка android

WindowInsetsController in AndroidX?

Unfortunately a Jetpack version of this API does not exist yet, but we are working on it. Stay tuned.

Going edge-to-edge: ✔️

So that’s the first step done. In the next blog post we’ll investigate the second step: apps reacting to inset animations.

Источник

Как подружить Custom View и клавиатуру

Введение

«МойОфис» работает на большинстве современных платформ: это Web-клиент, настольные версии приложения для Windows, MacOS и Linux, а также мобильные приложения для iOS, Android, Tizen. И если в разработке компьютерных приложений уже давно есть основные правила подхода к дизайну интерфейсов, то при создании приложений для мобильных устройств требуется отдельная проработка многих особенностей.

При разработке текстового редактора мы отказались от стандартного EditText и сделали свою реализацию компонента для ввода и форматирования текста, основываясь на компоненте TextureView. Стандартный механизм не давал нам возможности добавлять таблицы, изображения, применять стили, цвета, работу со списками, отступами и многое другое. Использование своего компонента дает нам гибкость при добавлении новой функциональности и позволяет оптимизировать производительность компонента.

Одна из задач компонента — дать пользователю возможность вводить данные с клавиатуры, редактировать текст, применять возможности автозамены и корректировки текста. Далее будет рассказано, как реализовать кастомный элемент, взаимодействующий с клавиатурой, получить введенный текст и отправить изменения в клавиатуру. За рамки данной статьи выходит создание custom keyboard (можно почитать тут, тут или тут).

CustomView и клавиатура

Весь процесс взаимодействия View-элементов с клавиатурой происходит через интерфейс InputConnection. В Android SDK уже существует базовая реализация — BaseInputConnection, позволяющая получать события от клавиатуры, обрабатывать их и взаимодействовать с интерфейсом Editable, который является результатом полученных данных для компонента.

Но начнем по порядку. Чтобы связаться с клавиатурой, прежде всего необходимо у разрабатываемого компонента определить реализацию интерфейса для взаимодействия — подписаться на события от клавиатуры. Помимо этого, в саму клавиатуру можно передать ряд настроек, которые влияют на тип клавиатуры и ее поведение. В итоге нужно переопределить метод компонента — onCreateInputConnection(. ), возвращающий реализацию. В качестве атрибута приходят параметры клавиатуры, которые можно модифицировать.

В указанном примере метод возвращает базовую реализацию интерфейса. А в EditorInfo указываются параметры для клавиатуры. О том, какие именно, можно посмотреть в документации тут. Например, с помощью inputType можно указать цифровой или текстовый ввод для клавиатуры.

Стоит отметить, что если в inputType передать значение TYPE_NULL, то у софтверной клавиатуры отключается автокомплит и все события будут приходить во View.onKeyDown (так же как и при работе с физической).

Если возникает необходимость изменить конфигурацию клавиатуры, когда она уже показывается (например, изменился тип ввода), то следует вызвать метод restartInput. В этом случае onCreateInputConnection будет вызван повторно и в EditorInfo можно будет передать новые значения. Но стоит учитывать, что при этом произойдет пересоздание и самого InputConnection.

Следующий шаг — вызов метода setFocusableInTouchMode(true) (например, из метода onFinishInflate() или с помощью атрибута в разметке). С его помощью компонент указывает, что может перехватывать фокус. А взаимодействие с клавиатурой может быть только у того элемента, который сейчас в фокусе. Если этого не сделать, метод onCreateInputConnection вызываться не будет.

Дополнительно стоит отметить, что при таче на компонент нужно инициировать открытие клавиатуры. Автоматически это не произойдет, поэтому об этом нужно позаботиться. Один из вариантов, как это можно сделать:

Последнее, что нужно выполнить, — переопределить метод onCheckIsTextEditor и возвращать значение TRUE (по умолчанию false). Согласно документации — является подсказкой для системы, чтобы автоматически показать клавиатуру на экране.

В итоге, чтобы наладить взаимодействие с клавиатурой, необходимо:

1. Переопределить метод onCreateInputConnection, указав реализацию интерфейса взаимодействия, а также параметры для клавиатуры.
2. Вызвать setFocusableInTouchMode(true) при инициализации компонента.
3. Вызывать imm.showSoftInput(. ) для отображения клавиатуры при таче на компонент.
4. Возвращать TRUE в методе onCheckIsTextEditor.

Сейчас был описан механизм, как начать получать события от клавиатуры. Далее будет рассказано, как эти события обрабатывать.

Input Connection

Ранее уже было отмечено, что в Android SDK существует базовая имплементация интерфейса InputConnection — BaseInputConnection. В ней добавлены основная логика, позволяющая взаимодействовать с клавиатурой, и делегирование полученных событий в объект Editable, с которым и должен в будущем работать кастомный компонент. Для разработки рекомендуется отнаследоваться от него и переопределить метод getEditable(), передавая туда свою реализацию Editable.

Более подробно про интерфейс Editable будет рассказано чуть ниже. Пока же хочется рассмотреть некоторые методы InputConnection, которые могут оказаться кому-то полезными. С полной документацией по всем методам можно ознакомиться тут. При этом стоит отметить, что последовательность вызовов методов, значение параметров, с которыми они вызываются, зависит от реализации клавиатуры, используемой в момент ввода на устройстве, и могут отличаться.

beginBatchEdit() и endBatchEdit()

Информирует о начале и окончании набора действий с клавиатуры. Например, с клавиатуры в режиме Т9 вводится пробел после текста. В этом случае произойдет последовательно вызов finishComposingText() и commitText() в рамках одного batch-события. То есть последовательность будет примерно такой:

beginBatchEdit
finishComposingText
beginBatchEdit
endBatchEdit
commitText
beginBatchEdit
endBatchEdit
endBatchEdit

Обратите внимание, что допускается вложенность batch’ей. То есть необходимо считать количество начавшихся вызовов и количество закончившихся, чтобы определить, закончился ли процесс или нет. К примеру, можно заглянуть в реализацию EditableInputConnection — реализация для TextView, где как раз происходит инкрементация при каждом begin и декремент при end.

Важно! До тех пор пока не закончился batch, не рекомендуется отправлять события от редактора в клавиатуру (например, изменение положения курсора).

Читайте также:  Фильтры вско для андроид

setComposingText()

Метод вызывается в тех случаях, когда с клавиатуры вводится так называемый составной текст. Например, голосовой ввод, ввод текста в режиме автозамены и т.д. То есть тот текст, который может быть откорректирован/заменен с клавиатуры.

Пример ввода слова test:

setComposingText t 1
setComposingText te 1
setComposingText tes 1
setComposingText test 1

В качестве параметров метода приходит новое значение составного текста. Далее это значение передается в Editable и отмечается с помощью специальных spans (метка начала и окончания composing text). При каждом новом составном тексте предыдущий удаляется согласно отмеченным spans. Таким образом происходит автозамена текста.

finishComposingText()

Тут все довольно просто, метод будет вызван в тот момент, когда клавиатура решает, что текст далее не будет корректироваться и пользователем введена финальная версия. При этом в Editable удаляется вся информация о composing Text.

commitText()

Вызывается метод с параметрами CharSequence text, int newCursorPosition, когда добавляемый текст утвержден, то есть его корректировка не планируется. Например, с клавиатуры выбирается suggest. В этом случае приходит значение текста, которое должно быть добавлено на место текущего курсора или вместо compose-текста, если он был. А также информация по новому положению курсора для редактора. Значение > 0 (например, 1) будет означать положение курсора в конце нового текста. Любое другое значение — в начале.

deleteSurroundingText()

Метод вызывается с 2 параметрами — int beforeLength, int afterLength и информирует о том, что необходимо удалить часть текста до текущего положения курсора и после. При этом если есть выделение текста, то данные символы игнорируются.

Например, данный метод вызывается, когда пользователь в режиме Т9 нажимает на текст в редакторе и заменяет выбранное слово из списка подсказок.

Реализация Editable

BaseInputConnection тесно взаимодействует с интерфейсом Editable, реализацию которого нужно передавать в методе getEditable(). Все методы интерфейса можно разделить на 3 типа:

* модификация и получение текста;
* работа со spans;
* применение фильтров.

Если заглянуть в реализацию TextView, то видно, что метод getText() возвращает Editable. А точнее, реализацию SpannableStringBuilder, являющуюся основной и готовой для хранения и модификации текста, работы с фильтрами и со spans.

Если по каким-то причинам стандартная реализация не подходит, можно реализовать свою. Основным методом работы с изменением текста является replace(. ). Все insert, append, delete и тд. вызывают замену текста определенного участка на новый. Но стоит не забывать, что перед заменой надо применить набор фильтров для текста. Далее важно корректно реализовать работу со spans, которые позволяют вешать метки: положение курсора, выделение текста, composing region (начало и конец региона для автозамены) и т.д.

TextWatcher

Допустим, что нас устроят стандартная реализация взаимодействия с клавиатурой и стандартный Editable. Теперь вернемся к разрабатываемому компоненту и подпишемся на изменения в Editable. Делается это довольно просто, добавлением специального spans с объектом TextWatcher.

После этого при любом изменении editable будут приходить уведомления. Важно указать флаг — SPAN_INCLUSIVE_INCLUSIVE, позволяющий не удалять слушателя при вызове метода clearSpans() (например, вызывается, когда происходит finishComposingText).

Получая уведомления, можно взять следующую информацию:

* mEditable.toString() вернет весь текст. Его можно отображать на UI — это то, что введено пользователем.
* Методы класса Selection нужны для получения информации о курсоре и выделении.

setText()

Предположим у компонента есть метод setText(). Нужно обновить значение Editable разрабатываемого компонента и уведомить клавиатуру о том, что предыдущий текст в буфере клавиатуры не валиден. Делается это созданием нового объекта Editable и вызовом метода restartInput.

Изменение позиции курсора

Для полноценного взаимодействия с клавиатурой необходимо добавить поддержку позиционирования курсора. В случае ввода текста в методах setComposingText() и commitText() приходит значение в параметре cursorPosition, который определяет, в начале или в конце добавляемого текста будет располагаться курсор. В случае реализации через BaseInputConnection нет необходимости заботиться о положении курсора, логика уже реализована внутри. Достаточно воспользоваться методом Selection.getSelectionStart и getSelectionEnd, чтобы узнать позицию.

Довольно важно добавлять обратную поддержку изменения курсора. Например, если разрабатываемый компонент умеет отображать курсор и у него есть возможность менять его положение, то при каждом изменении следует уведомлять клавиатуру. Если этого не делать, то последующий ввод текста с клавиатуры будет игнорировать измененное положение. Также некорректно будет работать замена слов в режиме Т9.

Для оповещения используется метод updateSelection, куда передается информация о новом положении пинов. Стоит не забывать, что до тех пор, пока не закончится batch в InputConnection, уведомление отправлять не стоит.

Замена слова с помощью T9

Теперь небольшой пример, когда пользователь использует клавиатуру с Т9. Допустим, что в редакторе введено несколько слов и происходит нажатие на одно из них. При работе со стандартным EditText в клавиатуре будет показана подсказка, при нажатии на которую слово полностью заменится.

С EditText клавиатура получает информацию о новом положении курсора, отправит обратно в редактор информацию о текущем выбранном composing text через метод setComposingRegion, тем самым помечая слово, с которым будет дальше работать, а именно слово под курсором. Теперь если выбрать одну из подсказок, то вызовется метод: удалить текущее слово и вставить новое.

Но как показывают исследования, одного вызова метода updateSelection недостаточно. Слово не заменяется, а добавляется в текущее положение по курсору, так как не вызывается setComposingRegion.

Чтобы найти решение, стоит посмотреть на последовательность вызываемых методов InputMethodManager при работе с EditText, которая получается примерно такой:

Теперь если добавить указанные строчки в обработку нажатия на компонент, то клавиатура начнет вызывать setComposingRegion и замена текста будет проходить корректно.

Источник

Оцените статью