本篇Himi来利用ListView和TextInput这两种组件实现对话、聊天框。
首先需要准备的有几点:(组件的学习就不赘述了,简单且官方有文档)
官方示例:http://reactnative.cn/docs/0.27/tutorial.html#content
官方文档:http://reactnative.cn/docs/0.27/listview.html#content
官方文档:http://reactnative.cn/docs/0.27/textinput.html#content
有时候,渲染出来的组件,我们需要拿到它的实例进行调用其函数等操作。假设有如下代码段:
render() {     return (         <Text>Himi</Text>     ) }    如上,如果我们想要拿到这个Text组件的实例对象,有如下两种形式:
render() {     return (         <Text ref='_text'>Himi</Text>     ) }    使用时:this.refs._text ,通过this.refs进行获取。
render() {     var _text;     return (         <Text ref={(text) => { _text = text; }}>         Himi         </Text>     ) }    使用时:_text ,直接用这个变量即可。
importReact, {   Component } from 'react'; import {   View,   Text,   TouchableHighlight,   Image,   PixelRatio,   ListView,   StyleSheet,   TextInput,   Alert,  } from 'react-native';     var datas =[  {     isMe:false,     talkContent:'最近在学习React Native哦!',  },  {     isMe:true,     talkContent:'听说是个跨平台开发原生App的开源引擎',  },   {     isMe:false,     talkContent:'嗯啊,很不错,可以尝试下吧。过了这段时间继续研究UE去了。唉~技术出身,就是放不下技术呀~',   },   {     isMe:false,     talkContent:'感觉编不下去对话了呀......感觉编不下去对话了呀......感觉编不下去对话了呀......感觉编不下去对话了呀......',   },   {     isMe:true,     talkContent:'无语!',   },   {     isMe:false,     talkContent:'自说自话,好难!随便补充点字数吧,嗯 就酱紫 :) ',   },   {     isMe:true,     talkContent:'感觉编不下去对话了呀......感觉编不下去对话了呀..',   },   {     isMe:false,     talkContent:'GG,思密达编不下去了!',   }, ];     exportdefault class FarmChildViewextends React.Component {     constructor(props) {         super(props);         this.state = {           inputContentText:'',           dataSource: new ListView.DataSource({             rowHasChanged: (row1, row2) => row1 !== row2,           }),         };         this.listHeight = 0;         this.footerY = 0;     }       componentDidMount() {         this.setState({             dataSource: this.state.dataSource.cloneWithRows(datas)         });     }     renderEveryData(eData) {   return (   <Viewstyle={{flexDirection:'row',alignItems: 'center'}}>           <Image             source={eData.isMe==true? null:require('./res/headIcon/ox1.png')}             style={eData.isMe==true?null:styles.talkImg}           />   <Viewstyle={eData.isMe==true?styles.talkViewRight:styles.talkView}>             <Text style={ styles.talkText }>                {eData.talkContent}             </Text>   </View>           <Image             source={eData.isMe==true? require('./res/headIcon/ox2.png') :null}             style={eData.isMe==true?styles.talkImgRight:null}           />   </View>   );   }       myRenderFooter(e){     }       pressSendBtn(){     }       render() {         return (             <Viewstyle={ styles.container }>               <Viewstyle={styles.topView}>                 <Text style={{fontSize:20,marginTop:15,color:'#f00'}}>HimiReactNative 系列教程</Text>               </View>                   <ListView                 ref='_listView'                 onLayout={(e)=>{this.listHeight = e.nativeEvent.layout.height;}}                 dataSource={this.state.dataSource}                 renderRow={this.renderEveryData.bind(this)}                 renderFooter={this.myRenderFooter.bind(this)}               />                   <Viewstyle={styles.bottomView}>                   <Viewstyle={styles.searchBox}>                   <TextInput                       ref='_textInput'           onChangeText={(text) =>{this.state.inputContentText=text}}                       placeholder=' 请输入对话内容'                       returnKeyType='done'                       style={styles.inputText}                   />                 </View>                   <TouchableHighlight                   underlayColor={'#AAAAAA'}                   activeOpacity={0.5}                   onPress={this.pressSendBtn.bind(this)}                 >                   <Viewstyle={styles.sendBtn}>                     <Text style={ styles.bottomBtnText }>                        发送                     </Text>           </View>                 </TouchableHighlight>                 </View>             </View>         );     } }   var styles = StyleSheet.create({   container: {     flex: 1,     backgroundColor: '#EEEEEE'   },   topView:{     alignItems: 'center',     backgroundColor: '#DDDDDD',     height: 52,     padding:5   },   bottomView:{     flexDirection: 'row',     alignItems: 'center',     backgroundColor: '#DDDDDD',     height: 52,     padding:5   },   sendBtn: {     alignItems: 'center',     backgroundColor: '#FF88C2',     padding: 10,     borderRadius:5,     height:40,   },   bottomBtnText: {     flex: 1,     fontSize: 18,     fontWeight: 'bold',   },     talkView: {     flex: 1,     alignItems: 'center',     backgroundColor: 'white',     flexDirection: 'row',     padding: 10,     borderRadius:5,     marginLeft:5,     marginRight:55,     marginBottom:10   },   talkImg: {     height: 40,     width: 40,     marginLeft:10,     marginBottom:10     },   talkText: {     flex: 1,     fontSize: 16,     fontWeight: 'bold',     },   talkViewRight: {     flex: 1,     alignItems: 'center',     backgroundColor: '#90EE90',     flexDirection: 'row',     justifyContent: 'flex-end',     padding: 10,     borderRadius:5,     marginLeft:55,     marginRight:5,     marginBottom:10   },   talkImgRight: {     height: 40,     width: 40,     marginRight:10,     marginBottom:10     },   searchBox: {     height: 40,     flexDirection: 'row',     flex:1,  // 类似于android中的layout_weight,设置为1即自动拉伸填充     borderRadius: 5,  // 设置圆角边     backgroundColor: 'white',     alignItems: 'center',     marginLeft:5,     marginRight:5,     marginTop:10,     marginBottom:10,   },   inputText: {     flex:1,     backgroundColor: 'transparent',     fontSize: 20,     marginLeft:5   }, });    以上一共做了这么几件事:
以上代码需要讲解的有几点:
1. inputContentText 这个state中的变量用于记录用户在TextInput输入的内容
2. this.listHeight = 0; 获取到ListHeight的高度
this.footerY = 0; 记录ListView内容的最底部的Y位置。
(作用后续讲)
3. myRenderFooter(e){} 这里是当ListView的 renderFooter 函数触发时候调用的。(作用后续讲)
4. pressSendBtn 是当当点击发送按钮后,调用我们的自定义函数。
主要处理逻辑,Himi已经设计好了,就是在 pressSendBtn 函数中处理即可,处理代码段如下:
    pressSendBtn(){       if(this.state.inputContentText.trim().length <= 0){         Alert.alert('提示', '输入的内容不能为空');         return;       }       datas.push({         isMe:false,         talkContent:this.state.inputContentText,       });         this.refs._textInput.clear();       this.setState({           inputContentText:'',           dataSource: this.state.dataSource.cloneWithRows(datas)       })     }    inputContentText用来记录用户在输入框输入的内容,因此这里我们先对内容是否为空进行判定!
trim () 函数不多说了吧,去掉字符串首尾空格。纯空格的内容也不允许发送~
这里是我们将新的数据添加到ListView中,其中文字内容就是我们记录的用户输入的内容
这里就是我们一开始准备工作介绍的小3节,通过this.refs._textInput()来获取我们定义的TextInput组件实例。
inputContentText :把记录用户刚才输入在聊天框内的内容清空。
dataSource:更新ListView的数据,因为我们刚添加了一条数据
a) 通过计算每个ListView的每一行View的高度来计算出位置,然后与ListView的视图高度进行对比,最后确定是否进行滚动操作(超出ListView的视图才应该滚动)
b) 根据官方ListView提供的renderFooter函数来完成!
renderFooter:
官方解释:“页头与页脚会在每次渲染过程中都重新渲染(如果提供了这些属性)。如果它们重绘的性能开销很大,把他们包装到一个StaticContainer或者其它恰当的结构中。页脚会永远在列表的最底部,而页头会在最顶部。”
粗糙的理解:每次绘制都会调用renderFooter这个绘制函数,而renderFooter就是绘制ListView最底部的位置。 这里不是ListView视图最底部,而且ListView内容高度的最底部位置!!
因此我们通过ListView的renderFooter 绘制一个0高度的view,通过获取其Y位置,其实就是获取到了ListView内容高度底部的Y高度。
其实通过上面布局这段代码中,可以看到,Himi也已经对renderFooter的函数也绑到了自定义函数myRenderFooter上,所以我们只要在renderFooter中处理即可,如下代码:
    myRenderFooter(e){       return <ViewonLayout={(e)=> {         this.footerY= e.nativeEvent.layout.y;           if (this.listHeight && this.footerY &&this.footerY>this.listHeight) {           var scrollDistance = this.listHeight - this.footerY;           this.refs._listView.scrollTo({y:-scrollDistance});         }       }}/>     }    onLayout 函数官方说明:
“当组件挂载或者布局变化的时候调用
参数为:{nativeEvent: { layout: {x, y, width, height}}}
这个事件会在布局计算完成后立即调用一次,不过收到此事件时新的布局可能还没有在屏幕上呈现,尤其是一个布局动画正在进行中的时候。”
this.footerY 一开始说过了,用来记录0高度view的相对于ListView所在底部的Y位置。
this.listHeight:与第三步类似,Himi通过ListView的onLayout函数获取到其高度记录在此变量上。
这里的判断目的:当最新的内容高度大雨ListView视图高度后,再开始执行滚动逻辑。
var scrollDistance = this.listHeight – this.footerY;this.refs._listView.scrollTo({y:-scrollDistance});
首先通过当前ListView的视图高度-内容底部Y位置,获取到相差的举例 scrollDistance,这个距离就是我们需要ListView 滚动的举例,且取反滚动!
最后 _listView 是我们ListView的组件实例,因为ListView中也有ScrollView的特性,因此我们可以使用其:
scrollTo({x: 0, y: 0, animated: true})
对我们ListView进行动画滚动操作!