关闭

[置顶] 简析在React Native中如何适配iPhoneX

标签: ReactNativeiPhoneX
2018人阅读 评论(1) 收藏 举报
分类:


刚创建的React Native 微信公众号,欢迎微信扫描关注订阅号,每天定期会分享react native 技术文章,移动技术干货,精彩文章技术推送。同时可以扫描我的微信加入react-native技术交流微信群。欢迎各位大牛,React Native技术爱好者加入交流!



一、介绍


iPhone X 发布也有一段时间了,独特的 "齐刘海",以及 "小嘴巴" 带给了苹果粉们无限的遐想,同时也带来众多的吐槽。

前几天,招商银行公众号在微信推送了一条消息,11月招商银行App要发布最新版本,完美适配iPhoneX,是国内第一家银行App适配iPhoneX。感兴趣的朋友可以去下载体验一下。作为App开发者,此时你的心情是欣喜若狂,还是一万个XXX奔腾而过。欣喜也许是因为又可以在自己开发App中"大展拳脚",而一万个XXX奔腾而过,也许完美表达了你的真心,又该乖乖的去做适配了。

扯了这么多,终于上道了。本篇博客内容就是要和大家分享在React Native开发的App中,我们该如何去做适配。首先在做适配之前,我们先了解下iPhoneX在UI上的一些变化。iPhoneX版本引入了一个新名词: 【安全区域】



以上图竖屏为例,安全区域即从顶部传感器之下,底部Home区域之上的可视交互区域。那么和之前的iPhone系列有什么不同呢?




iOS11前屏幕的分辨率为 375 * 667,而iPhoneX屏幕的高度则变为812,顶部高出145。所以适配的问题基本围绕UI来解决,并且适配的核心思路就是:【避开安全区域,使布局自适应】,我们来看几个对比图:


(1)状态栏部分





(2)底部导航部分



(3)横屏状态



二、适配


iOS11前导航栏的高度是64,其中状态栏(StatusBar)的高度为20。iPhoneX的状态栏(StatusBar)高度变为了44(传感器区域高度),如果是自定义的TopBar,这部分需要做相应的适配。
iPhoneX的底部增加了虚拟Home区,由于安全区域的原因默认TabBar的高度由49变为83,增高了34(Home区高度),所以自定义的底部TabBar也需要需改其适配方案。


图片来源:http://fighting300.com/2017/09/14/iOS11-UI-adjust/

解决这个问题,最简单的方式就是给每个界面的顶部布局和底部有导航的布局曾加高度,修改PaddingTop或者PaddingBottom。同时为了iOS11之前同样适用,我们需要根据版本来让系统选择不同的Style即可。所以第一步我们需要判定当前的手机设备是否为iPhoneX。如何判断呢?很简单,可以根据一个很明显的改变:屏幕高度。

import {
    Platform,
    Dimensions
} from 'react-native';

// iPhoneX
const X_WIDTH = 375;
const X_HEIGHT = 812;

// screen
const SCREEN_WIDTH = Dimensions.get('window').width;
const SCREEN_HEIGHT = Dimensions.get('window').height;

export function isIphoneX() {
    return (
        Platform.OS === 'ios' && 
        ((SCREEN_HEIGHT === X_HEIGHT && SCREEN_WIDTH === X_WIDTH) || 
        (SCREEN_HEIGHT === X_WIDTH && SCREEN_WIDTH === X_HEIGHT))
    )
}

有了上述条件,我们可以根据设备版本来选择不同的Style样式即可。

export function ifIphoneX (iphoneXStyle, regularStyle) {
    if (isIphoneX()) {
        return iphoneXStyle;
    } else {
        return regularStyle
    }
}

然后在你的样式文件中添加样式

const styles = StyleSheet.create({
    topBar: {
        backgroundColor: '#ffffff',
        ...ifIphoneX({
            paddingTop: 44
        }, {
            paddingTop: 20
        })
    },
})


三、扩展


想必大家都知道,React Native 在前两天发布了0.50.1版本。幸运的是,在该版本中,添加了一个SafeAreaView的Component,来完美支持iPhoneX的适配。并且React-Navigation导航控件库也在^1.0.0-beta.16版本添加对iPhoneX的支持。小伙伴们终于可以轻松的燥起来了。此时也会有一个新的问题,不能升级RN版本的童靴怎么办呢?也不用急,React社区react-community开源了一个JsOnly版本的SafeAreaView,使得在低版本上同样可以解决iPhoneX的适配问题,使用方式也很简单:

<SafeAreaView>
  <View>
    <Text>Look, I'm safe!</Text>
  </View>
</SafeAreaView>
只要将SafeAreaView作为最外层控件即可。


四、SafeAreaView 核心源码简析


SafeAreaView的index.js文件中的核心代码,分析实现大致分为如下:

(1)测量,设置触摸安全区域

  componentDidMount() {
    InteractionManager.runAfterInteractions(() => {
      this._onLayout();
    });
  }

.....

_onLayout = () => {
    if (!this.view) return;

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

    const WIDTH = isLandscape ? X_HEIGHT : X_WIDTH;
    const HEIGHT = isLandscape ? X_WIDTH : X_HEIGHT;

    this.view.measureInWindow((winX, winY, winWidth, winHeight) => {
      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,
      });
    });
  };

(2)获取设备环境

const isIPhoneX = (() => {
  if (minor >= 50) {
    return isIPhoneX_deprecated;
  }

  return (
    Platform.OS === 'ios' &&
    ((D_HEIGHT === X_HEIGHT && D_WIDTH === X_WIDTH) ||
      (D_HEIGHT === X_WIDTH && D_WIDTH === X_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;
})();

const statusBarHeight = isLandscape => {
  if (isIPhoneX) {
    return isLandscape ? 0 : 44;
  }

  if (isIPad) {
    return 20;
  }

  return isLandscape ? 0 : 20;
};

(3)根据设备环境版本,触摸区域,获取对应的Padding样式,并赋值给safeAreaStyle

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

    const style = {
      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;
          }
        }
      });
    }

    return style;
  };

  _getInset = key => {
    const { isLandscape } = this.props;
    switch (key) {
      case 'horizontal':
      case 'right':
      case 'left': {
        return isLandscape ? (isIPhoneX ? 44 : 0) : 0;
      }
      case 'vertical':
      case 'top': {
        return statusBarHeight(isLandscape);
      }
      case 'bottom': {
        return isIPhoneX ? (isLandscape ? 24 : 34) : 0;
      }
    }
  };

(4)将样式传递给顶层布局View,使得布局自使用

class SafeView extends Component {

  componentWillReceiveProps() {
    this._onLayout();
  }

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

    if (Platform.OS !== 'ios') {
      return <View style={style}>{this.props.children}</View>;
    }

    if (!forceInset && minor >= 50) {
      return <SafeAreaView style={style}>{this.props.children}</SafeAreaView>;
    }

    const safeAreaStyle = this._getSafeAreaStyle();

    return (
      <View
        ref={c => (this.view = c)}
        onLayout={this._onLayout}
        style={[style, safeAreaStyle]}
      >
        {this.props.children}
      </View>
    );
  }
}

基本的思路都是根据设备环境,以及屏幕状态,设置对应的Padding样式,从而自适应布局即可。


1
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

react-native 屏幕尺寸和文字大小适配

现在的手机品牌和型号越来越多,导致我们平时写布局的时候会在个不同的移动设备上显示的效果不同, 比如我们的设计稿一个View的大小是300px,如果直接写300px,可能在当前设备显示正常,但到了其他设...
  • u011272795
  • u011272795
  • 2017-06-27 22:45
  • 6554

React-Native跨平台多屏幕尺寸适配方案一览

前言对于RN而言,跨平台已经是不二之选,那么对于不同平台的尺寸的适配又是一个亘古的难题,下面说一下我再项目中的一点实践心得,大神勿喷。方法getstatic静态方法,进行返回屏幕的像素密度。一些例子如...
  • jiangbo_phd
  • jiangbo_phd
  • 2017-02-23 21:40
  • 6818

简析在React Native中如何适配iPhoneX

一、介绍 iPhone X 发布也有一段时间了,独特的 "齐刘海",以及 "小嘴巴" 带给了苹果粉们无限的遐想,同时也带来众多的吐槽。 前几天,招商银行公众号在微信推送了一条消息,11月招商银...
  • sinat_17775997
  • sinat_17775997
  • 2017-12-05 13:20
  • 160

React Native 基于画板简析封装安卓原生UI

封装Android原生视图的基本步骤展示。
  • SEATELL
  • SEATELL
  • 2017-12-25 15:10
  • 59

iOS11及iPhoneX适配最新最全指南.pdf

  • 2017-11-09 09:44
  • 1.21MB
  • 下载

iOS11及iPhoneX适配-探索方案.pdf

  • 2017-11-03 10:30
  • 1.64MB
  • 下载

基于React Native + redux 开发的客户端(适配IOS和Android),可查看保存妹纸,收藏分享文章等

基于react native + redux开发的iOS和安卓干果客户端> react native的优点想必不用多说,就如同facebook开发react native的宗旨所述,Learn Onc...
  • Xiongtm
  • Xiongtm
  • 2017-08-01 11:43
  • 635

React-Native中屏幕的适配问题

React-Native中屏幕的适配问题
  • m0_37260875
  • m0_37260875
  • 2017-03-21 16:34
  • 2911

React native 分辨率适配

React Native中使用的尺寸单位是pt,是一个绝对长度,而设计师使用的是px, 这两种尺寸如何换算呢?官方提供了PixelRatio: http://www.jianshu.com/p/...
  • sinat_17775997
  • sinat_17775997
  • 2017-05-30 18:10
  • 937

React Native升级指南|v0.40+升级适配经验与心得

React Native作为一个有上千开发者参与的开源项目,自从2015年3月27日第一版发布以来到现在已经有147次版本发布了,平均起来几乎每周都会有新的版本发布。随着一次次版本的迭代,React ...
  • fengyuzhengfan
  • fengyuzhengfan
  • 2017-01-17 19:20
  • 5011
    个人资料
    • 访问:455003次
    • 积分:5591
    • 等级:
    • 排名:第5474名
    • 原创:185篇
    • 转载:10篇
    • 译文:2篇
    • 评论:539条
    博客专栏
    文章分类
    最新评论
    关于我