react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面


Posted in Javascript onNovember 12, 2019

一、前言

9月,又到开学的季节。为每个一直默默努力的自己点赞!最近都沉浸在react native原生app开发中,之前也有使用vue/react/angular等技术开发过聊天室项目,另外还使用RN技术做了个自定义模态弹窗rnPop组件。

一、项目简述

基于react+react-native+react-navigation+react-redux+react-native-swiper+rnPop等技术开发的仿微信原生App界面聊天室——RN_ChatRoom,实现了原生app启动页、AsyncStorage本地存储登录拦截、集成rnPop模态框功能(仿微信popupWindow弹窗菜单)、消息触摸列表、发送消息、表情(动图),图片预览,拍摄图片、发红包、仿微信朋友圈等功能。

二、技术点

  • MVVM框架:react / react-native / react-native-cli
  • 状态管理:react-redux / redux页面导航:react-navigationrn
  • 弹窗组件:rnPop打包工具:webpack 2.0轮播组件:react-native-swiper
  • 图片/相册:react-native-image-picker
{
 "name": "RN_ChatRoom",
 "version": "0.0.1",
 "aboutMe": "QQ:282310962 、 wx:xy190310",
 "dependencies": {
  "react": "16.8.6",
  "react-native": "0.60.4"
 },
 "devDependencies": {
  "@babel/core": "^7.5.5",
  "@babel/runtime": "^7.5.5",
  "@react-native-community/async-storage": "^1.6.1",
  "@react-native-community/eslint-config": "^0.0.5",
  "babel-jest": "^24.8.0",
  "eslint": "^6.1.0",
  "jest": "^24.8.0",
  "metro-react-native-babel-preset": "^0.55.0",
  "react-native-gesture-handler": "^1.3.0",
  "react-native-image-picker": "^1.0.2",
  "react-native-swiper": "^1.5.14",
  "react-navigation": "^3.11.1",
  "react-redux": "^7.1.0",
  "react-test-renderer": "16.8.6",
  "redux": "^4.0.4",
  "redux-thunk": "^2.3.0"
 },
 "jest": {
  "preset": "react-native"
 }
}

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

◆ App全屏幕启动页splash模板

react-native如何全屏启动? 设置StatusBar顶部条背景为透明 translucent={true},并配合RN动画Animated

/**
 * @desc 启动页面
 */

import React, { Component } from 'react'
import { StatusBar, Animated, View, Text, Image } from 'react-native'

export default class Splash extends Component{
  constructor(props){
    super(props)
    this.state = {
      animFadeIn: new Animated.Value(0),
      animFadeOut: new Animated.Value(1),
    }
  }

  render(){
    return (
      <Animated.View style={[GStyle.flex1DC_a_j, {backgroundColor: '#1a4065', opacity: this.state.animFadeOut}]}>
        <StatusBar backgroundColor='transparent' barStyle='light-content' translucent={true} />

        <View style={GStyle.flex1_a_j}>
          <Image source={require('../assets/img/ic_default.jpg')} style={{borderRadius: 100, width: 100, height: 100}} />
        </View>
        <View style={[GStyle.align_c, {paddingVertical: 20}]}>
          <Text style={{color: '#dbdbdb', fontSize: 12, textAlign: 'center',}}>RN-ChatRoom v1.0.0</Text>
        </View>
      </Animated.View>
    )
  }

  componentDidMount(){
    // 判断是否登录
    storage.get('hasLogin', (err, object) => {
      setTimeout(() => {
        Animated.timing(
          this.state.animFadeOut, {duration: 300, toValue: 0}
        ).start(()=>{
          // 跳转页面
          util.navigationReset(this.props.navigation, (!err && object && object.hasLogin) ? 'Index' : 'Login')
        })
      }, 1500);
    })
  }
}

◆ RN本地存储技术async-storage

/**
 * @desc 本地存储函数
 */

import AsyncStorage from '@react-native-community/async-storage'

export default class Storage{
  static get(key, callback){
    return AsyncStorage.getItem(key, (err, object) => {
      callback(err, JSON.parse(object))
    })
  }

  static set(key, data, callback){
    return AsyncStorage.setItem(key, JSON.stringify(data), callback)
  }

  static del(key){
    return AsyncStorage.removeItem(key)
  }

  static clear(){
    AsyncStorage.clear()
  }
}

global.storage = Storage

声明全局global变量,只需在App.js页面一次引入、多个页面均可调用。

storage.set('hasLogin', { hasLogin: true })
storage.get('hasLogin', (err, object) => { ... })

◆ App主页面模板及全局引入组件

import React, { Fragment, Component } from 'react'
import { StatusBar } from 'react-native'

// 引入公共js
import './src/utils/util'
import './src/utils/storage'

// 导入样式
import './src/assets/css/common'
// 导入rnPop弹窗
import './src/assets/js/rnPop/rnPop.js'

// 引入页面路由
import PageRouter from './src/router'

class App extends Component{
 render(){
  return (
   <Fragment>
    {/* <StatusBar backgroundColor={GStyle.headerBackgroundColor} barStyle='light-content' /> */}

    {/* 页面 */}
    <PageRouter />

    {/* 弹窗模板 */}
    <RNPop />
   </Fragment>
  )
 }
}

export default App

◆ react-navigation页面导航器/地址路由、底部tabbar

由于react-navigation官方顶部导航器不能满足需求,如是自己封装了一个,功能效果有些类似微信导航。

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

export default class HeaderBar extends Component {
  constructor(props){
    super(props)
    this.state = {
      searchInput: ''
    }
  }

  render() {
    /**
     * 更新
     * @param { navigation | 页面导航 }
     * @param { title | 标题 }
     * @param { center | 标题是否居中 }
     * @param { search | 是否显示搜索 }
     * @param { headerRight | 右侧Icon按钮 }
     */
    let{ navigation, title, bg, center, search, headerRight } = this.props

    return (
      <View style={GStyle.flex_col}>
        <StatusBar backgroundColor={bg ? bg : GStyle.headerBackgroundColor} barStyle='light-content' translucent={true} />
        <View style={[styles.rnim__topBar, GStyle.flex_row, {backgroundColor: bg ? bg : GStyle.headerBackgroundColor}]}>
          {/* 返回 */}
          <TouchableOpacity style={[styles.iconBack]} activeOpacity={.5} onPress={this.goBack}><Text style={[GStyle.iconfont, GStyle.c_fff, GStyle.fs_18]}></Text></TouchableOpacity>
          {/* 标题 */}
          { !search && center ? <View style={GStyle.flex1} /> : null }
          {
            search ? 
            (
              <View style={[styles.barSearch, GStyle.flex1, GStyle.flex_row]}>
                <TextInput onChangeText={text=>{this.setState({searchInput: text})}} style={styles.barSearchText} placeholder='搜索' placeholderTextColor='rgba(255,255,255,.6)' />
              </View>
            )
            :
            (
              <View style={[styles.barTit, GStyle.flex1, GStyle.flex_row, center ? styles.barTitCenter : null]}>
                { title ? <Text style={[styles.barCell, {fontSize: 16, paddingLeft: 0}]}>{title}</Text> : null }
              </View>
            )
          }
          {/* 右侧 */}
          <View style={[styles.barBtn, GStyle.flex_row]}>
            { 
              !headerRight ? null : headerRight.map((item, index) => {
                return(
                  <TouchableOpacity style={[styles.iconItem]} activeOpacity={.5} key={index} onPress={()=>item.press ? item.press(this.state.searchInput) : null}>
                    {
                      item.type === 'iconfont' ? item.title : (
                        typeof item.title === 'string' ? 
                        <Text style={item.style ? item.style : null}>{`${item.title}`}</Text>
                        :
                        <Image source={item.title} style={{width: 24, height: 24, resizeMode: 'contain'}} />
                      )
                    }
                    {/* 圆点 */}
                    { item.badge ? <View style={[styles.iconBadge, GStyle.badge]}><Text style={GStyle.badge_text}>{item.badge}</Text></View> : null }
                    { item.badgeDot ? <View style={[styles.iconBadgeDot, GStyle.badge_dot]}></View> : null }
                  </TouchableOpacity>
                )
              })
            }
          </View>
        </View>
      </View>
    )
  }

  goBack = () => {
    this.props.navigation.goBack()
  }
}
// 创建底部TabBar
const tabNavigator = createBottomTabNavigator(
  // tabbar路由(消息、通讯录、我)
  {
    Index: {
      screen: Index,
      navigationOptions: ({navigation}) => ({
        tabBarLabel: '消息',
        tabBarIcon: ({focused, tintColor}) => (
          <View>
            <Text style={[ GStyle.iconfont, GStyle.fs_20, {color: (focused ? tintColor : '#999')} ]}></Text>
            <View style={[GStyle.badge, {position: 'absolute', top: -2, right: -15,}]}><Text style={GStyle.badge_text}>12</Text></View>
          </View>
        )
      })
    },
    Contact: {
      screen: Contact,
      navigationOptions: {
        tabBarLabel: '通讯录',
        tabBarIcon: ({focused, tintColor}) => (
          <View>
            <Text style={[ GStyle.iconfont, GStyle.fs_20, {color: (focused ? tintColor : '#999')} ]}></Text>
          </View>
        )
      }
    },
    Ucenter: {
      screen: Ucenter,
      navigationOptions: {
        tabBarLabel: '我',
        tabBarIcon: ({focused, tintColor}) => (
          <View>
            <Text style={[ GStyle.iconfont, GStyle.fs_20, {color: (focused ? tintColor : '#999')} ]}></Text>
            <View style={[GStyle.badge_dot, {position: 'absolute', top: -2, right: -6,}]}></View>
          </View>
        )
      }
    }
  },
  // tabbar配置
  {
    ...
  }
)

◆ RN聊天页面功能模块

1、表情处理:原本是想着使用图片表情gif,可是在RN里面textInput文本框不能插入图片,只能通过定义一些特殊字符 :66: (:12 [奋斗] 解析表情,处理起来有些麻烦,而且图片多了影响性能,如是就改用emoj表情符。

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

faceList: [
  {
    nodes: [
      '?','?','?','?','?','?','?',
      '?','?','?','?','?','?','?',
      '?','?','?','?','?','?','del',
    ]
  },
  ...
  {
    nodes: [
      '?','?','?','?','?','?','?',
      '?','?','?','?','?','??','??',
      '??','??','?‍?','??‍?','??‍?','??‍✈️','del',
    ]
  },
  ...
]

2、光标定位:在指定光标处插入内容,textInput提供了光标起始位置

let selection = this.textInput._lastNativeSelection || null;
this.textInput.setNativeProps({
selection : { start : xxx, end : xxx}
})

3、textInput判断内容是否为空,过滤空格、回车

isEmpty = (html) => {
return html.replace(/\r\n|\n|\r/, "").replace(/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, "") == ""
}
/**
 * 聊天模块JS----------------------------------------------------
 */
// ...滚动至聊天底部
scrollToBottom = (t) => {
  let that = this
  this._timer = setTimeout(() => {
    that.refs.scrollView.scrollToEnd({animated: false})
  }, t ? 16 : 0);
}

// ...隐藏键盘
hideKeyboard = () => {
  Keyboard && Keyboard.dismiss()
}

// 点击表情
handlePressEmotion = (img) => {
  if(img === 'del') return

  let selection = this.editorInput._lastNativeSelection || null;
  if (!selection){
    this.setState({
      editorText : this.state.editorText + `${img}`,
      lastRange: this.state.editorText.length
    })
  }
  else {
    let startStr = this.state.editorText.substr(0 , this.state.lastRange ? this.state.lastRange : selection.start)
    let endStr = this.state.editorText.substr(this.state.lastRange ? this.state.lastRange : selection.end)
    this.setState({
      editorText : startStr + `${img}` + endStr,
      lastRange: (startStr + `${img}`).length
    })
  } 
}

// 发送消息
isEmpty = (html) => {
  return html.replace(/\r\n|\n|\r/, "").replace(/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, "") == ""
}
handleSubmit = () => {
  // 判断是否为空值
  if(this.isEmpty(this.state.editorText)) return

  let _msg = this.state.__messageJson
  let _len = _msg.length
  // 消息队列
  let _data = {
    id: `msg${++_len}`,
    msgtype: 3,
    isme: true,
    avator: require('../../../assets/img/uimg/u__chat_img11.jpg'),
    author: '王梅(Fine)',
    msg: this.state.editorText,
    imgsrc: '',
    videosrc: ''
  }
  _msg = _msg.concat(_data)
  this.setState({ __messageJson: _msg, editorText: '' })

  this.scrollToBottom(true)
}


// >>> 【选择区功能模块】------------------------------------------
// 选择图片
handleLaunchImage = () => {
  let that = this

  ImagePicker.launchImageLibrary({
    // title: '请选择图片来源',
    // cancelButtonTitle: '取消',
    // takePhotoButtonTitle: '拍照',
    // chooseFromLibraryButtonTitle: '相册图片',
    // customButtons: [
    //   {name: 'baidu', title: 'baidu.com图片'},
    // ],
    // cameraType: 'back',
    // mediaType: 'photo',
    // videoQuality: 'high',
    // maxWidth: 300,
    // maxHeight: 300,
    // quality: .8,
    // noData: true,
    storageOptions: {
      skipBackup: true,
    },
  }, (response) => {
    // console.log(response)

    if(response.didCancel){
      console.log('user cancelled')
    }else if(response.error){
      console.log('ImagePicker Error')
    }else{
      let source = { uri: response.uri }
      // let source = {uri: 'data:image/jpeg;base64,' + response.data}
      that.setState({ imgsrc: source })

      that.scrollToBottom(true)
    }
  })
}

总结

以上所述是小编给大家介绍的react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
jQuery中append、insertBefore、after与insertAfter的简单用法与注意事项
Apr 04 Javascript
表单验证的完整应用案例探讨
Mar 29 Javascript
jquery对元素拖动排序示例
Jan 16 Javascript
js中settimeout方法加参数的使用实例
Feb 27 Javascript
JavaScript前端图片加载管理器imagepool使用详解
Dec 29 Javascript
jQuery获得document和window对象宽度和高度的方法
Mar 25 Javascript
如何使用jQuery技术开发ios风格的页面导航菜单
Jul 29 Javascript
IScroll5实现下拉刷新上拉加载的功能实例
Aug 11 Javascript
three.js中文文档学习之通过模块导入
Nov 20 Javascript
Angular实现的自定义模糊查询、排序及三角箭头标注功能示例
Dec 28 Javascript
jQuery表单元素过滤选择器用法实例分析
Feb 20 jQuery
JavaScript快速调试的两个技巧
Nov 04 Javascript
实现vuex与组件data之间的数据同步更新方式
Nov 12 #Javascript
uni-app自定义导航栏按钮|uniapp仿微信顶部导航条功能
Nov 12 #Javascript
基于vue+uniapp直播项目实现uni-app仿抖音/陌陌直播室功能
Nov 12 #Javascript
vuex管理状态 刷新页面保持不被清空的解决方案
Nov 11 #Javascript
vuex实现数据状态持久化
Nov 11 #Javascript
vue 更改连接后台的api示例
Nov 11 #Javascript
vue路由守卫,限制前端页面访问权限的例子
Nov 11 #Javascript
You might like
php面向对象全攻略 (十六) 对象的串行化
2009/09/30 PHP
php显示时间常用方法小结
2015/06/05 PHP
ThinkPHP函数详解之M方法和R方法
2015/09/10 PHP
php+ajax实现无刷新的新闻留言系统
2020/12/21 PHP
微信公众号模板消息群发php代码示例
2016/12/29 PHP
jQuery表单验证插件formValidator(改进版)
2012/02/03 Javascript
jQuery中closest()函数用法实例
2015/01/07 Javascript
使用js实现的简单拖拽效果
2015/03/18 Javascript
轻量级网页遮罩层jQuery插件用法实例
2015/07/31 Javascript
AngularJS 使用ng-repeat报错 [ngRepeat:dupes]
2017/01/19 Javascript
vue-resource 拦截器使用详解
2017/02/21 Javascript
JavaScript字符串转数字的5种方法及遇到的坑
2018/07/16 Javascript
vue+mousemove实现鼠标拖动功能(拖动过快失效问题解决方法)
2018/08/24 Javascript
浅入深出Vue之组件使用
2019/07/11 Javascript
Element-ui树形控件el-tree自定义增删改和局部刷新及懒加载操作
2020/08/31 Javascript
python计算时间差的方法
2015/05/20 Python
Python采用Django制作简易的知乎日报API
2016/08/03 Python
zookeeper python接口实例详解
2018/01/18 Python
python实现定时自动备份文件到其他主机的实例代码
2018/02/23 Python
python web自制框架之接受url传递过来的参数实例
2018/12/17 Python
NumPy 数组使用大全
2019/04/25 Python
Django上线部署之IIS的配置方法
2019/08/22 Python
python科学计算之scipy——optimize用法
2019/11/25 Python
Python如何爬取b站热门视频并导入Excel
2020/08/10 Python
使用OpenCV校准鱼眼镜头的方法
2020/11/26 Python
python程序实现BTC(比特币)挖矿的完整代码
2021/01/20 Python
借助HTML5 Canvas来绘制三角形和矩形等多边形的方法
2016/03/14 HTML / CSS
美国领先的医疗警报服务:Philips Lifeline
2018/03/12 全球购物
严选全球尖货,立足香港:Bonpont宝盆
2018/07/24 全球购物
英国票务网站:Ticketmaster英国
2018/08/27 全球购物
美国球迷装备的第一来源:FOCO
2020/07/03 全球购物
班主任寄语大全
2014/04/04 职场文书
2014年质量工作总结
2014/11/22 职场文书
2015年度企业工作总结
2015/05/21 职场文书
一起来学习Python的元组和列表
2022/03/13 Python
React自定义hook的方法
2022/06/25 Javascript