ReactNative iphone 12、14 适配

  1. iphone 12、14底部遮挡适配
  2. StatusBar 适配

1. iphone 12、14 底部遮挡适配

node_modules/react-native-safe-area-view/index.js
import React, { Component } from 'react'
import { Dimensions, InteractionManager, Platform, StyleSheet, Animated } from 'react-native'
import hoistStatics from 'hoist-non-react-statics'

import withOrientation from './withOrientation'

// See https://mydevice.io/devices/ for device dimensions
const X_WIDTH = 375
const X_HEIGHT = 812
const XSMAX_WIDTH = 414
const XSMAX_HEIGHT = 896
const PAD_WIDTH = 768
const PAD_HEIGHT = 1024
const IPADPRO11_WIDTH = 834
const IPADPRO11_HEIGHT = 1194
const IPADPRO129_HEIGHT = 1024
const IPADPRO129_WIDTH = 1366

const IPHONE12_H = 844
const IPHONE12_Max = 926
const IPHONE12_Mini = 780

const IPHONE14_H = 844
const IPHONE14_W = 393
const IPHONE14_MAX = 932
const IPHONE14_PLUS = 926
const IPHONE14_MINI = 852

const getResolvedDimensions = () => {
  const { width, height } = Dimensions.get('window')
  if (width === 0 && height === 0) return Dimensions.get('screen')
  return { width, height }
}

const { height: D_HEIGHT, width: D_WIDTH } = getResolvedDimensions()

const PlatformConstants = Platform.constants || {}
const { minor = 0 } = PlatformConstants.reactNativeVersion || {}

const isIPhoneX = (() => {
  if (Platform.OS === 'web') return false

  return (
      (Platform.OS === 'ios' &&
          ((D_HEIGHT === X_HEIGHT && D_WIDTH === X_WIDTH) ||
              (D_HEIGHT === X_WIDTH && D_WIDTH === X_HEIGHT))) ||
      ((D_HEIGHT === XSMAX_HEIGHT && D_WIDTH === XSMAX_WIDTH) ||
          (D_HEIGHT === XSMAX_WIDTH && D_WIDTH === XSMAX_HEIGHT) ||
          (D_HEIGHT === IPHONE12_H || D_HEIGHT === IPHONE12_Max || D_HEIGHT === IPHONE12_Mini) ||
          (D_HEIGHT === IPHONE14_H || D_HEIGHT === IPHONE14_MAX || D_HEIGHT === IPHONE14_MINI || D_HEIGHT === IPHONE14_PLUS))
  )
})()

const isNewIPadPro = (() => {
  if (Platform.OS !== 'ios') return false

  return (
      (D_HEIGHT === IPADPRO11_HEIGHT && D_WIDTH === IPADPRO11_WIDTH) ||
      (D_HEIGHT === IPADPRO11_WIDTH && D_WIDTH === IPADPRO11_HEIGHT) ||
      ((D_HEIGHT === IPADPRO129_HEIGHT && D_WIDTH === IPADPRO129_WIDTH) ||
          (D_HEIGHT === IPADPRO129_WIDTH && D_WIDTH === IPADPRO129_HEIGHT))
  )
})()

const isIPad = (() => {
  if (Platform.OS !== 'ios' || isIPhoneX) return false

  // if portrait and width is smaller than iPad width
  if (D_HEIGHT > D_WIDTH && D_WIDTH < PAD_WIDTH) {
    return false
  }

  // if landscape and height is smaller that iPad height
  if (D_WIDTH > D_HEIGHT && D_HEIGHT < PAD_WIDTH) {
    return false
  }

  return true
})()

let _customStatusBarHeight = null
let _customStatusBarHidden = null
const statusBarHeight = (isLandscape) => {
  if (_customStatusBarHeight !== null) {
    return _customStatusBarHeight
  }

  /**
   * This is a temporary workaround because we don't have a way to detect
   * if the status bar is translucent or opaque. If opaque, we don't need to
   * factor in the height here; if translucent (content renders under it) then
   * we do.
   */
  if (Platform.OS === 'android') {
    if (global.Expo) {
      return global.Expo.Constants.statusBarHeight
    } else {
      return 0
    }
  }

  if (isIPhoneX) {
    return isLandscape ? 0 : 44
  }

  if (isNewIPadPro) {
    return 24
  }

  if (isIPad) {
    return _customStatusBarHidden ? 0 : 20
  }

  return isLandscape || _customStatusBarHidden ? 0 : 20
}

const doubleFromPercentString = (percent) => {
  if (!percent.includes('%')) {
    return 0
  }

  const dbl = parseFloat(percent) / 100

  if (isNaN(dbl)) return 0

  return dbl
}

class SafeView extends Component {
  static setStatusBarHeight = (height) => {
    _customStatusBarHeight = height
  }

  static setStatusBarHidden = (hidden) => {
    _customStatusBarHidden = hidden
  }

  state = {
    touchesTop: true,
    touchesBottom: true,
    touchesLeft: true,
    touchesRight: true,
    orientation: null,
    viewWidth: 0,
    viewHeight: 0,
  }

  componentDidMount() {
    this._isMounted = true
    InteractionManager.runAfterInteractions(() => {
      this._updateMeasurements()
    })
  }

  componentWillUnmount() {
    this._isMounted = false
  }

  componentDidUpdate() {
    this._updateMeasurements()
  }

  render() {
    const { forceInset = false, isLandscape, style, ...props } = this.props

    const safeAreaStyle = this._getSafeAreaStyle()

    return (
        <Animated.View
            ref={(c) => (this.view = c)}
            pointerEvents="box-none"
            {...props}
            onLayout={this._handleLayout}
            style={safeAreaStyle}
        />
    )
  }

  _handleLayout = (e) => {
    if (this.props.onLayout) this.props.onLayout(e)

    this._updateMeasurements()
  }

  _updateMeasurements = () => {
    if (!this._isMounted) return
    if (!this.view) return

    const { isLandscape } = this.props
    const { orientation } = this.state
    const newOrientation = isLandscape ? 'landscape' : 'portrait'
    if (orientation && orientation === newOrientation) {
      return
    }

    const { width: WIDTH, height: HEIGHT } = getResolvedDimensions()

    this.view.getNode().measureInWindow((winX, winY, winWidth, winHeight) => {
      if (!this.view) {
        return
      }
      let realY = winY
      let realX = winX

      if (realY >= HEIGHT) {
        realY = realY % HEIGHT
      } else if (realY < 0) {
        realY = (realY % HEIGHT) + HEIGHT
      }

      if (realX >= WIDTH) {
        realX = realX % WIDTH
      } else if (realX < 0) {
        realX = (realX % WIDTH) + WIDTH
      }

      const touchesTop = realY === 0
      const touchesBottom = realY + winHeight >= HEIGHT
      const touchesLeft = realX === 0
      const touchesRight = realX + winWidth >= WIDTH

      this.setState({
        touchesTop,
        touchesBottom,
        touchesLeft,
        touchesRight,
        orientation: newOrientation,
        viewWidth: winWidth,
        viewHeight: winHeight,
      })
    })
  }

  _getSafeAreaStyle = () => {
    const { touchesTop, touchesBottom, touchesLeft, touchesRight } = this.state
    const { forceInset, isLandscape } = this.props

    const {
      paddingTop,
      paddingBottom,
      paddingLeft,
      paddingRight,
      viewStyle,
    } = this._getViewStyles()

    const style = {
      ...viewStyle,
      paddingTop: touchesTop ? this._getInset('top') : 0,
      paddingBottom: touchesBottom ? this._getInset('bottom') : 0,
      paddingLeft: touchesLeft ? this._getInset('left') : 0,
      paddingRight: touchesRight ? this._getInset('right') : 0,
    }

    if (forceInset) {
      Object.keys(forceInset).forEach((key) => {
        let inset = forceInset[key]

        if (inset === 'always') {
          inset = this._getInset(key)
        }

        if (inset === 'never') {
          inset = 0
        }

        switch (key) {
          case 'horizontal': {
            style.paddingLeft = inset
            style.paddingRight = inset
            break
          }
          case 'vertical': {
            style.paddingTop = inset
            style.paddingBottom = inset
            break
          }
          case 'left':
          case 'right':
          case 'top':
          case 'bottom': {
            const padding = `padding${key[0].toUpperCase()}${key.slice(1)}`
            style[padding] = inset
            break
          }
        }
      })
    }

    // new height/width should only include padding from insets
    // height/width should not be affected by padding from style obj
    if (style.height && typeof style.height === 'number') {
      style.height += style.paddingTop + style.paddingBottom
    }

    if (style.width && typeof style.width === 'number') {
      style.width += style.paddingLeft + style.paddingRight
    }

    style.paddingTop = Math.max(style.paddingTop, paddingTop)
    style.paddingBottom = Math.max(style.paddingBottom, paddingBottom)
    style.paddingLeft = Math.max(style.paddingLeft, paddingLeft)
    style.paddingRight = Math.max(style.paddingRight, paddingRight)

    return style
  }

  _getViewStyles = () => {
    const { viewWidth } = this.state
    // get padding values from style to add back in after insets are determined
    // default precedence: padding[Side] -> vertical | horizontal -> padding -> 0
    let {
      padding = 0,
      paddingVertical = padding,
      paddingHorizontal = padding,
      paddingTop = paddingVertical,
      paddingBottom = paddingVertical,
      paddingLeft = paddingHorizontal,
      paddingRight = paddingHorizontal,
      ...viewStyle
    } = StyleSheet.flatten(this.props.style || {})

    if (typeof paddingTop !== 'number') {
      paddingTop = doubleFromPercentString(paddingTop) * viewWidth
    }

    if (typeof paddingBottom !== 'number') {
      paddingBottom = doubleFromPercentString(paddingBottom) * viewWidth
    }

    if (typeof paddingLeft !== 'number') {
      paddingLeft = doubleFromPercentString(paddingLeft) * viewWidth
    }

    if (typeof paddingRight !== 'number') {
      paddingRight = doubleFromPercentString(paddingRight) * viewWidth
    }

    return {
      paddingTop,
      paddingBottom,
      paddingLeft,
      paddingRight,
      viewStyle,
    }
  }

  _getInset = (key) => {
    const { isLandscape } = this.props
    return getInset(key, isLandscape)
  }
}

export function getInset(key, isLandscape) {
  switch (key) {
    case 'horizontal':
    case 'right':
    case 'left': {
      return isLandscape ? (isIPhoneX ? 44 : 0) : 0
    }
    case 'vertical':
    case 'top': {
      return statusBarHeight(isLandscape)
    }
    case 'bottom': {
      if (isIPhoneX) {
        return isLandscape ? 24 : 34
      }

      if (isNewIPadPro) {
        return 20
      }

      return 0
    }
  }
}

export function getStatusBarHeight(isLandscape) {
  return statusBarHeight(isLandscape)
}

const SafeAreaView = withOrientation(SafeView)

export default SafeAreaView

export const withSafeArea = function(forceInset = {}) {
  return (WrappedComponent) => {
    class withSafeArea extends Component {
      render() {
        return (
            <SafeAreaView style={{ flex: 1 }} forceInset={forceInset}>
              <WrappedComponent {...this.props} />
            </SafeAreaView>
        )
      }
    }

    return hoistStatics(withSafeArea, WrappedComponent)
  }
}

2. StatusBar 适配

import { Dimensions, Platform, StatusBar, PixelRatio } from 'react-native'

const { width, height } = Dimensions.get('window')
const X_WIDTH = 375
const X_HEIGHT = 812
const XSMAX_WIDTH = 414
const XSMAX_HEIGHT = 896

const IPHONE12_H = 844
const IPHONE12_MAX = 926
const IPHONE12_MINI = 780

const IPHONE13_H = 844
const IPHONE13_MAX = 926
const IPHONE13_MINI = 812

const IPHONE14_H = 844
const IPHONE14_W = 393
const IPHONE14_MAX = 932
const IPHONE14_PLUS = 926
const IPHONE14_MINI = 852

const OS = Platform.OS
const ios = OS === 'ios'
const android = OS === 'android'

const isIPhoneX = (() => {
  if (Platform.OS === 'web') return false
  if (Platform.OS === 'android') return false

  return (
      (Platform.OS === 'ios' &&
          ((height === X_HEIGHT && width === X_WIDTH) || (height === X_WIDTH && width === X_HEIGHT))) ||
      ((height === XSMAX_HEIGHT && width === XSMAX_WIDTH) ||
          (height === XSMAX_WIDTH && width === XSMAX_HEIGHT) ||
          (height === IPHONE12_H || height === IPHONE12_MAX || height === IPHONE12_MINI) ||
          (height === IPHONE13_H || height === IPHONE13_MAX || height === IPHONE13_MINI) ||
          (height === IPHONE14_H || height === IPHONE14_MAX || height === IPHONE14_MINI || height === IPHONE14_PLUS))
  )
})()
const statusBarHeight = ios ? (isIPhoneX ? 44 : 20) : StatusBar.currentHeight
global.gScreen = {
  screen_width: width,
  screen_height: height,
  statusBarHeight: statusBarHeight,
  onePixelRatio: 1 / PixelRatio.get(),
}

global.gDevice = {
  ios: ios,
  android: android,
  isIPhoneX: isIPhoneX,
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容