react-native布局与组件

  • 2019 年 10 月 4 日
  • 笔记

RN布局与样式

布局

一款好的App离不开漂亮的布局,RN中的布局方式采⽤的是FlexBox(弹性布局) 。

经典资料参考:阮一峰flex 布局语法篇:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

FlexBox提供了在不同尺⼨设备上都能保持一致的布局⽅式 。在移动端,在这里不必担心兼容问题。

但是RN的flex布局和真正的css还是有所差别:

  • flexDirection:RN中默认是flexDirection:’column’,Web Css中默认是 flex-direction:’row’,也就是说RN的flex默认就是打竖的。
  • alignItems:RN中默认: ‘stretch’,在Web Css中默认 flex-start’,也就是说RN的flex是强制等高的。
  • RN的flex属性,只能接收一个值
  • 不支持的属性: align-content flex-basis order flex-flow flex-grow flex-shrink (平时也用得少)

样式

在移动端开发中,是没有像素概念的。所有量规无单位,表示的是是1个逻辑像素

<View style={{width:100,height:100,margin:10,backgroundColor:'gray'}}>    <Text style={{fontSize:16,margin:20}}>尺⼨寸</Text>  </View>  

上述代码,运⾏在Android上时,View的⻓宽被解释成:100dp 100dp,字体被解释成16sp,运⾏于 ios上时尺⼨单位被解释成pt,这些单位确保了布局在任何不同DPI的手机屏幕上,显示效果一致。

关于更详细的换算关系,查阅:http://www.woshipm.com/pmd/176328.html

写样式除了可以用传统react的css in js方式,也可以这么写:

<text style={[styles.aaa,{color:'red'}]}></text>  

所有文本的样式应该直接加在text上面,如果你在view里面写,就不会生效了。

{/* 错误的实例:不生效 */}  <view style={[styles.aaa,{color:'red'}]}></view>  

组件

react native的魅力在于能够使用系统原生的组件。他们和html标签相似,又有不少区别。

如果写过微信小程序,或许理解起来会比较快。因为前者”借用了”这些组件概念。

简单认知的话,组件和UI框架差不多,用什么引什么。以下对某些重要组件进行介绍。

view:万能容器

视图布局容器,可以理解为原生开发中的万能容器。可嵌套多层,支持flex。

一个组件通常是返回一个view包裹的,如果你想返回两个,可以使用[<View>...</View>,<View>...</View>]的形式返回多个兄弟组件。

SafeAreaView:安全区

SafeAreaView 的目的是在一个“安全”的可视区域内渲染内容。具体来说就是因为目前有 iPhone X 这样的带有“刘海”的全面屏设备,所以需要避免内容渲染到不可⻅见的“刘海”范围内。本组件目前仅⽀持 iOS 设备以及 iOS 11 或更高版本。

SafeAreaView 会自动根据系统的各种导航栏、工具栏等预留出空间来渲染内部内容。更重要的 是,它还会考虑到设备屏幕的局限,比如屏幕四周的圆⻆角或是顶部中间不可显示的“非安全”区域。

<SafeAreaView style={{backgroundColor:'red'}}></SafeAreaView>  

webview:加载网页容器(即将被移除)

创建一个原生的webview,用于加载网页.我们可结合safeAreaView使用:

      <SafeAreaView style={{flex:1}}>          <WebView          source={{uri: 'https://github.com/facebook/react-native'}}          style={{marginTop: 20}}          />        </SafeAreaView>  

在官方最新版本需要安装react-native-webview

需要明确的认知是:webview是有可能存在跨域问题的。

Text:文本容器

主要用于显示文本,具有响应之特性(表现为触摸时是否支持高亮)。同时支持多层嵌套,因此样式可继承(内部继承外部)。但是,不同于web css,字体样式(font color等)只有在text组件上才能起效——所以字体样式的实现只能依赖于text组件。

在Text内部的元素不再使⽤flexbox布局,而是采⽤用文本布局。这意味着内部的元素不再是】一个个矩 形,而可能会在行末进⾏折叠。

                <Text            ellipsizeMode={"tail"} //这个属性通常和下⾯面的 numberOfLines 属性配合使⽤用,⽂文本超出 numberOfLines设定的⾏行行数时,截取⽅方式:head- 从⽂文本内容头部截取显示省略略号。例例如: "...efg",middle - 在⽂文本内容中间截取显示省略略号。例如: "ab...yz",tail - 从⽂文本内容尾 部截取显示省略略号。例例如: "abcd...",clip - 不不显示省略略号,直接从尾部截断。            numberOfLines={1} //配合ellipsizeMode设置⾏行行数            onPress={...} //点击事件 selectable={true}//决定⽤用户是否可以⻓长按选择⽂文本,以便便复制和粘贴。          >123          </Text>  

Image:图片容器

类似img元素。但支持更多但来源,比如网络图片,本机磁盘图片,照相机图片等。

下⾯的例⼦分别演示了如何显示从本地缓存、网络乃至base64拉取图片。

{/* 显示本地图 */}  <Image    source={require('./img/favicon.png')}  />    {/* 显示网络图 */}  <Image      style={{width: 50, height: 50}}      //网络和 base64 数据的图⽚需要⼿动指定尺⼨    source={{uri: 'https://facebook.github.io/react-native/docs/assets/favicon.png'}}  />    {/* 显示base64图 */}  <Image  style={{width: 66, height: 58}} //⽹网络和 base64 数据的图⽚片需要⼿手动指定尺⼨寸  source={{uri:  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg=='}}  />  

自从ios9.0之后,官方就一直推荐使用https协议的网络图片。

ImageBackground 背景图

用法和Image差不多:

{/* 显示网络图 */}  <ImageBackground      style={{width: 50%, height: 50%}}      //网络和 base64 数据的图⽚需要⼿动指定尺⼨    source={{uri: 'https://facebook.github.io/react-native/docs/assets/favicon.png'}}>      <Text>...</Text>  </ImageBackground>  

Button:按钮

一个简单的跨平台的按钮组件。可以进行一些简单的定制。如图,前者为安卓,后者为ios。

<Button    onPress={onPressLearnMore} //⽤户点击此按钮时所调用的处理理函数    title="Learn More" //按钮内显示的⽂文本    color="#841584" //文本的颜⾊(iOS),或是按钮的背景⾊(Android)    disabled={false} //按钮是否可以点击    accessibilityLabel="Learn more about this purple button"    //用于给残障人⼠显示的文本(比如读屏应⽤可能会读取这一内容)  />  

ActivityIndicator loading的小菊花

显示一个loading提示符安卓设备时一个谷歌式半圆环,在ios设备上则显示一朵小菊花。

        <ActivityIndicator            size="large" //指示器器的大⼩,默认为'small'[enum('small', 'large'), number]。⽬前只能在 Android 上设定具体的数值            animating={true} //是否要显示指示器动画,默认为 true 表示显示,false 则隐藏。            hidesWhenStopped={false} //在animating为 false 的时候,是否要隐藏指示器(默认为 true)。如果animating和hidesWhenStopped都为 false,则显示⼀一个静⽌止的指示器。            color="#0000ff" />  

ListView:列表

这个组件的性能比较差,尤其是当有大量的数据需要展示的时候,ListView对内存的占⽤用较多,常出现丢帧卡顿现象。

ListView底层实现,渲染组件Item是全量渲染,而且没有复用机制,当渲染较⼤数据量时,会不可避免地卡顿。

第⼀次打开与切换Tab时会出现卡顿或白屏的情况,比如ListView中有100个Item,只能等这 100条Item都渲染完成,ListView中的内容才会展示滑动列表时会出现卡顿。

未来有很⼤大可能性会被移除 。

VirtualizedList: 虚拟列表

替代ListView的主要解决方案就是VirtualizedList。RN0.43版本中引⼊了了FlatList,SectionList和VirtualizedList,其中VirtualizedList是FlatList和SectionList的底层实现。

FlatList 和 SectionList 的底层实现:VirtualizedList通过维护一个有限的渲染窗⼝(其中包含可⻅的元素),并将渲染窗⼝之外的元素全部用合适的定⻓空⽩空间代替的⽅式,极⼤的改善了内存使⽤,提⾼了大量数据情况下的渲染性能。这个渲染窗⼝能响应滚动行为,元素离可视区越远优先级越低,越近优先级越高,当用户滑动速度过快时,会出现短暂空⽩的情况。

<FlatList      data={[{key: 'a'}, {key: 'b'}]}      renderItem={({item}) => <Text>{item.key}</Text>}  />  

缺点:

(1)为了优化内存占⽤同时保持滑动的流畅,列表内容会在屏幕外异步绘制。这意味着如果用户滑动的速度超过渲染的速度,则会先看到空白的内容。

(2)不支持分组列列表

扯了那么多理论,如果列表写不了想说自己懂rn是很扯的。是时候开始写一个了。

需求:列表的下拉刷新和上划动加载

看今日头条等新闻列表类app时,都需要用到。

import React,{Component} from 'react';  import {View,Text,StyleSheet,Button,FlatList,RefreshControl} from 'react-native';    const listData=Array(20).fill(1).map((x,i)=>{      return {          key:i,          value:`列表项${i+1}`      }  });    export default class HotPage extends Component{      static navigationOptions=({navigation})=>{          return {              headerTitle:navigation.getParam('title')          }      }        constructor(props){          super(props);          this.state={              listData,              isLoading:false          }      }        loadData(refresh){          if(refresh){              this.setState({                  isLoading:refresh              });          }              setTimeout(()=>{              let _listData=[];              if(refresh){                  for(let i=this.state.listData.length-1;i>=0;i--){                      _listData.push(this.state.listData[i])                  }              }else{                  _listData=this.state.listData.concat(listData)              }                this.setState({                  listData:_listData,                  isLoading:false              })          },2000)      }        render(){            return (            <View style={styles.container}>                <FlatList                  data={this.state.listData}                  renderItem={({item}) =>                      <View style={{                          justifyContent:'center',                          alignItems:'center',                          flex:1,                          height:60,                          backgroundColor:'#ccc'                          }}>                      <Text>{item.value}</Text>                      </View>                      }                  // 分割线:不会出现在第一行之前,也不会出现在第一行之后                  ItemSeparatorComponent={()=>{                      return <View style={{height:2,backgroundColor:'#eee'}}/>                  }}                  // 列表为空时渲染组件                  ListEmptyComponent={()=>{                      return <Text style={{textAlign:'center'}}>空空如也</Text>                  }}                  // 顶部组件                  // ListHeaderComponent={()=>{                    // }}                  // 尾部组件                  ListFooterComponent={()=>{                      return <Text>我也是有底线的</Text>                  }}                    // 刷新相关:                  // 如果设置了此选项,则会在列表头部增加一个标准的RefreshControl控件,                  // 同时也需要正确设置refreshing属性                  refreshControl={                      <RefreshControl                        title='loading'                        colors={['red']}                        // 如果设置该属性为true,列表将出现一个正在加载的符号                        refreshing={this.state.isLoading}                        onRefresh={()=>{                            this.loadData(true)                        }}                        tintColor={'orange'}                      />                  }                    Threshold='0.4'                  // 当列表滚动到地步距离不足Threshold时调用                  onEndReached={()=>{                      this.loadData();                  }}                />            </View>          )        }  }    const styles=StyleSheet.create({      container:{          flex:1,          width:'100%',          backgroundColor:'#f5f5f5'      },      text:{          fontSize:26,          marginBottom:20      }  })  

其它组件(Switch/Modal)

可自行查阅api。