您的位置:首页 > Web前端 > React

【REACT NATIVE 系列教程之十三】利用LISTVIEW与TEXTINPUT制作聊天/对话框&&获取组件实例常用的两种方式

2016-06-07 21:44 1011 查看
本站文章均为 李华明Himi 原创,转载务必在明显处注明:
转载自【黑米GameDev街区】 原文链接: http://www.himigame.com/react-native/2346.html

补充说明:一:很多童鞋问,键盘调出来被挡住了,那么下面给出三个解决方案:1. 在render最外层包一个ScrollView,然后当键盘调出时,scrollTo即可实现。2. 在底部添加一个可变化高度的view,根据键盘获取、失去焦点时,进行处理实现3. 使用插件:react-native-keyboard-spacer :https://github.com/Andr3wHur5t/react-native-keyboard-spacer二:有的童鞋说对话框的背景没有根据内容长短自适应,OK ,下面给出自动适应的样式与修改:先看效果图:

1. 导入一个组件:Dimensions2. 我们先将 renderEveryData 的函数改为如下:
renderEveryData(eData) {
var sWidth = Dimensions.get('window').width
return (
<View style={eData.isMe==true?styles.everyRowRight:styles.everyRow}>
<Image
source={eData.isMe==true? null:require('./res/headIcon/ox1.png')}
style={eData.isMe==true?null:styles.talkImg}
/>
<View style={{width:sWidth - 100}}>
<View style={eData.isMe==true?styles.talkViewRight:styles.talkView}>
<Text style={ eData.isMe==true?styles.talkTextRight:styles.talkText }>
{eData.talkContent}
</Text>
</View>
</View>
<Image
source={eData.isMe==true? require('./res/headIcon/ox2.png') :null}
style={eData.isMe==true?styles.talkImgRight:null}
/>
</View>
);
}

3. 用到的样式如下:
everyRow:{
flexDirection:'row',
alignItems: 'center'
},
everyRowRight:{
flexDirection:'row',
alignItems: 'center',
justifyContent:'flex-end'
},
talkView: {
backgroundColor: 'white',
padding: 10,
borderRadius:5,
marginLeft:5,
marginRight:55,
marginBottom:10,
alignSelf:'flex-start',
},
talkViewRight: {
backgroundColor: '#90EE90',
padding: 10,
borderRadius:5,
marginLeft:55,
marginRight:5,
marginBottom:10,
alignSelf:'flex-end',
},
talkText: {
fontSize: 16,
fontWeight: 'bold',
},
talkTextRight: {
fontSize: 16,
fontWeight: 'bold',
alignSelf:'flex-end',
},
talkImg: {
height: 40,
width: 40,
marginLeft:10,
marginBottom:10
},
talkImgRight: {
height: 40,
width: 40,
marginRight:10,
marginBottom:10
},

width:sWidth – 100:这里是来限定Text每一行的最大宽度。sWidth:是获取的屏幕宽。因此通过修改这里的值来指定你想要的每一行最大宽度吧。——————————————–以上为补充内容,下面是正文——————————————–
本篇Himi来利用ListView和TextInput这两种组件实现对话、聊天框。首先需要准备的有几点:(组件的学习就不赘述了,简单且官方有文档)1. 学习下 ListView:官方示例:http://reactnative.cn/docs/0.27/tutorial.html#content官方文档:http://reactnative.cn/docs/0.27/listview.html#content2. 学习下:TextInput:官方文档:http://reactnative.cn/docs/0.27/textinput.html#content3. 获取组件实例常用的两种方式:有时候,渲染出来的组件,我们需要拿到它的实例进行调用其函数等操作。假设有如下代码段:
render() {
return (
<Text>Himi</Text>
)
}

如上,如果我们想要拿到这个Text组件的实例对象,有如下两种形式:第一种:
render() {
return (
<Text>Himi</Text>
)
}

使用时:this.refs._text ,通过this.refs进行获取。第二种:
render() {
var _text;
return (
<Text ref={(text) => { _text = text; }}>
Himi
</Text>
)
}

使用时:_text ,直接用这个变量即可。如上都有了一定了解时,那么下面我们进行本篇的正题: 制作一个对话、聊天框,内容可滚动,且最新的消息永远保持在最底部显示!一:首先我们先简单布局一个聊天场景,布局+各种小组件的使用(代码简单,不多说):
import React, {
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,思密达编不下去了!',
},
];

export default class FarmChildView extends 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 (
<View style={{flexDirection:'row',alignItems: 'center'}}>
<Image
source={eData.isMe==true? null:require('./res/headIcon/ox1.png')}
style={eData.isMe==true?null:styles.talkImg}
/>
<View style={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 (
<View style={ styles.container }>
<View style={styles.topView}>
<Text style={{fontSize:20,marginTop:15,color:'#f00'}}>Himi React Native 系列教程</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)}
/>

<View style={styles.bottomView}>

<View style={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)}
>
<View style={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
},
});

以上一共做了这么几件事:顶部添加一个标题

添加一个ListView

底部添加一个输入框和发送按钮

以上代码需要讲解的有几点: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 <View onLayout={(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});
}
}}/>
}
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就是想做一些区分,模仿聊天的对话形式,所以加的变量。大家也可以各种自定义的啦~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  listview chat himi