React Native 响应式布局实践

louisfj 5年前
   <p>【导语】React Native 的样式和布局部分采用了前端布局上所使用 CSS 的子集。利用 CSS 里的 Flexbox 进行布局和原生平台的布局方式有比较大的区别。本文集中讲解 Flexbox 的原理,以及 Platform、Dimensions API的用法。并结合具体例子,介绍如何具体实现跨平台响应式的布局。</p>    <p>和原生的 iOS 以及 Android 的开发方式不同,React Native 的布局采用了 Web 前端布局所常用的 Flexbox 模型。这个模型的特点在于能够在按照固定尺寸布局之后,灵活地分配屏幕上的剩余空间,利用这个模型可以轻松实现许多应用中所需要的布局设计。</p>    <p>开发人员掌握了 Flexbox 模型即可随心所欲地对屏幕上的 UI 元素进行布局,再结合 React Native 所提供的获取屏幕信息、平台信息的 API,就可以进阶实现响应式布局。本文就实现响应式布局的三大支柱——Flex box 模型,获取屏幕信息的 Dimensions API,获取平台信息的 Platform API 进行介绍,最后结合例子来实践响应式布局。</p>    <h2>Flexbox 模型</h2>    <p>React Native 在布局和样式上极大程度上借鉴了 Web 前端所使用的 CSS 规格。CSS 布局方面的算法主要由三个部分组成,首先是解决单个 UI 元素的尺寸问题的 Box 模型(具体由 width,height,padding,border,margin 属性构成),其次是解决 UI 元素相对位置的 Position 模型(具体由 position,top,right,bottom,left 属性构成),最后是解决剩余空间分配问题的 Flexbox 模型。</p>    <p>三者当中,前两者解决相对局部的布局问题,概念也相对易懂,本文中将不再多做说明。Flexbox 模型则相对复杂,会牵扯一些独特的概念,下图展示了 Flexbox 算法中所涉及的用语。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/da9f9c93f08f6fb73e6d2ceb073552cc.png"></p>    <p>“容器”指定了进行 Flexbox 模型布局的范围,任意的某个单个 UI 元素都可以当作容器,Flexbox 模型的算法不会改变该元素以及其外部元素的布局,只影响其直系子辈元素的布局。</p>    <p>“项目”则是 Flexbox 所直接作用的部分,通常是容器下面的直系子辈元素。</p>    <p>“主轴”定义了 Flexbox 进行布局的方向,在 React Native 中默认为纵向(从上往下),Flexbox 模型的算法将会沿这个方向依序对项目进行布局。</p>    <p>“交叉轴”为主轴所垂直的轴,在 React Native 中默认为横向(从左往右),开发人员可以指定每个项目在交叉轴上如何布局。</p>    <p>在了解了 Flexbox 的主要用语之后,就可以试着理解一下 Flexbox 模型的算法。下图辅以具体的布局例子进行图解,算法的步骤如下:</p>    <p>1. 首先在主轴上按各项目默认尺寸(通常按 Box 模型属性或是 flexBasis 属性指定)进行布局;</p>    <p><img src="https://simg.open-open.com/show/bcb4eab83fee68b249f472a956eba677.png"></p>    <p>2. 如果主轴方向上需要换行,则依据 flexWrap 的值,处理换行;</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/12ae749251624d3b7314cb47ac550f34.png"></p>    <p>3. 逐行计算主轴上是否有剩余长度;</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/21d40d383c62415913201347f2a693c1.png"></p>    <p>4. 如果该行剩余长度>0(有空白),则根据 flexGrow 系数伸长各个项目,尽量确保填满空白;</p>    <p><img src="https://simg.open-open.com/show/da0c3d26724eeba5a6a5684a98f42cbb.png"></p>    <p>5. 如果该行剩余长度<0(有溢出),则根据 flexShrink 系数缩短各个项目,尽量确保没有溢出;</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/cbdbe1b962bfd2259327f03c6c2a1795.png"></p>    <p>6. 依照 justifyContent 的值,处理各个项目在主轴上的对齐;</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/3daf31c00a24fce701e722551dd7ed63.png"></p>    <p>7. 依照 alignItems 以及 alignSelf 的值,处理各个项目在交叉轴上的对齐。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/6297555c87b37d8e83a9e37e56156351.png"></p>    <p>理解了算法的步骤之后,最后具体介绍一下构成 Flexbox 的各类属性,这里仅简要概括一下每个属性的作用,更加详细的说明则可以参考 React Native 或是 CSS 的相关文档。</p>    <p>首先是可以在容器上指定的属性,它们会作用在所有项目的布局上:</p>    <ul>     <li>flexDirection 控制主轴的方向,可以选择纵向的从上往下(column),从下往上(column-reverse),或是横向的从左往右(row),从右往左(row-reverse)</li>     <li>flexWarp 控制项目换行的方式,可以选择换行(wrap)或是强制不换行(nowrap)</li>     <li>justifyContent 控制项目在主轴上的对齐方式,可以选择起始位置对齐(flex-start),终止位置对齐(flex-end),居中对齐(center),两侧贴边等间隔对齐(space-between),两侧非贴边等间隔对齐(space-around)</li>     <li>alignItems 控制项目在交叉轴上的对齐方式,可以选择起始位置对齐(flex-start),终止位置对齐(flex-end),居中对齐(center),拉伸对齐(strecth)</li>    </ul>    <p>其次是可以在项目上个别指定的属性,它们只会作用在被指定的元素上,并且优先于级容器上所指定的内容:</p>    <ul>     <li>flexBasis 设置项目在主轴上的默认尺寸</li>     <li>flexGrow 设置项目在需要伸长时,所伸长的比重</li>     <li>flexShrink 设置项目在需要缩短时,所缩短的比重</li>     <li>flex 方便同时设置 flexBasis,flexGrow,flexShrink 的属性,按照所指定的值分 3 种情况      <ul>       <li>N > 0 时,设置主轴上所占长度,效果上相当于 flexGrow: N, flexShrink: 1</li>       <li>N = 0 时,根据 width/height 来设置尺寸,效果上相当于 flexGrow: 0, flexShrink: 0</li>       <li>N = -1 时,根据 width/height 来设置尺寸,主轴空间不足时进行缩短,效果上相当于 flexGrow: 0, flexShrink: 1</li>      </ul> </li>     <li>alignSelf 设置项目在交叉轴上的对齐方式,和 alignItems 一样,可以选择起始位置对齐(flex-start),终止位置对齐(flex-end),居中对齐(center),拉伸对齐(strecth)</li>    </ul>    <p>flexBox 模型的容器可以指定在任意元素上,可以无限制地进行嵌套,这极大地增强了布局的自由性。结合 Box 模型和 Position 模型可以满足应用开发过程中大部分的布局需求。</p>    <p>同时 flexBox 模型可以灵活分配容器的剩余空间的特性,使其在屏幕大小、内容尺寸不定的情况下具有相当强的适应性,开发人员无需事先计算并指定各种屏幕大小情况下的固定尺寸,往往是实现各式响应式布局问题的最佳解决方案。</p>    <h2>获取屏幕信息</h2>    <p>Dimensions API 是 React Native 提供的获取屏幕信息用的 API。开发人员可以通过调用 Dimensions.get()方法取得一个包含屏幕长宽信息的对象,来把握当前用户的设备的屏幕大小,并且以此来简易推测用户是否处于横屏状态。</p>    <p>用户使用应用的过程中,由于设备的旋转方向变化或者多应用分屏等情况,屏幕信息可能随时会产生变化。作为可以对应各种变化情况的最佳实践,推荐在组件的 onLayout 的回调中使用 Dimensions.get()方法来获取屏幕信息。</p>    <p>下面的例子展示了如何获取屏幕信息,保存在组件的 state 中,并只在横屏的时候显示组件。</p>    <pre>  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>      );    }  };</pre>    <h2>获取平台信息</h2>    <p>Platform API 是 React Native 提供的获取平台信息用的 API,同时也提供了一些方法来方便开发人员对各个平台进行分支处理。</p>    <p>Platform.OS 和 Platform.Version 属性分别提供了当前设备的 OS 信息以及 OS 版本信息。开发人员可以根据相应的值,对 UI 组件进行分支处理。下面的例子在用户使用 iOS 的情况下会显示组件,而在 Android 的情况下则会显示组件。</p>    <pre>  class PlatformDemo extends Component {    render() {      return (        <View>          { Platform.OS === 'ios' && <NavigatorIOS /> }          { Platform.OS === 'android' && <NavigatorAndroid /> }        </View>      );    }  }</pre>    <p>另外通过利用 Platform.select()方式可以以更加精简的方式来同样实现上面例子中的分支处理。</p>    <pre>  class PlatformDemo extends Component {    render() {      return (        <View>          { Platform.select({            ios: <NavigatorIOS />,            android: <NavigatorAndroid />           }) }        </View>      );    }  }</pre>    <h2>响应式布局实践</h2>    <p>最后来实际结合以上的知识,用单套代码实现一个简单的响应式布局的例子。这里以 iOS 的邮件应用主屏为例,在手机屏幕上该屏幕只显示邮件列表,但在平板上则同时显示列表窗格和邮件的详细窗格,工具栏的位置也随之调整。</p>    <p><img src="https://simg.open-open.com/show/d152f1eaab410dacc8d6328c30cf1357.jpg"></p>    <p>手机屏幕单栏布局(屏幕宽度<768px)/平板屏幕分栏布局(屏幕宽度≧768px)</p>    <p>以下节选的代码展示了如何利用同一套代码,根据不同的屏幕尺寸情况灵活改变布局方式,实现响应式布局:</p>    <pre>  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>      );    }  }</pre>    <h2>总结</h2>    <p>开发人员只要熟悉 Flexbox 模型,再加以活用 Dimensions 以及 Platform API,即可实现一套代码对应多平台多设备的目的。相比按 Android 和 iOS 分别开发响应式布局,可以节省大量的时间,同时学习成本相对较低。另外相比开发 Web 应用,又可以提供更好的纯生用户体验,这是 React Native 博得大量人气的原因之一。如果想要进一步了解文中所介绍的技术细节,推荐可以参考阅读以下链接,利用范例代码实际操练加深理解。</p>    <p> </p>    <p>来自:http://geek.csdn.net/news/detail/190557</p>    <p> </p>