返回 登录
13

React Native 响应式布局实践

作者简介: 张臻,现任前端开发团队 Leader,就职于 NHN Techorus,侧重 Web 技术和互动性的内容开发,主要负责日本 NHN comico 集团下各项目的 Web 前端架构设计与开发,最近兴趣专注于利用 React 和 Redux 的移动端 Web 应用开发实践。
责编:唐小引,技术之路,共同进步。欢迎技术投稿、给文章纠错,请发送邮件至tangxy@csdn.net
版权声明:本文为 CSDN 原创文章,未经允许,请勿转载。

【导语】React Native 的样式和布局部分采用了前端布局上所使用 CSS 的子集。利用 CSS 里的 Flexbox 进行布局和原生平台的布局方式有比较大的区别。本文集中讲解 Flexbox 的原理,以及 Platform、Dimensions API的用法。并结合具体例子,介绍如何具体实现跨平台响应式的布局。


和原生的 iOS 以及 Android 的开发方式不同,React Native 的布局采用了 Web 前端布局所常用的 Flexbox 模型。这个模型的特点在于能够在按照固定尺寸布局之后,灵活地分配屏幕上的剩余空间,利用这个模型可以轻松实现许多应用中所需要的布局设计。

开发人员掌握了 Flexbox 模型即可随心所欲地对屏幕上的 UI 元素进行布局,再结合 React Native 所提供的获取屏幕信息、平台信息的 API,就可以进阶实现响应式布局。本文就实现响应式布局的三大支柱——Flex box 模型,获取屏幕信息的 Dimensions API,获取平台信息的 Platform API 进行介绍,最后结合例子来实践响应式布局。

Flexbox 模型

React Native 在布局和样式上极大程度上借鉴了 Web 前端所使用的 CSS 规格。CSS 布局方面的算法主要由三个部分组成,首先是解决单个 UI 元素的尺寸问题的 Box 模型(具体由 width,height,padding,border,margin 属性构成),其次是解决 UI 元素相对位置的 Position 模型(具体由 position,top,right,bottom,left 属性构成),最后是解决剩余空间分配问题的 Flexbox 模型。

三者当中,前两者解决相对局部的布局问题,概念也相对易懂,本文中将不再多做说明。Flexbox 模型则相对复杂,会牵扯一些独特的概念,下图展示了 Flexbox 算法中所涉及的用语。

“容器”指定了进行 Flexbox 模型布局的范围,任意的某个单个 UI 元素都可以当作容器,Flexbox 模型的算法不会改变该元素以及其外部元素的布局,只影响其直系子辈元素的布局。

“项目”则是 Flexbox 所直接作用的部分,通常是容器下面的直系子辈元素。

“主轴”定义了 Flexbox 进行布局的方向,在 React Native 中默认为纵向(从上往下),Flexbox 模型的算法将会沿这个方向依序对项目进行布局。

“交叉轴”为主轴所垂直的轴,在 React Native 中默认为横向(从左往右),开发人员可以指定每个项目在交叉轴上如何布局。

在了解了 Flexbox 的主要用语之后,就可以试着理解一下 Flexbox 模型的算法。下图辅以具体的布局例子进行图解,算法的步骤如下:

1. 首先在主轴上按各项目默认尺寸(通常按 Box 模型属性或是 flexBasis 属性指定)进行布局;

2. 如果主轴方向上需要换行,则依据 flexWrap 的值,处理换行;

3. 逐行计算主轴上是否有剩余长度;

4. 如果该行剩余长度>0(有空白),则根据 flexGrow 系数伸长各个项目,尽量确保填满空白;

5. 如果该行剩余长度<0(有溢出),则根据 flexShrink 系数缩短各个项目,尽量确保没有溢出;

6. 依照 justifyContent 的值,处理各个项目在主轴上的对齐;

7. 依照 alignItems 以及 alignSelf 的值,处理各个项目在交叉轴上的对齐。

理解了算法的步骤之后,最后具体介绍一下构成 Flexbox 的各类属性,这里仅简要概括一下每个属性的作用,更加详细的说明则可以参考 React Native 或是 CSS 的相关文档。

首先是可以在容器上指定的属性,它们会作用在所有项目的布局上:

  • flexDirection 控制主轴的方向,可以选择纵向的从上往下(column),从下往上(column-reverse),或是横向的从左往右(row),从右往左(row-reverse)
  • flexWarp 控制项目换行的方式,可以选择换行(wrap)或是强制不换行(nowrap)
  • justifyContent 控制项目在主轴上的对齐方式,可以选择起始位置对齐(flex-start),终止位置对齐(flex-end),居中对齐(center),两侧贴边等间隔对齐(space-between),两侧非贴边等间隔对齐(space-around)
  • alignItems 控制项目在交叉轴上的对齐方式,可以选择起始位置对齐(flex-start),终止位置对齐(flex-end),居中对齐(center),拉伸对齐(strecth)

其次是可以在项目上个别指定的属性,它们只会作用在被指定的元素上,并且优先于级容器上所指定的内容:

  • flexBasis 设置项目在主轴上的默认尺寸
  • flexGrow 设置项目在需要伸长时,所伸长的比重
  • flexShrink 设置项目在需要缩短时,所缩短的比重
  • flex 方便同时设置 flexBasis,flexGrow,flexShrink 的属性,按照所指定的值分 3 种情况
    • N > 0 时,设置主轴上所占长度,效果上相当于 flexGrow: N, flexShrink: 1
    • N = 0 时,根据 width/height 来设置尺寸,效果上相当于 flexGrow: 0, flexShrink: 0
    • N = -1 时,根据 width/height 来设置尺寸,主轴空间不足时进行缩短,效果上相当于 flexGrow: 0, flexShrink: 1
  • alignSelf 设置项目在交叉轴上的对齐方式,和 alignItems 一样,可以选择起始位置对齐(flex-start),终止位置对齐(flex-end),居中对齐(center),拉伸对齐(strecth)

flexBox 模型的容器可以指定在任意元素上,可以无限制地进行嵌套,这极大地增强了布局的自由性。结合 Box 模型和 Position 模型可以满足应用开发过程中大部分的布局需求。

同时 flexBox 模型可以灵活分配容器的剩余空间的特性,使其在屏幕大小、内容尺寸不定的情况下具有相当强的适应性,开发人员无需事先计算并指定各种屏幕大小情况下的固定尺寸,往往是实现各式响应式布局问题的最佳解决方案。

获取屏幕信息

Dimensions API 是 React Native 提供的获取屏幕信息用的 API。开发人员可以通过调用 Dimensions.get()方法取得一个包含屏幕长宽信息的对象,来把握当前用户的设备的屏幕大小,并且以此来简易推测用户是否处于横屏状态。

用户使用应用的过程中,由于设备的旋转方向变化或者多应用分屏等情况,屏幕信息可能随时会产生变化。作为可以对应各种变化情况的最佳实践,推荐在组件的 onLayout 的回调中使用 Dimensions.get()方法来获取屏幕信息。

下面的例子展示了如何获取屏幕信息,保存在组件的 state 中,并只在横屏的时候显示组件。

class DimensionsDemo extends Component {
  constructor(props) {
    super(props);

    this.state = { ...Dimensions.get('window') };
  }

  render() {
    return (
      <View onLayout={ () => this.setState({
          ...Dimensions.get('window')
        }) }>
        { this.state.width >= this.state.height && <LandscapeWarning /> }
      </View>
    );
  }
};

获取平台信息

Platform API 是 React Native 提供的获取平台信息用的 API,同时也提供了一些方法来方便开发人员对各个平台进行分支处理。

Platform.OS 和 Platform.Version 属性分别提供了当前设备的 OS 信息以及 OS 版本信息。开发人员可以根据相应的值,对 UI 组件进行分支处理。下面的例子在用户使用 iOS 的情况下会显示组件,而在 Android 的情况下则会显示组件。

class PlatformDemo extends Component {
  render() {
    return (
      <View>
        { Platform.OS === 'ios' && <NavigatorIOS /> }
        { Platform.OS === 'android' && <NavigatorAndroid /> }
      </View>
    );
  }
}

另外通过利用 Platform.select()方式可以以更加精简的方式来同样实现上面例子中的分支处理。

class PlatformDemo extends Component {
  render() {
    return (
      <View>
        { Platform.select({
          ios: <NavigatorIOS />,
          android: <NavigatorAndroid /> 
        }) }
      </View>
    );
  }
}

响应式布局实践

最后来实际结合以上的知识,用单套代码实现一个简单的响应式布局的例子。这里以 iOS 的邮件应用主屏为例,在手机屏幕上该屏幕只显示邮件列表,但在平板上则同时显示列表窗格和邮件的详细窗格,工具栏的位置也随之调整。

手机屏幕单栏布局(屏幕宽度<768px)/平板屏幕分栏布局(屏幕宽度≧768px)

以下节选的代码展示了如何利用同一套代码,根据不同的屏幕尺寸情况灵活改变布局方式,实现响应式布局:

class MailAppExample extends Component {
  constructor(props) {
    super(props);

    this.state = {
      ...Dimensions.get('window')
    };
  }

  render() {
    const isTabletLayout = this.state.width >= 768;

    return (
      <View style={ {
        flex: 1,
        alignItems: 'stretch',
        flexDirection: isTabletLayout ? 'row' : 'column'
      } } onLayout={ () => this.setState({
        ...Dimensions.get('window')
      }) }>
      <View style={ [
        {
          flex: 1,
          alignItems: 'stretch'
        },
        isTabletLayout && {
          flex: 0,
          width: 320
        }
      ] }>
        <MailAppNavigator />
        <MailList />
        { !isTabletLayout && <Toolbar /> }
      </View>
      { isTabletLayout && (
        <View style={ {
          flex: 2,
          alignItems: 'stretch',
          borderLeftWidth: 1
        } }>
          <Toolbar isTabletLayout />
          <MailDetail />
        </View>
      ) }
      </View>
    );
  }
}

总结

开发人员只要熟悉 Flexbox 模型,再加以活用 Dimensions 以及 Platform API,即可实现一套代码对应多平台多设备的目的。相比按 Android 和 iOS 分别开发响应式布局,可以节省大量的时间,同时学习成本相对较低。另外相比开发 Web 应用,又可以提供更好的纯生用户体验,这是 React Native 博得大量人气的原因之一。如果想要进一步了解文中所介绍的技术细节,推荐可以参考阅读以下链接,利用范例代码实际操练加深理解。

相关链接

评论