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

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

本篇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‘}}>
          <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 是当当点击发送按钮后,调用我们的自定义函数。

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

二:下面我们实现点击发送后,将用户在输入框内输入的内容添加到我们的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制作聊天/对话框&&获取组件实例常用的两种方式

时间: 2024-10-10 21:16:37

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

【REACT NATIVE 系列教程之二】创建自定义组件&&导入与使用示例

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2219.html 在上一篇  [REACT NATIVE 系列教程之一]触摸事件的两种形式与四种TOUCHABLE组件详解 中的最后介绍了如何使用Touchable的四种组件进行监听触摸事件.  那么紧接着我们利用Touchable来包装一个带图片的Button组件,且设计成可接受很多自定义参数. 一:创建我们自定义

【REACT NATIVE 系列教程之十二】REACT NATIVE(JS/ES)与IOS(OBJECT-C)交互通信

一用到跨平台的引擎必然要有引擎与各平台原生进行交互通信的需要.那么Himi先讲解React Native与iOS之间的通信交互. 本篇主要分为两部分讲解:(关于其中讲解的OC语法等不介绍,不懂的请自行学习) 1. React Native 访问iOS 2. iOS访问React Native     一:React Native 访问iOS 1. 我们想要JS调用OC函数,就要实现一个"RCTBridgeModule"协议的Objective-C类 所以首先我们先创建一个oc新类,  

【REACT NATIVE 系列教程之十一】插件的安装、使用与更新(示例:REACT-NATIVE-TAB-NAVIGATOR)

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2294.html 本篇主要来详细介绍如何安装.升级插件及讲解一个react-native-tab-navigator的示例. 首先推荐一个插件网站:https://www.npmjs.com     (此插件属于半官方维护的) 本文举例使用的插件:react-native-tab-navigator ,选项卡形式的导

菜鸟窝React Native 系列教程-1.移动端开发趋势与未来

菜鸟窝React Native 系列教程-1.移动端开发趋势与未来 课程持续更新中..... 我是RichardCao,现任新美大酒店旅游事业群的Android Developer.如果你也有兴趣录制RN视频,请加入下面QQ群找我. 下载地址:https://pan.baidu.com/s/1c1XmE56 密码:shhw 首发地址:菜鸟窝-ReactNative学习板块 交流QQ群:576089067 课程目录:菜鸟窝React Native 系列教程

【REACT NATIVE 系列教程之七】统一ANDROID与IOS两个平台的程序入口&&区分平台的组件简介

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2260.html       本篇介绍两个细节:       1. 关于如何将index.android.js 与index.ios.js统一管理起来.       2.  Platform 组件的简单介绍与使用   一:将index.android.js 与index.ios.js统一管理起来. 由于React本身

【REACT NATIVE 系列教程之一】触摸事件的两种形式与四种TOUCHABLE组件详解

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2203.html 本文是RN(React Native)系列教程第一篇,当然也要给自己的群做个广告:   React Native @Himi :126100395  刚创建的群,欢迎一起学习.讨论.进步. 本文主要讲解两点: 1.   PanResponder:触摸事件,用以获取用户手指所在屏幕的坐标(x,y)或触

【REACT NATIVE 系列教程之五】NAVIGATOR(页面导航)的基本使用与传参

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2248.html 今天介绍一种应用开发中常用的负责页面切换及导航功能的组件:Navigator 一:Navigator 对于页面导航其实主要功能就是:每个页面都知道本身应该切换到哪个页面,并且切到的页面会记录从哪里来,如果要返回的话,知道返回到哪个页面.这一切都不需要再用逻辑管理!而且每个页面之间也可以进行参数传递,

【REACT NATIVE 系列教程之八】不使用NAVIGATOR实现切换(页面)场景的两种形式(逻辑与MODAL)

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2285.html 在第五篇中介绍了使用导航Navigator进行(页面)场景的切换,但是对于一些需求其实是无法满足,或者说太浪费.例如: 1. 只是想做一个很简单的页面切换,并不需要记录从哪来-也不需要手势滑屏返回等,这时使用导航Navigator太浪费了. 2. 假设: a) 有个View 包括一个Navigato

【REACT NATIVE 系列教程之九】REACT NATIVE版本升级步骤与注意事项!

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2288.html 由于React Native处于快速迭代发展中,因此组件功能的扩展.语法的变更也将会有较大的区别,因此升级版本则属于务必掌握的了. 昨天Himi刚从0.23版本升级到0.26,升级的主要原因有两点: 1. 一些组件在最新版本中加入了很多新的属性,例如0.23版本中Modal动画没有最新的属性: an