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

简介:

补充说明:

一:很多童鞋问,键盘调出来被挡住了,那么下面给出三个解决方案:

1. 在render最外层包一个ScrollView,然后当键盘调出时,scrollTo即可实现。

2. 在底部添加一个可变化高度的view,根据键盘获取、失去焦点时,进行处理实现

3. 使用插件:react-native-keyboard-spacer :https://github.com/Andr3wHur5t/react-native-keyboard-spacer

二:有的童鞋说对话框的背景没有根据内容长短自适应,OK ,下面给出自动适应的样式与修改:

先看效果图:

11221

1. 导入一个组件:Dimensions

2. 我们先将 renderEveryData 的函数改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     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. 用到的样式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
   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#content

2. 学习下:TextInput:

官方文档:http://reactnative.cn/docs/0.27/textinput.html#content

3.  获取组件实例常用的两种方式:

有时候,渲染出来的组件,我们需要拿到它的实例进行调用其函数等操作。假设有如下代码段:

1
2
3
4
5
render() {
     return  (
         <Text>Himi</Text>
     )
}


如上,如果我们想要拿到这个Text组件的实例对象,有如下两种形式:

第一种:

1
2
3
4
5
render() {
     return  (
         <Text>Himi</Text>
     )
}


使用时:this.refs._text ,通过this.refs进行获取。

第二种:

1
2
3
4
5
6
7
8
render() {
     var  _text;
     return  (
         <Text ref={(text) => { _text = text; }}>
         Himi
         </Text>
     )
}


使用时:_text ,直接用这个变量即可。

如上都有了一定了解时,那么下面我们进行本篇的正题:

  制作一个对话、聊天框,内容可滚动,且最新的消息永远保持在最底部显示!

一:首先我们先简单布局一个聊天场景,布局+各种小组件的使用(代码简单,不多说):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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
   },
});


以上一共做了这么几件事:

  1. 顶部添加一个标题

  2. 添加一个ListView

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

以上代码需要讲解的有几点:

1. inputContentText 这个state中的变量用于记录用户在TextInput输入的内容

2.  this.listHeight = 0; 获取到ListHeight的高度

this.footerY = 0; 记录ListView内容的最底部的Y位置。

(作用后续讲)

3.  myRenderFooter(e){} 这里是当ListView的 renderFooter 函数触发时候调用的。(作用后续讲)

4. pressSendBtn 是当当点击发送按钮后,调用我们的自定义函数。

先看下布局后的效果图(点击查看动态效果):

user19118

二:下面我们实现点击发送后,将用户在输入框内输入的内容添加到我们的ListView上,并重绘!

主要处理逻辑,Himi已经设计好了,就是在 pressSendBtn 函数中处理即可,处理代码段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的数据,因为我们刚添加了一条数据

 效果图如下(点击查看动态效果):

user191218

三:让新的数据永远展示在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中处理即可,如下代码:

1
2
3
4
5
6
7
8
9
10
  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进行动画滚动操作!

截此,我们的聊天、对话框完成,效果图如下(点击查看动态图):

user324

   备注:每一行数据中Himi都定义了一个 isMe 的字段,这里来表示说话是对方还是自己。

isMe = true :  头像在右边,说话底为绿色。

    isMe =false : 头像放左侧,说话底为白色。

    其实这里Himi就是想做一些区分,模仿聊天的对话形式,所以加的变量。大家也可以各种自定义的啦~





本文转自 xiaominghimi 51CTO博客,原文链接:http://blog.51cto.com/xiaominghimi/1787122,如需转载请自行联系原作者

目录
相关文章
|
5月前
|
缓存 前端开发 JavaScript
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
🌟蒋星熠Jaxonic,前端探索者。专注React Hooks深度实践,从原理到实战,分享状态管理、性能优化与自定义Hook精髓。助力开发者掌握函数组件的无限可能,共赴技术星辰大海!
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
|
10月前
|
缓存 前端开发 数据安全/隐私保护
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
352 68
|
10月前
|
缓存 前端开发 Java
在 React 中,组合组件和高阶组件在性能方面有何区别?
在 React 中,组合组件和高阶组件在性能方面有何区别?
324 67
|
10月前
|
前端开发 JavaScript 安全
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
400 62
|
8月前
|
前端开发 JavaScript 编译器
React编程新手入门实践教程
本书深入解析React核心思想与设计哲学,涵盖组件化思维、虚拟DOM原理及JSX本质,探讨函数组件与类组件特性,详解状态管理、生命周期控制及事件处理机制,帮助开发者掌握高效构建用户界面的技巧。
239 1
|
编解码 前端开发 开发者
React 图片组件样式自定义:常见问题与解决方案
在 React 开发中,图片组件的样式自定义常因细节问题导致布局错乱、性能损耗或交互异常。本文系统梳理常见问题及解决方案,涵盖基础样式应用、响应式设计、加载状态与性能优化等,结合代码案例帮助开发者高效实现图片组件的样式控制。重点解决图片尺寸不匹配、边框阴影不一致、移动端显示模糊、加载失败处理及懒加载等问题,并总结易错点和最佳实践,助力开发者提升开发效率和用户体验。
435 22
|
移动开发 前端开发 Android开发
《React Native移动开发实战》出版啦
对不起,我来晚了 首先要感谢支持和关注我的朋友,感谢人邮的赵老师,还有公司的领导和同事,他们在我写作的过程中给了很多有用的信息,也给了很多有用的建议,为本书的写作提供了很大帮助。感谢,再次感谢!!! 工作6年多以来,一直想写一本自己的书,一方面是对自己工作经历的一个总结,也是对希望写一本书给曾经的自己一个交代,毕竟30岁的人了,搞不了几年的技术了。
1993 0
|
移动开发 前端开发 索引
《React Native移动开发实战》一一3.3 完善轮播广告——Image组件
本节书摘来自华章出版社《React Native移动开发实战》一 书中的第3章,第3.3节,作者:袁林 著 ,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2050 0
|
新零售 移动开发 前端开发
《React Native移动开发实战》一一3.9 小 结
本节书摘来自华章出版社《React Native移动开发实战》一 书中的第3章,第3.9节,作者:袁林 著 ,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1613 0