【REACT NATIVE 系列教程之十三】利用LISTVIEW与TEXTINPUT制作聊天/对话框获取组件实例常用的两种方式...
本站文章均為?李華明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. 導入一個組件:Dimensions
2. 我們先將?renderEveryData 的函數改為如下:
????renderEveryData(eData)?{var?sWidth?=?Dimensions.get('window').widthreturn?(<View?style={eData.isMe==true?styles.everyRowRight:styles.everyRow}><Imagesource={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><Imagesource={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#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?(<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'}}><Imagesource={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><Imagesource={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><ListViewref='_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}><TextInputref='_textInput'onChangeText={(text)?=>{this.state.inputContentText=text}}placeholder='?請輸入對話內容'returnKeyType='done'style={styles.inputText}/></View><TouchableHighlightunderlayColor={'#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就是想做一些區分,模仿聊天的對話形式,所以加的變量。大家也可以各種自定義的啦~
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的【REACT NATIVE 系列教程之十三】利用LISTVIEW与TEXTINPUT制作聊天/对话框获取组件实例常用的两种方式...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 十四周进度报告
- 下一篇: elixir 高可用系列(五) Supe