@Awesome-Tauren
2016-06-13T07:14:14.000000Z
字数 16146
阅读 629
rn
flex是Flexible Box的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。采用flex布局的元素,称为flex容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为flex项目(flex item),简称"项目"。如下图所示:

容器默认存在两根轴:主轴(main axis)和交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。
项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。
Flex布局与Android的线性布局(LinearLayout)有点类似,都可以设置布局方向,对齐方式,以及项目的布局占位权重,区别是flex容器中项目分布的总长度超出屏幕宽度,超出的那部分项目不可见,项目不会变形,或者可以设置flexWrap属性,让容器可以分行布局,所有项目都能显示出来。
flex属性声明在:/node_modules/react-native/Libraries/StyleSheet/LayoutPropTypes.js
// https://developer.mozilla.org/en-US/docs/Web/CSS/flex-directionflexDirection: ReactPropTypes.oneOf(['row','column']),// https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrapflexWrap: ReactPropTypes.oneOf(['wrap','nowrap']),// How to align children in the main direction// https://developer.mozilla.org/en-US/docs/Web/CSS/justify-contentjustifyContent: ReactPropTypes.oneOf(['flex-start','flex-end','center','space-between','space-around']),// How to align children in the cross direction// https://developer.mozilla.org/en-US/docs/Web/CSS/align-itemsalignItems: ReactPropTypes.oneOf(['flex-start','flex-end','center','stretch']),// How to align the element in the cross direction// https://developer.mozilla.org/en-US/docs/Web/CSS/align-itemsalignSelf: ReactPropTypes.oneOf(['auto','flex-start','flex-end','center','stretch']),// https://developer.mozilla.org/en-US/docs/Web/CSS/flexflex: ReactPropTypes.number,
由上述代码,我们可以看到flex的属性并不多,而且很好记忆,以下将会一一介绍
flex属性可以分为容器属性和项目属性
其中容器属性包括:flexDirection,justifyContent,alignItems,flexWrap
项目属性包括:flex,alignSelf
以下介绍会使用到一些代码和图片,先定义两个简单组件,方便理解
//定义一个默认半径为20,颜色为#527fe4的圆组件var Circle = React.createClass({render : function(){var size = this.props.size || 20;var color = this.props.color || '#527fe4';return <View style={{backgroundColor:color,borderRadius:size/2,height:size,width:size,margin:1}}/>},});//定义一个放置标题和项目的容器,传入的value属性将会是需要介绍的flex属性var Value = React.createClass({render : function(){var value =<View><Text style={styles.valueText}>{this.props.title}</Text><View style={[styles.valueContainer,this.props.value]}>{this.props.children}</View></View>;return value;},});//定义一个数组放置5个圆var children = [<Circle/>,<Circle/>,<Circle/>,<Circle/>,<Circle/>];
flexDirection:布局方向,决定主轴的方向,默认值是column,即纵向布局
| 值 | 描述 |
|---|---|
| row | 横向布局,主轴为水平方向 |
| column | 纵向布局,主轴为竖直方向 |
row:横向布局
代码:
<Value title='row' value={{flexDirection:'row'}}>{children}</Value>
视图:

column:纵向布局
代码:
<Value title='column' value={{flexDirection:'column'}}>{children}</Value>
视图:

justifyContent:主轴方向对齐方式,默认值是flex-start,即主轴的开端
| 值 | 描述 |
|---|---|
| flex-start | 主轴开端 |
| center | 居中 |
| flex-end | 主轴末端 |
| space-between | 项目与项目之间插入相等空隙 |
| space-around | 项目两旁插入相等空隙 |
flex-start:主轴开端
代码:
<Value title='flex-start' value={{flexDirection:'row', justifyContent:'flex-start'}}>{children}</Value>
视图:

center:主轴的中间位置
代码:
<Value title='center' value={{flexDirection:'row',justifyContent:'center'}}>{children}</Value>
视图:

flex-end:主轴的末端位置
代码:
<Value title='flex-end' value={{flexDirection:'row',justifyContent:'flex-end'}}>{children}</Value>
视图:

space-between:项目与项目之间插入相同距离的空隙
代码:
<Value title='space-between' value={{flexDirection:'row',justifyContent:'space-between'}}>{children}</Value>
视图:

space-around:项目两旁插入相同距离的空隙
代码:
<Value title='space-around' value={{flexDirection:'row',justifyContent:'space-around'}}>{children}</Value>
视图:

alignItems:交叉轴方向对齐方式,默认值flex-start,即交叉轴开端
| 值 | 描述 |
|---|---|
| flex-start | 交叉轴开端 |
| center | 交叉轴居中 |
| flex-end | 交叉轴末端 |
flex-start:交叉轴开端

center:交叉轴的中间位置

flex-end:交叉轴的末端位置

flexWrap:包含内容,默认值是nowrap,不包裹所有内容
| 值 | 描述 |
|---|---|
| nowrap | 项目沿主轴方向布局,超出容器长度的部分不可见 |
| wrap | 项目沿主轴布局所需长度大于容器总长度时,分行布局,所有项目内容都可见 |
nowrap:不包裹内容
代码:
<Value title='nowrap' value={{flexWrap:'nowrap',flexDirection:'row'}}>{children}{children}{children}{children}</Value>
视图::

wrap:包裹内容
代码:
<Value title='wrap' value={{flexWrap:'wrap',flexDirection:'row'}}>{children}{children}{children}{children}</Value>
视图:

flex:布局权重
| 值 | 描述 |
|---|---|
| >=0 | 项目占位权重,容器包含至少2项目时,项目使用flex属性才有意义 |
1:0:flex=0的项目占用空间仅为内容所需空间,flex=1的项目会占据其余所有空间
代码:
<Value title='1:0' value={{flexDirection:'row'}}><Text style={{color:'white',flex:1,textAlign:'center',backgroundColor:'red',fontSize:20,paddingHorizontal:10}}>flex=1</Text><Text style={{color:'white',textAlign:'center',backgroundColor:'yellow',fontSize:20,paddingHorizontal:10}}>flex=0</Text></Value>

2:1
代码:
<Value title='2:1' value={{flexDirection:'row'}}><Text style={{color:'white',flex:2,textAlign:'center',backgroundColor:'blue',fontSize:20}}>flex=2</Text><Text style={{color:'white',flex:1,textAlign:'center',backgroundColor:'green',fontSize:20}}>flex=1</Text></Value>

1:1:1:1
代码:
<Value title='1:1:1:1' value={{flexDirection:'row'}}><Text style={{color:'white',flex:1,textAlign:'center',backgroundColor:'red',fontSize:20}}>flex=1</Text><Text style={{color:'white',flex:1,textAlign:'center',backgroundColor:'yellow',fontSize:20}}>flex=1</Text><Text style={{color:'white',flex:1,textAlign:'center',backgroundColor:'blue',fontSize:20}}>flex=1</Text><Text style={{color:'white',flex:1,textAlign:'center',backgroundColor:'green',fontSize:20}}>flex=1</Text></Value>

alignSelf:项目交叉轴方向自身对齐方式
| 值 | 描述 |
|---|---|
| flex-start | 开端 |
| center | 居中 |
| flex-end | 末端 |
代码:
<Value title='alignSelf' value={{flexDirection:'row',height:30,alignItems:'center'}}><View style={{alignSelf:'flex-start'}}><Circle/></View><View style={{alignSelf:'flex-end'}}><Circle/></View><View style={{alignSelf:'flex-start'}}><Circle/></View><View style={{alignSelf:'flex-end'}}><Circle/></View><View style={{alignSelf:'flex-start'}}><Circle/></View></Value>
视图:

layout除了flex属性之外,当然还有其他属性,同样声明在:/node_modules/react-native/Libraries/StyleSheet/LayoutPropTypes.js
width: ReactPropTypes.number,height: ReactPropTypes.number,top: ReactPropTypes.number,left: ReactPropTypes.number,right: ReactPropTypes.number,bottom: ReactPropTypes.number,margin: ReactPropTypes.number,marginVertical: ReactPropTypes.number,marginHorizontal: ReactPropTypes.number,marginTop: ReactPropTypes.number,marginBottom: ReactPropTypes.number,marginLeft: ReactPropTypes.number,marginRight: ReactPropTypes.number,padding: ReactPropTypes.number,paddingVertical: ReactPropTypes.number,paddingHorizontal: ReactPropTypes.number,paddingTop: ReactPropTypes.number,paddingBottom: ReactPropTypes.number,paddingLeft: ReactPropTypes.number,paddingRight: ReactPropTypes.number,borderWidth: ReactPropTypes.number,borderTopWidth: ReactPropTypes.number,borderRightWidth: ReactPropTypes.number,borderBottomWidth: ReactPropTypes.number,borderLeftWidth: ReactPropTypes.number,position: ReactPropTypes.oneOf(['absolute','relative']),
| 属性 | 类型 | 描述 |
|---|---|---|
| width | number | 容器或者项目的宽度 |
| height | number | 容器或者项目的高度 |
| top,bottom,left,right | number | 在父容器的上下左右偏移量 |
| margin | number | 留边,留边的空间不属于容器或者项目自身空间 |
| marginHorizontal | number | 水平方向留边 |
| marginVertical | number | 垂直方向留边 |
| padding | number | 填充,填充的空间输入容器或者项目自身空间 |
| paddingHorizontal | number | 水平方向填充 |
| paddingVertical | number | 垂直方向填充 |
| borderWidth | number | 边界宽度 |
| position | enum | 位置方式:absolute与relative |
position:默认值为relative
| 值 | 描述 |
|---|---|
| absolute | 绝对布局 |
| relative | 相对布局 |
react的默认位置方式是relative,项目是一个接一个排列下去的,absolute为绝对布局,一般会与left和top属性一起使用。有时候我们需要实现某些项目重叠起来,absolute属性就能发挥作用了,例如下图:

react的基本组件暂时不支持以图片作为背景,所以这里的的转入是一个文本组件,而红色的圆形是一个图片组件,在IOS里面组件也可以作为容器,图片可以正常显示,但是在Android里面,可能存在些问题,如果使用组件作为容器都会出现图片变得好奇怪,所以就可以absoulte来解决问题了。代码如下:
<View style={{width:80,height:80,alignItems:'center',justifyContent:'center'}}><Image style={{position:'absolute',left:0,top:0,resizeMode:'contain',width:80,height:80}} source={require('image!finance_usercenter_ic_into')}/><Text style={{width:80,textAlign:'center',color:'white',fontSize:16}}>转入</Text></View>
这里的View跟Android的View有点不一样,View是可以作为容器也可以作为项目,View作为容器还有其他很多属性,例如backgroundColor,borderWidth,borderColor,opacity等等,这里不一一介绍。
react native的宽高是不需要带单位的,那些width,height,padding,margin的赋值都直接是数字的,当你设定width:10,在IOS的话就是设置了10pt宽度,而在Android上面是10dp,在做项目时,辛勤的美工会帮我们标出所有UI控件的宽,高,边距等等,他们用的单位也是dp,所以赋值style中宽高时,直接填入数字即可。
先上预览图:

TitleBar
'use strict'var React = require('react-native');var {Image,Text,TouchableWithoutFeedback,StyleSheet,View,} = React;var TitleBar = React.createClass({getInitialState: function(){return ({loginState:0,});},render: function(){return(<View style={styles.container}><Image style={styles.icon}source={require('image!ic_low_login_head')}/><Text style={styles.title}>{this.props.title}</Text><Image style={styles.icon} source={require('image!ic_low_login_news')}/></View>);},});var styles = StyleSheet.create({container:{flexDirection:'row',paddingLeft:16,paddingTop:8,paddingRight:16,paddingBottom:8,height:56,alignItems:'center',backgroundColor:'#63534e'},icon:{resizeMode:'cover',width:30,height:30,},title:{flex:1,marginLeft:10,textAlign:'left',color:'white',fontSize:15,},});module.exports = TitleBar;
Header
var Header = React.createClass({getInitialState:function(){return {data:{total:2345.67,increasement:0.00,},};},render: function(){var data = this.state.data;var total = data.total;var increasement = data.increasement;var header =<View style={{flexDirection:'row', alignItems:'center', paddingTop:17,paddingBottom:10,paddingRight:17,backgroundColor:'#63534e'}}><View style={{flex:1, marginLeft:16}}><Text style={{color:'#9a8f8d',fontSize:13,}}>活期余额(元)</Text><View style={{flexDirection:'row', alignItems:'flex-start', marginTop:5}}><Text style={{color:'#ffe7bc',fontSize:32}}>{parseInt(total)+'.'}</Text><Text style={{color:'#ffe7bc',fontSize:18, marginTop:5}}>{total*100%100}</Text></View><Text style={{color:'#9a8f8d',fontSize:10,marginTop:5}}>昨日+{increasement}</Text></View><View style={{width:80,height:80,alignItems:'center',justifyContent:'center'}}><Image style={{position:'absolute',left:0,top:0,resizeMode:'contain',width:80,height:80}} source={require('image!finance_usercenter_ic_into')}/><Text style={{width:80,textAlign:'center',color:'white',fontSize:16}}>转入</Text></View><View style={{width:80,height:80,alignItems:'center',justifyContent:'center'}}><Image style={{position:'absolute',left:0,top:0,resizeMode:'contain',width:80,height:80}} source={require('image!finance_usercenter_ic_out')}/><Text style={{width:80,textAlign:'center',color:'white',fontSize:16}}>转出</Text></View></View>;return header;},});
Insurance
var Insurance = React.createClass({fetchData:function(){var url = 'http://202.69.27.140/btoa/portal/financial/financialList';fetch(url).then((response) => response.json()).then((responseData) => {var categoryList = responseData.data.categoryList;for(var i=0;i<categoryList.length;i++){var categoryItem = categoryList[i];if(categoryItem.category==='insure'){this.setState({data: this.state.data.cloneWithRows(categoryItem.productList),loaded: true,expanded: true,});}}}).done();},componentDidMount:function(){this.fetchData();},getInitialState:function(){return {data:new ListView.DataSource({rowHasChanged:(r1,r2)=>r1!==r2,}),loaded:false,expanded:false,};},renderRow:function(item){var price = (item.productTypeID===30014)?'8.5折起':item.minPremium+'~'+item.maxPremium+'元';var ensure = (item.productTypeID===30014)?'':parseFloat(item.minEnsure/10000)+'~'+parseFloat(item.maxEnsure/10000)+'万';var itemView =<View style={{paddingVertical:15,paddingLeft:17,borderBottomWidth:0.5,borderBottomColor:'#dddddd',backgroundColor:'white'}}><Text style={{color:'#6a534f',fontSize:18}}>{item.productName}</Text><View style={{flexDirection:'row',}}><Text style={{flex:1,color:'#fc7200',fontSize:24}}>{price}</Text><Text style={{flex:1,color:'#6a534f',fontSize:24}}>{ensure}</Text></View><Text style={{color:'#a19796',fontSize:12}}>保费</Text></View>;return itemView;},render:function(){var listView = <ListView dataSource={this.state.data} renderRow={this.renderRow}/>;var moreView = <TouchableWithoutFeedback style={{paddingRight:15}} onPress={()=>{}}><View style={{flexDirection:'row',alignItems:'center',padding:5,height:36}}><Text style={{color:'#9a8f8d',fontSize:13}}>更多</Text><Image style={{width:17,height:15,marginLeft:5,resizeMode:'contain'}} source={require('image!finance_more')}/></View></TouchableWithoutFeedback>var insurance =<View><TouchableWithoutFeedback onPress={()=>{this.setState({expanded:!this.state.expanded});this.props.onExpand&&this.props.onExpand(this.state.expanded);}}><View style={{flexDirection:'row',alignItems:'center',borderColor:'#dddddd',borderTopWidth:0.5,borderBottomWidth:0.5,backgroundColor:'white'}}><Image style={{width:6,height:36,resizeMode:'stretch'}} source={require('image!finance_list_bar_current_deposit_retract')}/><Text style={{textAlign:'center',fontSize:17,color:'#d78d85',marginLeft:16,}}>保险</Text><Text style={{fontSize:13,color:'#9a8f8d',marginLeft:5,flex:1}}>保障每一天</Text>{!this.state.expanded&&<Image style={{width:10,height:10,resizeMode:'contain',marginRight:15}} source={require('image!common_ic_more_spread_out')}/>}{this.state.expanded&&moreView}</View></TouchableWithoutFeedback>{this.state.expanded&&listView}</View>;return insurance;},});
Avertisement
var Avertisement = React.createClass({render:function(){var advertisement =<View style={{margin:8,alignItems:'center'}}><TouchableHighlight><Image style={{resizeMode:'contain',width:344,height:146.7}} source={require('image!finance_banner_001')}/></TouchableHighlight><TouchableHighlight style={{marginTop:8}}><Image style={{resizeMode:'contain',width:344,height:146.7}} source={require('image!finance_banner_002')}/></TouchableHighlight></View>;return advertisement;}});
整体
var FinanceScreen = React.createClass({render:function(){return (<ScrollView ref={(scrollView)=>{this.scrollView=scrollView;}}><View style={styles.container}><TitleBar loginState={0} title={'未登录'}/><Header/><Insurance onExpand={(expanded)=>{}}/><Avertisement/></View></ScrollView>);},});
TabLayout
'use strict'var React = require('react-native');var {Image,Text,View,StyleSheet,TouchableWithoutFeedback,TouchableOpacity,} = React;var iconHome = [require('image!tab_home_normal'),require('image!tab_home_focused')];var iconFinance = [require('image!tab_finance_normal'),require('image!tab_finance_focused')];var iconBank = [require('image!tab_bank_normal'),require('image!tab_bank_focused')];var iconLife = [require('image!tab_life_normal'),require('image!tab_life_focused')];var iconCredit = [require('image!tab_credit_normal'),require('image!tab_credit_focused')];var titleHome = '首页';var titleFinance = '理财';var titleBank = '银行';var titleLife = '生活';var titleCredit = '信用';var tabArray = [{icon:iconHome,title:titleHome},{icon:iconBank,title:titleBank},{icon:iconFinance,title:titleFinance},{icon:iconCredit,title:titleCredit},{icon:iconLife,title:titleLife}];var TabView = React.createClass({_handlePress :function(){if(this.props.onPress){this.props.onPress();}},render:function(){return (<TouchableWithoutFeedback onPress={this._handlePress}><View style={styles.tabViewContainer} on><Image style={styles.tabViewIcon}source={this.props.selected?this.props.icon[1]:this.props.icon[0]}/><Text style={[styles.tabViewText,this.props.selected?styles.tabViewTextFocused:{}]}>{this.props.text}</Text></View></TouchableWithoutFeedback>);},});var TabLayout = React.createClass({select : function(position){if(this.state.selected!==position){this.setState({selected:position});}},onTabChanged : function(position){this.setState({selected:position});if(this.props.onTabChanged){this.props.onTabChanged(position);}},getInitialState: function(){return ({selected:0});},render:function(){// var tabViews = [];// for(var i=0;i<tabArray.length;i++)// {// var tab = tabArray[i];// tabViews.push(<TabView icon={tab.icon} text={tab.title} selected={this.state===i} onPress={()=>{// this.state.selected!==i&&this.onTabChanged(i);// }}/>);// }return (<View style={styles.tabLayout}><TabView icon={iconHome} text={titleHome} selected={this.state.selected===0} onPress={()=>{this.state.selected!==0&&this.onTabChanged(0);}}/><TabView icon={iconBank} text={titleBank} selected={this.state.selected===1} onPress={()=>{this.state.selected!==1&&this.onTabChanged(1);}}/><TabView icon={iconFinance} text={titleFinance} selected={this.state.selected===2} onPress={()=>{this.state.selected!==2&&this.onTabChanged(2);}}/><TabView icon={iconCredit} text={titleCredit} selected={this.state.selected===3} onPress={()=>{this.state.selected!==3&&this.onTabChanged(3);}}/><TabView icon={iconLife} text={titleLife} selected={this.state.selected===4} onPress={()=>{this.state.selected!==4&&this.onTabChanged(4);}}/></View>);},});var styles = StyleSheet.create({tabLayout:{flexDirection:'row',backgroundColor:'white',height:56,borderTopColor:'#e8e8e8',borderTopWidth:0.5,borderBottomWidth:0.5,borderBottomColor:'#e8e8e8',},tabViewContainer:{flex:1,flexDirection:'column',alignItems:'center',justifyContent:'center',},tabViewIcon:{width:30,height:30,resizeMode:'contain',},tabViewText:{color:'#9a8e8c',fontSize:13,textAlign:'center',},tabViewTextFocused:{color:'#63534e',}});module.exports = TabLayout;