本站文章均为李华明Himi原创,转载务必在明显处注明:
转载自【黑米GameDev街区】 原文链接:http://www.himigame.com/react-native/2346.html
本篇Himi来利用ListView和TextInput这两种组件实现对话、聊天框。
首先需要准备的有几点:(组件的学习就不赘述了,简单且官方有文档)
1. 学习下 ListView:
官方示例:http://reactnative.cn/docs/0.27/tutorial.html#content
官方文档:http://reactnative.cn/docs/0.27/listview.html#content
2. 学习下:TextInput:
官方文档:http://reactnative.cn/docs/0.27/textinput.html#content
3. 获取组件实例常用的两种方式:
有时候,渲染出来的组件,我们需要拿到它的实例进行调用其函数等操作。假设有如下代码段:
render(){ return( <Text>Himi</Text> ) }
如上,如果我们想要拿到这个Text组件的实例对象,有如下两种形式:
第一种:
render(){ return( <Text>Himi</Text> ) }
使用时:this.refs._text ,通过this.refs进行获取。
第二种:
render(){ var_text; return( <Textref={(text)=>{_text=text;}}> Himi </Text> ) }
使用时:_text ,直接用这个变量即可。
如上都有了一定了解时,那么下面我们进行本篇的正题:
制作一个对话、聊天框,内容可滚动,且最新的消息永远保持在最底部显示!
一:首先我们先简单布局一个聊天场景,布局+各种小组件的使用(代码简单,不多说):
importReact,{ Component }from'react'; import{ View,Text,TouchableHighlight,Image,PixelRatio,ListView,StyleSheet,TextInput,Alert,}from'react-native'; vardatas=[ { isMe:false,talkContent:'最近在学习ReactNative哦!',},{ isMe:true,talkContent:'听说是个跨平台开发原生App的开源引擎',{ isMe:false,talkContent:'嗯啊,很不错,可以尝试下吧。过了这段时间继续研究UE去了。唉~技术出身,就是放不下技术呀~',talkContent:'感觉编不下去对话了呀......感觉编不下去对话了呀......感觉编不下去对话了呀......感觉编不下去对话了呀......',talkContent:'无语!',talkContent:'自说自话,好难!随便补充点字数吧,嗯就酱紫:)',talkContent:'感觉编不下去对话了呀......感觉编不下去对话了呀..',talkContent:'GG,思密达编不下去了!',]; exportdefaultclassFarmChildViewextendsReact.Component{ constructor(props){ super(props); this.state={ inputContentText:'',dataSource:newListView.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}> <Textstyle={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}> <Textstyle={{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}> <Textstyle={styles.bottomBtnText}> 发送 </Text> </View> </TouchableHighlight> </View> </View> ); } } varstyles=StyleSheet.create({ container:{ flex:1,backgroundColor:'#EEEEEE' },topView:{ alignItems:'center',backgroundColor:'#DDDDDD',height:52,padding:5 },bottomView:{ flexDirection:'row',alignItems:'center',sendBtn:{ alignItems:'center',backgroundColor:'#FF88C2',padding:10,borderRadius:5,height:40,bottomBtnText:{ flex:1,fontSize:18,fontWeight:'bold',talkView:{ flex:1,backgroundColor:'white',flexDirection:'row',marginLeft:5,marginRight:55,marginBottom:10 },talkImg:{ height:40,width:40,marginLeft:10,talkText:{ flex:1,fontSize:16,talkViewRight:{ flex:1,backgroundColor:'#90EE90',justifyContent:'flex-end',marginLeft:55,marginRight:5,talkImgRight:{ height:40,marginRight:10,searchBox:{ height:40,flex:1,//类似于android中的layout_weight,设置为1即自动拉伸填充 borderRadius:5,//设置圆角边 backgroundColor:'white',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 是当当点击发送按钮后,调用我们的自定义函数。
二:下面我们实现点击发送后,将用户在输入框内输入的内容添加到我们的ListView上,并重绘!
主要处理逻辑,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) }) }
1. if( this.state.inputContentText.trim().length <= 0 )
inputContentText用来记录用户在输入框输入的内容,因此这里我们先对内容是否为空进行判定!
trim () 函数不多说了吧,去掉字符串首尾空格。纯空格的内容也不允许发送~
2. datas.push
这里是我们将新的数据添加到ListView中,其中文字内容就是我们记录的用户输入的内容
3. this.refs._textInput.clear()
这里就是我们一开始准备工作介绍的小3节,通过this.refs._textInput()来获取我们定义的TextInput组件实例。
4. 最后我们调用了 this.setState函数来对其两个变量进行修改:
inputContentText :把记录用户刚才输入在聊天框内的内容清空。
dataSource:更新ListView的数据,因为我们刚添加了一条数据
三:让新的数据永远展示在ListView的底部,其实就是想要一个新数据添加后,自动从下滚上来的效果。
Himi在做这一步的时候考虑过几种方式,下面介绍两种比较容易理解实现的方式:
a) 通过计算每个ListView的每一行View的高度来计算出位置,然后与ListView的视图高度进行对比,最后确定是否进行滚动操作(超出ListView的视图才应该滚动)
b) 根据官方ListView提供的renderFooter函数来完成!
renderFooter:
官方解释:“页头与页脚会在每次渲染过程中都重新渲染(如果提供了这些属性)。如果它们重绘的性能开销很大,把他们包装到一个StaticContainer或者其它恰当的结构中。页脚会永远在列表的最底部,而页头会在最顶部。”
粗糙的理解:每次绘制都会调用renderFooter这个绘制函数,而renderFooter就是绘制ListView最底部的位置。这里不是ListView视图最底部,而且ListView内容高度的最底部位置!!
因此我们通过ListView的renderFooter 绘制一个0高度的view,通过获取其Y位置,其实就是获取到了ListView内容高度底部的Y位置。
这里我们来介绍b方案,简单便捷。关于a方案,我想大家自己都很容易理解实现。
其实通过上面布局这段代码中,可以看到,Himi也已经对renderFooter的函数也绑到了自定义函数myRenderFooter上,所以我们只要在renderFooter中处理即可,如下代码:
myRenderFooter(e){ return<ViewonLayout={(e)=>{ this.footerY=e.nativeEvent.layout.y; if(this.listHeight&&this.footerY&&this.footerY>this.listHeight){ varscrollDistance=this.listHeight-this.footerY; this.refs._listView.scrollTo({y:-scrollDistance}); } }}/> }
1. 首先我们先绘制一个0高度的view :return <View/>
2. 通过ListView的onLayout函数来获取并执行我们的滚动等逻辑。
onLayout 函数官方说明:
“当组件挂载或者布局变化的时候调用
参数为:{nativeEvent: { layout: {x,y,width,height}}}
这个事件会在布局计算完成后立即调用一次,不过收到此事件时新的布局可能还没有在屏幕上呈现,尤其是一个布局动画正在进行中的时候。”
3. this.footerY= e.nativeEvent.layout.y;
this.footerY 一开始说过了,用来记录0高度view的相对于ListView所在底部的Y位置。
注:这里Himi定义成this.footerY,原因是Himi也尝试了其他方式实现聊天滚动,为了方便使用。因此大家这里也可以定义var临时的即可。或者直接得到使用都无所谓啦~
4. if( this.listHeight && this.footerY &&this.footerY>this.listHeight )
this.listHeight:与第三步类似,Himi通过ListView的onLayout函数获取到其高度记录在此变量上。
这里的判断目的:当最新的内容高度大雨ListView视图高度后,再开始执行滚动逻辑。
5. 最后的滚动逻辑代码段:
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进行动画滚动操作!
截此,我们的聊天、对话框完成,效果图如下(点击查看动态图):
备注:每一行数据中Himi都定义了一个isMe 的字段,这里来表示说话是对方还是自己。
isMe = true : 头像在右边,说话底为绿色。
isMe =false : 头像放左侧,说话底为白色。
其实这里Himi就是想做一些区分,模仿聊天的对话形式,所以加的变量。大家也可以各种自定义的啦~
原文链接:https://www.f2er.com/react/306653.html