- Handling Android Back Button Events in React Native with Custom Components
- React Native and Android’s Back Button
- React Side Effect
- Implementing our Desired Android Behavior
- In conclusion
- Клавиатура и аппаратные кнопки
- Аппаратные и клавиатурные клавиши
- Кнопка Back: Вы уверены, что хотите выйти из программы?
- Двойное нажатие на кнопку Back
- Кнопка Home
- Обработка кнопки Menu
- Другие кнопки
- Прячем клавиатуру
- Изменить вид клавиатуры для EditText
- Переопределяем кнопку Enter
- Интерфейс OnKeyListener
- Сдвигаем активность
- Узнать выбранный язык на клавиатуре
Handling Android Back Button Events in React Native with Custom Components
As you build a React Native app across multiple platforms, one of the key differences between your iPhone and Android apps is that Android phones have a separate system back button that users can touch at any time.
As an Android user moves through your app and goes deeper and deeper into new scenes, the expected behavior is that a press on the back button returns the user to the previous screen, unless they are on the first screen, where a press on the back button means they exit the app.
The system Back button is used to navigate, in reverse chronological order, through the history of screens the user has recently worked with. It is generally based on the temporal relationships between screens, rather than the app’s hierarchy.
React Native and Android’s Back Button
The default React Native behavior for the back button is that pressing the button will exit the app.
React Native provides a BackAndroid module that gives developers control of back button behavior. This module is in charge of registering callbacks to decide what to do when the back button is pressed. When a user taps on the back button, the app runs through every registered event listener and only stops if one callback returns true . If no callbacks return true , the button press quits your app.
To implement the Back Button’s reverse chronological screen navigation as discussed by the Android Developer Guide, for example, a developer could write a callback that checks the current number of routes before deciding whether or not to exit the app:
This works well enough if this function is the only registered callback. But what happens when you need to add more and more event listeners to cover the business logic for different use cases? After all, the same Android developer guide writes that the back button can be used to dismiss dialogs and popups or to dismiss contextual action bars.
Imagine a screen that performs an expensive calculation, or multiple network calls, or a payment page where the user has submitted credit card information and you are waiting for a reply from the server. If someone presses the back button here, you may want to handle this press differently.
React Native’s out of the box experience means that each of these situations requires adding a new event listener when it is necessary and manually removing it when it is not. It becomes mentally taxing to keep a list in your head of which listeners are mounted and whether the functions return true values or false values.
On top of that, forgetting to remove a listener could lead to strange bugs! Without enough defensive coding, you could end up running your back-button-press on credit-card-submission function even if that view is no longer visible.
An easier way to think about this problem and avoid these bugs is by reframing the problem: instead of adding new listeners when your business logic changes, only ever mount one listener, and change that function when your business logic changes.
React Side Effect
Handling back button behavior is a perfect use case for Dan Abramov’s React Side Effect library. React Side Effect allows programmers to create special higher-order components that are designed to perform side effects when they are rendered and updated by your app.
One popular package that is implemented with React Side Effect is React Document Title, which allows programmers to change browser window’s title by passing a property to a component, like so:
React Side Effect fits with React’s component-based architecture because it allows wrapping of imperative APIs (like React Native’s BackAndroid or changing a document’s title) into components that better fit React’s programming model and abstractions.
The basic idea with React Side Effect is to create a React component that accepts any arbitrary props you define. This component is then wrapped with the withSideEffect function and two or three additional functions you implement. These functions are called, one after the other, every time your app re-renders and then emit a side effect based on the logic you provide. Here are their conventional names and behavior:
- reducePropsToState — The argument passed to this function is an array of the properties to all of the mounted instances of your component
- handleStateChangeOnClient — This function is called with the return value of reducePropsToState and is used to emit a side effect
- mapStateOnServer (optional) — This function behaves like handleStateChangeOnClient but is invoked on the server if your React app uses server rendering
A simple implementation of DocumentTitle may look like this:
Programmers can implement these functions in any manner they choose: some use cases could lend themselves to aggregating properties from many components together into one value. Other use cases, like this one, are only concerned with the value provided to the last component.
In either case, the return value of reducePropsToState is passed as the argument to handleStateChangeOnClient , where we use this argument to change the title of the document.
Implementing our Desired Android Behavior
Let us take a look at a more complicated example. Here are the basic rules: no matter how many AndroidBackButton components are rendered by our app at once, only the function provided as a property to the innermost rendered component should be executed. The return value of this function determines whether or not an Android button press quits the app.
This is implemented by ensuring that there is only ever one callback registered with React Native’s BackAndroid library. This callback will in turn call the function provided to the innermost AndroidBackButton instance. We will use React Side Effect to update the function that is called inside the event listener.
If you try this code as-is in your app, you may be surprised to find that it does not fully work. Namely, the reducePropsToState function executes while handleStateChangeOnClient does not. Why not?
The answer has to do with React Native’s execution environment and the way that React Side Effect determines how to invoke functions. Because React Side Effect can run on both the client and server, the library checks to see which environment it is in by asking if it has access to the DOM when it is time to emit a change.
React Native, as we know, does not render a DOM! Because React Native’s environment does not satisfy the conditions of this check, we cannot place our function as the second handleStateClientOnClient argument and must instead position it as the optional third argument, the rather confusingly (for us) named mapStateOnServer .
Despite the name of the function and the implication in a request-response world that this function is only called once, this function is called whenever a component is mounted, unmounted, or changed. We can rewrite the bottom of our above example as this:
With a working version of our component, we can now control back button behavior by rendering the component anywhere in our app’s view hierarchy. We can define even define a base behavior (for instance, navigating back unless the user is on the first screen of your app) and then override that behavior in certain contexts. All without manually juggling the addition and subtraction of event listeners!
In conclusion
I hope you learned something about React Side Effect and how higher order components can let you take advantage of React’s component abstraction to wrap imperative APIs and deal with changing data in your programs.
I’m open sourcing this component as “react-native-android-back-button.” View the source here, install it with npm install react-native-android-back-button , and let me know what you think!
Источник
Клавиатура и аппаратные кнопки
Аппаратные и клавиатурные клавиши
Обработка аппаратных клавиш и клавиатуры имеет следующие методы
- onKeyDown() — вызывается при нажатии любой аппаратной клавиши;
- onKeyUp() — вызывается при отпускании любой аппаратной клавиши;
Кроме клавиш, есть ещё другие методы обработки пользовательского ввода (здесь не рассматриваются):
- onTrackballEvent() — срабатывает при движениях трекбола;
- onTouchEvent() — обработчик событий сенсорного экрана, срабатывает при касании, убирания пальца и при перетаскивании.
Чтобы ваши компоненты и активности реагировали на нажатия клавиш, переопределите обработчики событий onKeyUp() и onKeyDown():
Параметр keyCode содержит код клавиши, которая была нажата; сравнивайте его со статическими кодами клавиш, хранящимися в классе KeyEvent, чтобы выполнять соответствующую обработку.
Параметр KeyEvent также включает в себя несколько методов: isAltPressed(), isShiftPressed() и isSymPressed(), определяющих, были ли нажаты функциональные клавиши, такие как Alt, Shift или Sym. Статический метод isModifierKey() принимает keyCode и определяет, является ли нажатая клавиша модификатором.
Кнопка Back: Вы уверены, что хотите выйти из программы?
Кнопка Back (Назад) закрывает приложение, точнее текущую активность, но если приложение состоит из одной активности, то это равносильно закрытию всего приложения. В большинстве случаев вам нет никакого дела до неуклюжего пользователя, который по ошибке нажал на кнопку «Back» вместо кнопки Подарить разработчику миллион. Но, если ваша программа, будучи запущенной на телефоне пользователя, потихоньку списывает деньги клиента в счёт Фонда голодных котов, то нужно дать ему шанс задуматься и вывести диалоговое окно с вопросом: «А действительно ли вы хотите выйти из программы?»
Чтобы реализовать такую задачу, нужно переопределить поведение кнопки «Back» через метод активности onBackPressed() следующим образом:
Данный метод появился в Android 2.0. Для более ранних версий использовался стандартный код обработки onKeyDown():
Двойное нажатие на кнопку Back
Другой вариант — выход из приложения при двойном нажатии на кнопку «Back». Удобно в тех случаях, когда считаете, что пользователь может случайно нажать на кнопку, например, во время активной игры. Приложение закроется, если пользователь дважды нажмёт на кнопку в течение двух секунд.
Кнопка Home
Можно отследить нажатие кнопки Home через метод активности onUserLeaveHint():
Обработка кнопки Menu
У телефона, кроме кнопки «Back», есть ещё кнопка «Menu» для вызова команд меню (на старых устройствах). Если необходимо обрабатывать нажатия этой кнопки (например, управление в игре), то используйте следующий код (обычное и долгое нажатие):
Должен заметить, что длинное нажатие трудно уловить, так как обычное нажатие постоянно подавляет это событие.
Другие кнопки
Ну на самом деле можно отслеживать не только нажатие кнопки Меню, но и кнопку Поиска и кнопки увеличения громкости.
Обратите внимание, что для кнопки громкости возвращаем false, т.е. мы не переопределяем поведение кнопки, а оставляем её на усмотрение системы.
Пример работы с кнопками громкости можно посмотреть в статье Рингтоны. Управление громкостью
По такому же принципу работает метод onKeyUp(). Метод onKeyLongPress() можно использовать, если в методе onKeyDown() был задействован метод event.startTracking(), отслеживающий поведение кнопки. В нашем примере мы отслеживали кнопку Volume_Up.
Прячем клавиатуру
Бывает так, что при запуске активности сразу выскакивает клавиатура. Если такое поведение не нравится, то пропишите в манифесте нужное значение у атрибута android:windowSoftInputMode (см. ниже).
В некоторых случаях хочется убрать клавиатуру с экрана, не нажимая кнопку «Back», а программно. В одном моём приложении, где было много текстовых полей, я воспользовался следующим кодом при щелчке кнопки:
Код так выглядит, если писать его в Activity. Если расположить его в другом классе, экземпляр Activity нужно передать туда как параметр и вызывать методы как activity.getApplicationContext(), где activity — экземпляр Activity.
Можно избавить компонент от фокуса:
Чтобы принудительно показать клавиатуру, используйте следующий код:
Кстати, повторный вызов метода закроет клавиатуру. Указанный способ не требует наличия элементов View.
Если продолжить тему показа клавиатуры, то может возникнуть следующая ситуация. Допустим у вас есть DialogFragment с EditText. При выводе диалогового окна вам нужно установить фокус на EditText и показать клавиатуру:
Либо используйте тег для нужного EditText.
Изменить вид клавиатуры для EditText
Когда элемент EditText получает фокус, то появляется клавиатура. Можно установить нужный вид клавиатуры через атрибут InputType или программно через метод setInputType():
TYPE_CLASS_DATETIME — дата и время
TYPE_CLASS_NUMBER — цифры
TYPE_CLASS_TEXT — буквы
Переопределяем кнопку Enter
Кроме атрибута InputType можно также использовать атрибут android:imeOptions в компоненте EditText, который позволяет заменить кнопку Enter на клавиатуре на другие кнопки, например, Next, Go, Search и др. Возможны следующие значения:
- actionUnspecified: Используется по умолчанию. Система сама выбирает нужный вид кнопки (IME_NULL)
- actionGo: Выводит надпись Go. Действует как клавиша Enter при наборе адреса в адресной строке браузера (IME_ACTION_GO)
- actionSearch: Выводит значок поиска (IME_ACTION_SEARCH)
- actionSend: Выводит надпись Send (IME_ACTION_SEND)
- actionNext: Выводит надпись Next (IME_ACTION_NEXT)
- actionDone: Выводи надпись Done (IME_ACTION_DONE)
Чтобы увидеть все варианты воочию, можете создать несколько текстовых полей и переключаться между ними:
Чтобы реагировать на нажатия разных состояний кнопки Enter, необходимо реализовать интерфейс TextView.OnEditorActionListener. Небольшой пример:
В нашем примере если пользователь ищет что-то, не связанное с котом, то кнопка поиска не будет выполнять желание владельца устройства.
Также можно поменять текст на кнопке с помощью атрибута android:imeActionLabel:
Текст на кнопке поменялся, но вот обработка Enter из предыдущего примера у меня перестала работать. Мой неработающий код на память.
Upd: Читатель Максим Г. предложил следующее решение проблемы. Убираем атрибуты imeOptions, imeActionId, imeActionLabel и установим их программно.
По желанию можете отслеживать только у нужного поля. Поставьте дополнительное условие после первого блока if:
Интерфейс OnKeyListener
Чтобы среагировать на нажатие клавиши внутри существующего представления из активности, реализуйте интерфейс OnKeyListener и назначьте его для объекта View, используя метод setOnKeyListener(). Вместо того, чтобы реализовывать отдельные методы для событий нажатия и отпускания клавиш, OnKeyListener использует единое событие onKey().
Используйте параметр keyCode для получения клавиши, которая была нажата. Параметр KeyEvent нужен для распознавания типа события (нажатие представлено константой ACTION_DOWN, а отпускание — ACTION_UP).
Сдвигаем активность
Чтобы всплывающая клавиатура не заслоняла элемент интерфейса, который получил фокус, а сдвигала активность вверх, можно в манифесте для нужной активности прописать атрибут android:windowSoftInputMode с параметром adjustPan:
Также доступны и другие параметры:
- stateUnspecified — настройка по умолчанию. Система сама выбирает подходящее поведение клавиатуры.
- stateUnchanged — клавиатура сохраняет своё последнее состояние (видимое или невидимое), когда активность с текстовым полем получает фокус.
- stateHidden — клавиатура скрыта, когда открывается активность. Клавиатура появится при наборе текста. Если пользователь переключится на другую активность, то клавиатура будут скрыта, но при возвращении назад клавиатура останется на экране, если она была видима при закрытии активности.
- stateAlwaysHidden — клавиатура всегда скрывается, если активность получает фокус.
- stateVisible — клавиатура видима.
- stateAlwaysVisible — клавиатура становится видимой, когда пользователь открывает активность.
- adjustResize — размеры компонентов в окне активности могут изменяться, чтобы освободить место для экранной клавиатуры.
- adjustPan — окно активности и его компоненты не изменяются, а сдвигаются таким образом, чтобы текстовое поле с фокусом не было закрыто клавиатурой.
- adjustUnspecified — настройка по умолчанию. Система сама выбирает нужный режим.
Параметры с префиксом state можно комбинировать с настройками с префиксом adjust:
Например, чтобы показать клавиатуру при старте активности, используйте stateVisible.
Данные настройки доступны и программно. Например, код для adjustResize:
Кстати, этот код не сработает в полноэкранном режиме (флаг FLAG_FULLSCREEN). Сверяйтесь с документацией.
Узнать выбранный язык на клавиатуре
Для определения текущего языка на клавиатуре можно использовать следующий код.
Следует быть осторожным с примером. На эмуляторе с Android 6.0 пример работал корректно. На реальных устройствах у меня корректно определялся русский язык, но при переключении на английский язык выдавал пустую строку или значение «zz». В этом случае можно прибегнуть к условиям if и проверять ожидаемое значение.
Источник