天天看点

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

补充说明:

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

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

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

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

先看效果图:

<a href="http://www.himigame.com/wp-content/uploads/2016/06/11221.jpg" target="_blank"></a>

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

<code>    </code><code>renderEveryData(eData) {</code>

<code>      </code><code>var</code> <code>sWidth = Dimensions.get(</code><code>'window'</code><code>).width</code>

<code>          </code><code>return</code> <code>(</code>

<code>              </code><code>&lt;View style={eData.isMe==</code><code>true</code><code>?styles.everyRowRight:styles.everyRow}&gt;</code>

<code>          </code><code>&lt;Image</code>

<code>            </code><code>source={eData.isMe==</code><code>true</code><code>? </code><code>null</code><code>:require(</code><code>'./res/headIcon/ox1.png'</code><code>)}</code>

<code>            </code><code>style={eData.isMe==</code><code>true</code><code>?</code><code>null</code><code>:styles.talkImg}</code>

<code>          </code><code>/&gt;</code>

<code>          </code><code>&lt;View style={{width:sWidth - 100}}&gt;</code>

<code>                    </code><code>&lt;View style={eData.isMe==</code><code>true</code><code>?styles.talkViewRight:styles.talkView}&gt;</code>

<code>              </code><code>&lt;Text style={ eData.isMe==</code><code>true</code><code>?styles.talkTextRight:styles.talkText }&gt;</code>

<code>                        </code><code>{eData.talkContent}</code>

<code>              </code><code>&lt;/Text&gt;</code>

<code>                    </code><code>&lt;/View&gt;</code>

<code>          </code><code>&lt;/View&gt;</code>

<code>            </code><code>source={eData.isMe==</code><code>true</code><code>? require(</code><code>'./res/headIcon/ox2.png'</code><code>) :</code><code>null</code><code>}</code>

<code>            </code><code>style={eData.isMe==</code><code>true</code><code>?styles.talkImgRight:</code><code>null</code><code>}</code>

<code>              </code><code>&lt;/View&gt;</code>

<code>          </code><code>);</code>

<code>      </code><code>}</code>

3. 用到的样式如下:

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

<code>  </code><code>everyRow:{</code>

<code>    </code><code>flexDirection:</code><code>'row'</code><code>,</code>

<code>    </code><code>alignItems: </code><code>'center'</code>

<code>  </code><code>},</code>

<code>  </code><code>everyRowRight:{</code>

<code>    </code><code>alignItems: </code><code>'center'</code><code>,</code>

<code>    </code><code>justifyContent:</code><code>'flex-end'</code>

<code>  </code><code>talkView: {</code>

<code>    </code><code>backgroundColor: </code><code>'white'</code><code>,</code>

<code>    </code><code>padding: 10,</code>

<code>    </code><code>borderRadius:5,</code>

<code>    </code><code>marginLeft:5,</code>

<code>    </code><code>marginRight:55,</code>

<code>    </code><code>marginBottom:10,</code>

<code>    </code><code>alignSelf:</code><code>'flex-start'</code><code>,</code>

<code>  </code><code>talkViewRight: {</code>

<code>    </code><code>backgroundColor: </code><code>'#90EE90'</code><code>,</code>

<code>    </code><code>marginLeft:55,</code>

<code>    </code><code>marginRight:5,</code>

<code>    </code><code>alignSelf:</code><code>'flex-end'</code><code>,</code>

<code>  </code><code>talkText: {</code>

<code>    </code><code>fontSize: 16,</code>

<code>    </code><code>fontWeight: </code><code>'bold'</code><code>,</code>

<code>    </code><code>},</code>

<code>  </code><code>talkTextRight: {</code>

<code>  </code><code>talkImg: {</code>

<code>    </code><code>height: 40,</code>

<code>    </code><code>width: 40,</code>

<code>    </code><code>marginLeft:10,</code>

<code>    </code><code>marginBottom:10</code>

<code>  </code><code>talkImgRight: {</code>

<code>    </code><code>marginRight:10,</code>

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.  获取组件实例常用的两种方式:

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

<code>render() {</code>

<code>    </code><code>return</code> <code>(</code>

<code>        </code><code>&lt;Text&gt;Himi&lt;/Text&gt;</code>

<code>    </code><code>)</code>

<code>}</code>

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

第一种:

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

第二种:

<code>    </code><code>var</code> <code>_text;</code>

<code>        </code><code>&lt;Text ref={(text) =&gt; { _text = text; }}&gt;</code>

<code>        </code><code>Himi</code>

<code>        </code><code>&lt;/Text&gt;</code>

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

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

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

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

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

<code>import React, {</code>

<code>  </code><code>Component</code>

<code>} from </code><code>'react'</code><code>;</code>

<code>import {</code>

<code>  </code><code>View,</code>

<code>  </code><code>Text,</code>

<code>  </code><code>TouchableHighlight,</code>

<code>  </code><code>Image,</code>

<code>  </code><code>PixelRatio,</code>

<code>  </code><code>ListView,</code>

<code>  </code><code>StyleSheet,</code>

<code>  </code><code>TextInput,</code>

<code>  </code><code>Alert,</code>

<code> </code><code>} from </code><code>'react-native'</code><code>;</code>

<code> </code> 

<code>var</code> <code>datas =[</code>

<code> </code><code>{</code>

<code>    </code><code>isMe:</code><code>false</code><code>,</code>

<code>    </code><code>talkContent:</code><code>'最近在学习React Native哦!'</code><code>,</code>

<code> </code><code>},</code>

<code>    </code><code>isMe:</code><code>true</code><code>,</code>

<code>    </code><code>talkContent:</code><code>'听说是个跨平台开发原生App的开源引擎'</code><code>,</code>

<code>  </code><code>{</code>

<code>    </code><code>talkContent:</code><code>'嗯啊,很不错,可以尝试下吧。过了这段时间继续研究UE去了。唉~技术出身,就是放不下技术呀~'</code><code>,</code>

<code>    </code><code>talkContent:</code><code>'感觉编不下去对话了呀......感觉编不下去对话了呀......感觉编不下去对话了呀......感觉编不下去对话了呀......'</code><code>,</code>

<code>    </code><code>talkContent:</code><code>'无语!'</code><code>,</code>

<code>    </code><code>talkContent:</code><code>'自说自话,好难!随便补充点字数吧,嗯 就酱紫 :) '</code><code>,</code>

<code>    </code><code>talkContent:</code><code>'感觉编不下去对话了呀......感觉编不下去对话了呀..'</code><code>,</code>

<code>    </code><code>talkContent:</code><code>'GG,思密达编不下去了!'</code><code>,</code>

<code>];</code>

<code>export </code><code>default</code> <code>class FarmChildView extends React.Component {</code>

<code>    </code><code>constructor(props) {</code>

<code>        </code><code>super</code><code>(props);</code>

<code>        </code><code>this</code><code>.state = {</code>

<code>          </code><code>inputContentText:</code><code>''</code><code>,</code>

<code>          </code><code>dataSource: </code><code>new</code> <code>ListView.DataSource({</code>

<code>            </code><code>rowHasChanged: (row1, row2) =&gt; row1 !== row2,</code>

<code>          </code><code>}),</code>

<code>        </code><code>};</code>

<code>        </code><code>this</code><code>.listHeight = 0;</code>

<code>        </code><code>this</code><code>.footerY = 0;</code>

<code>    </code><code>}</code>

<code>    </code><code>componentDidMount() {</code>

<code>        </code><code>this</code><code>.setState({</code>

<code>            </code><code>dataSource: </code><code>this</code><code>.state.dataSource.cloneWithRows(datas)</code>

<code>        </code><code>});</code>

<code>   </code><code>return</code> <code>(</code>

<code>   </code><code>&lt;View style={{flexDirection:</code><code>'row'</code><code>,alignItems: </code><code>'center'</code><code>}}&gt;</code>

<code>   </code><code>&lt;View style={eData.isMe==</code><code>true</code><code>?styles.talkViewRight:styles.talkView}&gt;</code>

<code>            </code><code>&lt;Text style={ styles.talkText }&gt;</code>

<code>               </code><code>{eData.talkContent}</code>

<code>            </code><code>&lt;/Text&gt;</code>

<code>   </code><code>&lt;/View&gt;</code>

<code>   </code><code>);</code>

<code>   </code><code>}</code>

<code>    </code><code>myRenderFooter(e){</code>

<code>    </code><code>pressSendBtn(){</code>

<code>    </code><code>render() {</code>

<code>        </code><code>return</code> <code>(</code>

<code>            </code><code>&lt;View style={ styles.container }&gt;</code>

<code>              </code><code>&lt;View style={styles.topView}&gt;</code>

<code>                </code><code>&lt;Text style={{fontSize:20,marginTop:15,color:</code><code>'#f00'</code><code>}}&gt;Himi React Native 系列教程&lt;/Text&gt;</code>

<code>              </code><code>&lt;ListView</code>

<code>                </code><code>ref=</code><code>'_listView'</code>

<code>                </code><code>onLayout={(e)=&gt;{</code><code>this</code><code>.listHeight = e.nativeEvent.layout.height;}}</code>

<code>                </code><code>dataSource={</code><code>this</code><code>.state.dataSource}</code>

<code>                </code><code>renderRow={</code><code>this</code><code>.renderEveryData.bind(</code><code>this</code><code>)}</code>

<code>                </code><code>renderFooter={</code><code>this</code><code>.myRenderFooter.bind(</code><code>this</code><code>)}</code>

<code>              </code><code>/&gt;</code>

<code>              </code><code>&lt;View style={styles.bottomView}&gt;</code>

<code>                </code><code>&lt;View style={styles.searchBox}&gt;</code>

<code>                  </code><code>&lt;TextInput</code>

<code>                      </code><code>ref=</code><code>'_textInput'</code>

<code>           </code><code>onChangeText={(text) =&gt;{</code><code>this</code><code>.state.inputContentText=text}}</code>

<code>                      </code><code>placeholder=</code><code>' 请输入对话内容'</code>

<code>                      </code><code>returnKeyType=</code><code>'done'</code>

<code>                      </code><code>style={styles.inputText}</code>

<code>                  </code><code>/&gt;</code>

<code>                </code><code>&lt;/View&gt;</code>

<code>                </code><code>&lt;TouchableHighlight</code>

<code>                  </code><code>underlayColor={</code><code>'#AAAAAA'</code><code>}</code>

<code>                  </code><code>activeOpacity={0.5}</code>

<code>                  </code><code>onPress={</code><code>this</code><code>.pressSendBtn.bind(</code><code>this</code><code>)}</code>

<code>                </code><code>&gt;</code>

<code>                  </code><code>&lt;View style={styles.sendBtn}&gt;</code>

<code>                    </code><code>&lt;Text style={ styles.bottomBtnText }&gt;</code>

<code>                       </code><code>发送</code>

<code>                    </code><code>&lt;/Text&gt;</code>

<code>           </code><code>&lt;/View&gt;</code>

<code>                </code><code>&lt;/TouchableHighlight&gt;</code>

<code>            </code><code>&lt;/View&gt;</code>

<code>        </code><code>);</code>

<code>var</code> <code>styles = StyleSheet.create({</code>

<code>  </code><code>container: {</code>

<code>    </code><code>flex: 1,</code>

<code>    </code><code>backgroundColor: </code><code>'#EEEEEE'</code>

<code>  </code><code>topView:{</code>

<code>    </code><code>backgroundColor: </code><code>'#DDDDDD'</code><code>,</code>

<code>    </code><code>height: 52,</code>

<code>    </code><code>padding:5</code>

<code>  </code><code>bottomView:{</code>

<code>    </code><code>flexDirection: </code><code>'row'</code><code>,</code>

<code>  </code><code>sendBtn: {</code>

<code>    </code><code>backgroundColor: </code><code>'#FF88C2'</code><code>,</code>

<code>    </code><code>height:40,</code>

<code>  </code><code>bottomBtnText: {</code>

<code>    </code><code>fontSize: 18,</code>

<code>    </code><code>justifyContent: </code><code>'flex-end'</code><code>,</code>

<code>  </code><code>searchBox: {</code>

<code>    </code><code>flex:1,  </code><code>// 类似于android中的layout_weight,设置为1即自动拉伸填充</code>

<code>    </code><code>borderRadius: 5,  </code><code>// 设置圆角边</code>

<code>    </code><code>marginTop:10,</code>

<code>  </code><code>inputText: {</code>

<code>    </code><code>flex:1,</code>

<code>    </code><code>backgroundColor: </code><code>'transparent'</code><code>,</code>

<code>    </code><code>fontSize: 20,</code>

<code>    </code><code>marginLeft:5</code>

<code>});</code>

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

顶部添加一个标题

添加一个ListView

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

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

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

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

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

(作用后续讲)

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

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

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

<a href="http://www.himigame.com/wp-content/uploads/2016/06/user19118.gif" target="_blank"></a>

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

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

<code>pressSendBtn(){</code>

<code>      </code><code>if</code><code>(</code><code>this</code><code>.state.inputContentText.trim().length &lt;= 0){</code>

<code>        </code><code>Alert.alert(</code><code>'提示'</code><code>, </code><code>'输入的内容不能为空'</code><code>);</code>

<code>        </code><code>return</code><code>;</code>

<code>      </code><code>datas.push({</code>

<code>        </code><code>isMe:</code><code>false</code><code>,</code>

<code>        </code><code>talkContent:</code><code>this</code><code>.state.inputContentText,</code>

<code>      </code><code>});</code>

<code>      </code><code>this</code><code>.refs._textInput.clear();</code>

<code>      </code><code>this</code><code>.setState({</code>

<code>          </code><code>dataSource: </code><code>this</code><code>.state.dataSource.cloneWithRows(datas)</code>

<code>      </code><code>})</code>

1. if(  this.state.inputContentText.trim().length &lt;= 0 )

inputContentText用来记录用户在输入框输入的内容,因此这里我们先对内容是否为空进行判定!

trim () 函数不多说了吧,去掉字符串首尾空格。纯空格的内容也不允许发送~

   2. datas.push 

这里是我们将新的数据添加到ListView中,其中文字内容就是我们记录的用户输入的内容

   3. this.refs._textInput.clear()

这里就是我们一开始准备工作介绍的小3节,通过this.refs._textInput()来获取我们定义的TextInput组件实例。

   4. 最后我们调用了 this.setState函数来对其两个变量进行修改:

inputContentText :把记录用户刚才输入在聊天框内的内容清空。

dataSource:更新ListView的数据,因为我们刚添加了一条数据

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

<a href="http://www.himigame.com/wp-content/uploads/2016/06/user191218.gif" target="_blank"></a>

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

<code> </code><code>myRenderFooter(e){</code>

<code>      </code><code>return</code> <code>&lt;View onLayout={(e)=&gt; {</code>

<code>         </code><code>this</code><code>.footerY= e.nativeEvent.layout.y;</code>

<code>         </code><code>if</code> <code>(</code><code>this</code><code>.listHeight &amp;&amp; </code><code>this</code><code>.footerY &amp;&amp;</code><code>this</code><code>.footerY&gt;</code><code>this</code><code>.listHeight) {</code>

<code>           </code><code>var</code> <code>scrollDistance = </code><code>this</code><code>.listHeight - </code><code>this</code><code>.footerY;</code>

<code>           </code><code>this</code><code>.refs._listView.scrollTo({y:-scrollDistance});</code>

<code>         </code><code>}</code>

<code>       </code><code>}}/&gt;</code>

1. 首先我们先绘制一个0高度的view : return &lt;View/&gt;

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 &amp;&amp; this.footerY &amp;&amp;this.footerY&gt;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进行动画滚动操作!

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

<a href="http://www.himigame.com/wp-content/uploads/2016/06/user324.gif" target="_blank"></a>

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

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

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

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

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