Я люблю React Native, но одна вещь, которую всегда было слишком сложно реализовать в моих приложениях, — это реализация поведения избегания клавиатуры. Так что, возможно, пришло время сделать это проще для всех.
Я знаю, что вы можете подумать: в React Native есть KeyboardAvoidingView
. Просто используйте его, верно? Это достаточно просто?
Я бы хотел, чтобы все было так просто.
Самая большая ошибка React Native?
KeyboardAvoidingView
иногда может работать хорошо, но есть много макетов, где он просто не работает (и, вероятно, никогда не будет работать). Кроме того, он несовместим на разных платформах.
Давайте рассмотрим несколько простых макетов:
function SomeSimpleLayout() {
return (
<View style={{
flex: 1,
}}>
<View
style={{
height: Dimensions.get('window').height - 300,
width: '100%',
justifyContent: 'flex-start'
}}
/>
<KeyboardAvoidingView>
<TextInput
style={textInputStyle}
/>
<TextInput
style={textInputStyle}
/>
</KeyboardAvoidingView>
</View>
)
}
Это очень похоже на дизайн, который мне недавно пришлось реализовать на моей консалтинговой работе.
Он просто не работает. Да, вы правильно меня услышали, собственный KeyboardAvoidingView
от React Native не может избежать клавиатуры в этой очень простой ситуации.
Может быть, им стоит назвать его KeyboardSometimesMaybeAvoidingIfItFeelsLikeItView
? В довершение ко всем неприятностям, когда это работает, оно не обеспечивает согласованности между платформами.
Вы можете подумать, а пробовали ли вы все различные значения для параметра behavior
. Да, пробовал. Но они не сработали.
И даже если бы они работали, это все равно было бы отстойно, потому что KeyboardAvoidingView
должен просто избегать этой чертовой клавиатуры. Он не должен заставлять меня помнить и думать о деталях реализации.
С этим я сталкивался время от времени на протяжении всей моей карьеры консультанта по React Native, и это всегда было так неприятно.
Почему мы не можем просто иметь представление, которое постоянно избегает клавиатуры? Неужели избегать клавиатуры так сложно?
Действительно ли это так сложно?
Ответ… Нет. Это действительно не так. React Native просто облажался в этом вопросе.
Повторюсь, я ❤️ люблю❤️ React Native. Это удивительный инструмент, который позволил мне создать несколько удивительных вещей очень быстро. Но этот компонент просто отстой. API плохой, он не справляется со всеми случаями использования, он просто неуклюжий и обеспечивает плохой опыт разработчика.
Еще хуже то, что он включен в сам React Native, что означает, что разработчики, скорее всего, выберут его в качестве первого решения!
Подумайте о том, сколько часов было потрачено на настройку макетов, чтобы заставить KeyboardAvoidingView
правильно работать на iOS, а на Android он ведет себя неожиданно?
Должен быть лучший способ. Мы можем это исправить, мы же инженеры.
Решение
Используя возможности базовой геометрии и знания React Native, мы можем создать представление, избегающее клавиатуры, которое будет работать в любой раскладке. Представление, избегающее клавиатуры, которое действительно постоянно избегает клавиатуры. Давайте назовем его… KeyboardAvoiderView
! 😬
И оно будет избегать клавиатуры 😁:
Тот же макет, что и раньше, только теперь KeyboardAvoiderView
избегает клавиатуры. Отлично! И он будет работать в любом макете, потому что использует трансформации (которые не зависят от макета) вместо анимации свойств, которые влияют на этот макет.
Кроме того, он имеет одинаковое поведение на обеих платформах:
Потрясающе. Это то, чего мы хотели. Как это работает? Если хотите, вы можете посмотреть полный исходный код компонента, который я сделал для этого, здесь, но это, вероятно, не так полезно для вас, как знать ключевые детали реализации компонента:
Детали реализации ключа KeyboardAvoiderView
- React Native позволяет нам получить текущий сфокусированный текстовый ввод из
TextInput.State.currentlyFocusedInput()
, и мы можем измерить этот ввод с помощью.measure()
:
function MyComponent() {
function handleKeyboardShow(e) {
const input = TextInput.State.currentlyFocusedInput();
if(!input) return;
input.measure((x, y, width, height, pageX, pageY)=>{
const inputBottom = (pageY + height);
})
}
}
Это на самом деле очень здорово и необходимо для реализации поведения избегания клавиатуры. В любое время, когда мы хотим получить ссылку на текущий сфокусированный ввод, мы можем просто вызвать эту функцию.
- Координата конца клавиатуры передается в обработчики событий клавиатуры:
function MyComponent() {
function handleKeyboardShow(e) {
const topOfKeyboard = e.endCoordinates.screenY;
}
}
- В Android есть системное поведение клавиатуры, которое нельзя отключить, поэтому мы должны вычислить его и компенсировать:
function MyComponent() {
function handleKeyboardShow(e) {
const input = TextInput.State.currentlyFocusedInput();
if(!input) return;
input.measure((x, y, width, height, pageX, pageY)=>{
const inputBottom = (pageY + height);
// How much the system panned by
const androidPannedBy = Math.max(inputBottomY - e.endCoordinates.screenY, 0);
})
}
}
Здесь мы рассчитываем расстояние, на которое Android будет панорамировать экран при отображении клавиатуры. Было бы хорошо, если бы Android позволил нам отключить системное поведение, но это не так. Это нормально. Мы знаем математику.
Теперь мы можем просто рассчитать количество, на которое мы должны переместиться, основываясь на этих значениях:
var weShouldScrollBy = inputBottom - extraSpace - topOfKeyboard;
if(Platform.OS == 'android') weShouldScrollBy += androidPannedBy;
Обратите внимание на переменную extraSpace
, это расстояние, которое мы хотим иметь между вводом и клавиатурой. Если бы у нас не было этой переменной, нам бы даже не пришлось выполнять никаких вычислений на Android, но помните, что нам нужна кроссплатформенная совместимость!
Мы не будем слишком углубляться в код, но полный KeyboardAvoiderView
доступен в моем пакете @good-react-native/keyboard-avoider (да, я рассказываю о своей собственной библиотеке в этом посте, но только потому, что я думаю, что здесь мы можем решить реальную проблему).
Что насчет представлений с прокруткой?
Иногда мы имеем текстовые входы в представлениях прокрутки. В этих случаях мы просто используем react-native-keyboard-aware-scroll-view
, да?
Не так быстро. react-native-keyboard-aware-scroll-view
— очень полезная библиотека, она работает, и api у нее хороший.
К сожалению, у нее есть несколько существенных недостатков. Я подробно рассказываю о них в своем репо пакета, но основные проблемы заключаются в том, что она просто ломается в некоторых ситуациях и ей не хватает отзывчивости/согласованности.
Как и KeyboardAwareScrollView
, react-native-keyboard-aware-scroll-view
работает не со всеми макетами. Если он не полноэкранный, то он ломается:
Кроме того, он будет работать с ошибкой в любое время, когда вы используете навигацию react. (Да, эта ошибка все еще существует, и вы не сможете избежать ее во всех ситуациях).
Он не максимально отзывчив:
Заметьте, что ввод скрывается. Это не очень заметно, но это хуже, чем если бы ввод полностью обходил клавиатуру (IMO).
Кроме того, это не соответствует поведению KeyboardAvoidingView
. (он использует метод ScrollView
scrollTo
вместо того, чтобы самому управлять прокруткой). Есть и другие проблемы, но я думаю, вы уже поняли суть.
И, кстати, я очень благодарен разработчикам react-native-keyboard-aware-scroll-view
за вклад в сообщество. Если я звучу расстроенным, то это только потому, что у меня остались шрамы от всех проблем, которые эта библиотека вызвала у меня и моих клиентов, и потому, что я знаю, что она может быть лучше.
Мы инженеры, мы можем что-то улучшить. Всегда есть лучший способ. И этот лучший способ называется… KeyboardAvoiderScrollView?
Еще один член @good-react-native/keyboard-avoider
. Это представление прокрутки имеет поведение, соответствующее KeyboardAvoiderView
, более отзывчиво и не ломается, основываясь на остальной части экрана.
Как KeyboardAvoiderView
делает это, спросите вы? Основными причинами являются следующие:
- Он использует анимацию вместо прокрутки представления прокрутки с помощью встроенной анимации, что позволяет лучше контролировать анимацию.
- Он использует абсолютные измерения страницы (я не уверен почему, но
react-native-keyboard-aware-scroll-view
использует измерения окна, что может быть причиной его поломки).
В общем,
В общем, вот и все. Я создал пакет, который, похоже, значительно улучшает все типы поведения при обходе клавиатуры по сравнению с тем, что люди обычно используют в React Native
(KeyboardAvoidingView и react-native-keyboard-aware-scroll-view
).
Есть даже компонент <KeyboardAvoiderInsets/>
для тех случаев, когда вы хотите изменить раскладку при отображении клавиатуры.
Опять же, вы можете проверить его здесь. Он все еще находится на ранней стадии, так что ему еще предстоит решить несколько проблем, так что любой вклад сообщества будет просто замечательным.
Особенно хотелось бы услышать пожелания по функциям. На данный момент у react-native-keyboard-aware-scroll-view
есть некоторые свойства, которых нет у нашей библиотеки, поэтому если вам нужно что-то из этого, вы просто не сможете использовать нашу библиотеку (пока).
Я бы хотел знать, какие из этих функций действительно нужны людям, чтобы мы могли их добавить.
Пакет не идеален, конечно, есть некоторые проблемы и ошибки, которые еще предстоит обнаружить. Но в конечном итоге его реализация означает, что он намного проще в использовании и может обеспечить лучший опыт для разработчиков, чем другие решения.
Другие пакеты не собираются «догонять», так сказать, поэтому я думаю, что в этом редком случае, возможно, пришло время начать с чистого листа.
В любом случае, большое спасибо за ваше время. 😊