React Native использует Yoga для создания макета в стиле Flexbox, который помогает нам декларативно и легко настроить макет.

Модуль Flexible Box, обычно называемый flexbox, был разработан как модель одномерного макета и как метод, который может предлагать распределение пространства между элементами в интерфейсе и мощные возможности выравнивания.

Как человек, который работал с Auto Layout в iOS и Constrain Layout в Android, мне иногда бывает трудно работать с Flexbox в React Native. Один из них - как разместить определенный элемент вверху или внизу экрана. Это сценарий, когда один элемент не следует правилу контейнера.

Традиционный макет

Рассмотрим этот традиционный экран приветствия, на котором есть текст и кнопка входа в систему.

Что легко достигается с помощью

import React from 'react'
import { View, StyleSheet, Image, Text, Button } from 'react-native'
import strings from 'res/strings'
import palette from 'res/palette'
import images from 'res/images'
import ImageButton from 'library/components/ImageButton'
export default class Welcome extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Image
          style={styles.image}
          source={images.placeholder} />
        <Text style={styles.heading}>{strings.onboarding.welcome.heading.toUpperCase()}</Text>
        <Text style={styles.text}>{strings.onboarding.welcome.text1}</Text>
        <Text style={styles.text}>{strings.onboarding.welcome.text2}</Text>
        <ImageButton
          style={styles.button}
          title={strings.onboarding.welcome.button} />
      </View>
    )
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center'
  },
  image: {
    marginTop: 50
  },
  heading: {
    ...palette.heading, ...{
      marginTop: 40
    }
  },
  text: {
    ...palette.text, ...{
      marginHorizontal: 8,
      marginVertical: 10
    }
  }
})

Обратите внимание на styles. В отличие от Интернета, Flexbox в React Native по умолчанию устанавливает вертикальную ось главной оси, поэтому элементы располагаются сверху вниз. alignItems обеспечивает центрирование всех элементов по горизонтальной оси, которая является поперечной осью в соответствии с терминологией Flexbox.

Кнопка положения внизу

По замыслу, кнопка должна располагаться внизу экрана. Темный хотя может предложить нам использовать position: 'absolute', что-то вроде

button: {
  position: 'absolute',
  bottom:0
}

Этот обходной путь может сработать, но это все равно, что отказаться от Flexbox. Нам нравится Flexbox, и мы любим его. Решение состоит в том, чтобы добавить контейнер для кнопки и использовать flex-end внутри, чтобы кнопка переместилась вниз.

Давайте добавим контейнер

<View style={styles.bottom}>
  <ImageButton
    style={styles.button}
    title={strings.onboarding.welcome.button} />
</View>

и стили

bottom: {
  flex: 1,
  justifyContent: 'flex-end',
  marginBottom: 36
}

flex сообщает bottom виду, что нужно занять оставшееся место. И внутри этого пространства дно выкладывается снизу, вот что означает flex-end.

Вот как выглядит результат

И есть полный код

import React from 'react'
import { View, StyleSheet, Image, Text, Button } from 'react-native'
import strings from 'res/strings'
import palette from 'res/palette'
import images from 'res/images'
import ImageButton from 'library/components/ImageButton'
export default class Welcome extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Image
          style={styles.image}
          source={images.placeholder} />
        <Text style={styles.heading}>{strings.onboarding.welcome.heading.toUpperCase()}</Text>
        <Text style={styles.text}>{strings.onboarding.welcome.text1}</Text>
        <Text style={styles.text}>{strings.onboarding.welcome.text2}</Text>
        <View style={styles.bottom}>
          <ImageButton
            style={styles.button}
            title={strings.onboarding.welcome.button} />
        </View>
      </View>
    )
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center'
  },
  image: {
    marginTop: 50
  },
  heading: {
    ...palette.heading, ...{
      marginTop: 40
    }
  },
  text: {
    ...palette.text, ...{
      marginHorizontal: 8,
      marginVertical: 10
    }
  },
  bottom: {
    flex: 1,
    justifyContent: 'flex-end',
    marginBottom: 36
  }
})

Что такое flex: 1

Согласно Базовым концепциям flexbox

Свойство flex CSS определяет, как гибкий элемент будет увеличиваться или уменьшаться, чтобы соответствовать пространству, доступному в его гибком контейнере. Это сокращенное свойство, устанавливающее flex-grow, flex-shrink и flex-basis.

и w3

Flex: ‹positive-number› Эквивалент flex: ‹positive-number› 1 0. Делает гибкий элемент гибким и устанавливает основу гибкости равной нулю, в результате чего элемент получает указанную долю свободного пространства в гибком контейнере. Если все элементы в гибком контейнере используют этот шаблон, их размеры будут пропорциональны указанному коэффициенту гибкости.

В большинстве браузеров flex: 1 равно 1 1 0, что означает flex-grow: 1, flex-shrink:1, flex-basis: 0. flex-grow и flex-shrink указывают, насколько элемент будет увеличиваться или уменьшаться по сравнению с остальными гибкими элементами в том же контейнере. А the flex-basis указывает начальную длину гибкого элемента. В этом случае bottom View займет оставшееся место. И в этом пространстве мы можем иметь любой поток, какой захотим. Чтобы переместить кнопку вниз, мы используем justifyContent для размещения элементов по главной оси с flex-end, который выравнивает гибкие элементы в конце контейнера.

Композиционный подход

Хотя это работает, код можно быстро продублировать, так как нам нужно делать это на большом количестве экранов. Все, что нам нужно, это обернуть это ImageButton внутри container. Давайте инкапсулируем это с помощью функции полезности. Добавьте это utils/moveToBottom.js

import React from 'react'
import { View, StyleSheet } from 'react-native'
function moveToBottom(component) {
  return (
    <View style={styles.container}>
      {component}
    </View>
  )
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-end',
    marginBottom: 36
  }
})
export default moveToBottom

Теперь на нашем экране нам просто нужно импортировать

import moveToBottom from 'library/utils/moveToBottom'

и обернем нашу кнопку

{
  moveToBottom(
    <ImageButton
      style={styles.button}
      title={strings.onboarding.welcome.button}
      onPress={() => {
        this.props.navigation.navigate('Term')
      }} />
  )
}

На этот раз у нас тот же экран, что и раньше, но с более повторно используемым кодом. Поскольку styles находятся внутри нашего модуля moveToBottom, нам больше не нужно указывать стили на нашем экране. Вот полный код

import React from 'react'
import { View, StyleSheet, Image, Text, Button } from 'react-native'
import strings from 'res/strings'
import palette from 'res/palette'
import images from 'res/images'
import ImageButton from 'library/components/ImageButton'
import moveToBottom from 'library/utils/moveToBottom'
export default class Welcome extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Image
          style={styles.logo}
          source={images.logo} />
        <Image
          style={styles.image}
          source={images.placeholder} />
        <Text style={styles.heading}>{strings.onboarding.welcome.heading.toUpperCase()}</Text>
        <Text style={styles.text}>{strings.onboarding.welcome.text1}</Text>
        <Text style={styles.text}>{strings.onboarding.welcome.text2}</Text>
        {
          moveToBottom(
            <ImageButton
              style={styles.button}
              title={strings.onboarding.welcome.button}
              onPress={() => {
                this.props.navigation.navigate('Term')
              }} />
          )
        }
      </View>
    )
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center'
  },
  logo: {
    marginTop: 70,
    marginBottom: 42,
  },
  image: {
},
  heading: {
    ...palette.heading, ...{
      marginTop: 40
    }
  },
  text: {
    ...palette.text, ...{
      marginHorizontal: 8,
      marginVertical: 10
    }
  }
})

Как передать компонент в качестве параметра

Я должен признать, что изначально я реализую moveToBottom, используя Component (нам нужны прописные буквы, поскольку в React есть соглашение об использовании начальных прописных букв для компонентов), чтобы вставить Component внутрь View.

function moveToBottom(Component) {
  return (
    <View style={styles.container}>
      <Component />
    </View>
  )
}

Но это приводит к ошибке комплектации

ExceptionsManager.js:84 Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: <ImageButton />. Did you accidentally export a JSX literal instead of a component?

а также

ExceptionsManager.js:76 Invariant Violation: Invariant Violation: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

В этот момент я сообщаю, что вещь, которую я передаю, на самом деле является объектом, а не class, поэтому я рассматриваю его как объект, и он работает.

function moveToBottom(component) {
  return (
    <View style={styles.container}>
      {component}
    </View>
  )
}

marginBottom на Android

В приведенной выше функции moveToBottom я использую marginBottom, чтобы иметь некоторый запас снизу. Это работает на iOS, но каким-то образом не влияет на Android, и сейчас я использую react-native 0.57.0. Это несоответствие часто может происходить при разработке React Native. Быстрый обходной путь - выполнить проверку платформы, мы можем превратить ее в отличную функцию в src/library/utils/check

import { Platform } from 'react-native'
const check = {
  isAndroid: () => {
    return Platform.OS === 'android'
  }
}
export default check

Затем в moveToBottom давайте использовать paddingBottom в случае, если приложение работает на Android.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-end',
    paddingBottom: check.isAndroid ? 14 : 0
  }
})

Куда пойти отсюда

В этом посте мы переходим от абсолютного положения к другому контейнеру, узнаем flex, как добавить многоразовую функцию и как правильно передать компонент в качестве параметра. Надеюсь, что вы найдете ее полезной. Вы также можете ознакомиться с этим постом Вход в React Native с помощью Facebook SDK, где я показываю больше советов по разработке React Native и рекомендуемые ссылки, чтобы узнать о Flexbox.

❤️ Поддержите мои приложения ❤️

❤️❤️😇😍🤘❤️❤️