天天看點

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,如需轉載請自行聯系原作者