+7

Messenger với React Native!

Hello các bạn, nhân tiện facebook ra thêm tính năng Dating và Secret Crush mình cũng thử cover lại app Messenger 😃)) của họ sử dụng React Native xem sao, và dưới đây là kết quả của mình.

Bài viết này tập chung về phía giao diện cho các bạn có cái nhìn chung trước!

Oke bắt đầu, đầu tiên mình cần tạo 1 app, nếu các bạn còn chưa cài React Native thì có thể xem lại bài trước của mình , mình có hướng dẫn cài đặt React Native từng bước một nên các bạn yên tâm nhá:

  • react-native init MessengerRN

1. Khung chương trình

Ta tạo 1 thư mục App cho dễ quản lý, đầu tiên cần phải sửa lại file App.js của project:

import App from './App/index'

export default App 

import React, { Component } from 'react'
import { View, FlatList, Text } from 'react-native'
import data from './data'
import Item from './Item'
import styles from './style'

class Home extends Component {
   render() {
       return(
           <View style={styles.wrapper}>
               <View>
                   <Text style={styles.title}>My Crush</Text>
               </View>
               <FlatList
                   ref={"flatList"}
                   data={ data }
                   renderItem={({ item }) => (
                       <Item item={ item } />
                   )}
                   keyExtractor={(item) => item.id } // tránh trùng các item với nhau
                   parentFlatList={this} //để lát làm swipe left và swipe right
               />
           </View>
       )
   }
}

export default Home

Ở trên mình có tạo ra 1 list item, và truyền từng Item sang 1 component khác cho dễ quản lý

renderItem={({ item }) => (
  <Item item={ item } />
)}

Các bạn cũng thấy mình tách style ra làm một file riêng, các bạn cứ thử tưởng tượng về sau mình maintain 1 feature mà tất cả code trong 1 file xem sao, oải thật sự =)).

import { StyleSheet, Dimensions } from 'react-native'

const { width } = Dimensions.get('window')

export default styles = StyleSheet.create({
   wrapper: {
       flex: 1,
       backgroundColor: 'white'
   },
   title: {
       fontSize: 24,
       fontWeight: 'bold',
       color: 'black',
       padding: 20
   },
   container: {
       flex: 1,
       flexDirection: 'row',
       paddingHorizontal: 10,
       paddingVertical: 5,
   },
})

MÌnh cũng có tạo sẵn data cho chúng ta test rồi đây 😙😙

export default data = [
   {
       id: 1,
       avatar: 'http://woridnews.com/wp-content/uploads/2016/10/cd3e35dbcf23269780779b3f7b9e2fcc.png',
       name: 'Crush số 1',
       description: 'Crush số 1 waved at you!'
   },
   {
       id: 2,
       avatar: 'http://sphm-female-site-production.s3-ap-southeast-1.amazonaws.com/2017/07/w111-750x500.jpg',
       name: 'Crush số 2',
       description: 'Em à, Em đã đánh cắp trái tim anh. Vì thế anh sẽ đánh cắp nụ hôn của em'
   },
   {
       id: 3,
       avatar: 'https://bellanyc.com/wp-content/uploads/2017/06/blake-lively.jpg',
       name: 'Crush số 3',
       description: 'Em là nguồn cảm hứng đằng sau tất cả những gì anh làm, làm nguồn gốc của những điều tốt lành trong cuộc sống của anh'
   },
   {
       id: 4,
       avatar: 'https://d1o7cxaf8di5ts.cloudfront.net/file/brand/member-girlcrush-BM.jpg?d=200',
       name: 'Crush số 4',
       description: 'Cảm ơn chúa, bởi người đã gửi nữ thần xinh đẹp nhất của thiên đường vào cuộc sống của con.'
   },
   {
       id: 5,
       avatar: 'https://pbs.twimg.com/profile_images/652669289326092288/RsXc7UnS_400x400.jpg',
       name: 'Crush số 5',
       description: 'Anh rất hạnh phúc vì được gặp em, em là điều tuyệt vời nhất trong cuộc sống của anh.'
   },
   {
       id: 6,
       avatar: 'https://qph.fs.quoracdn.net/main-qimg-bd14ad5123f2a0b5b7fd457a18e23de8',
       name: 'Crush số 6',
       description: 'Khi anh yêu em, trái tim em trở nên ấm áp. Hãy để tình yêu ngọt ngào của chúng ta lớn lên theo từng ngày'
   },
   {
       id: 7,
       avatar: 'https://66.media.tumblr.com/2ffbcea054ae96a839d0583f4c56ce38/tumblr_ots4vdLKgl1w0bqvso2_250.gif',
       name: 'Crush số 7',
       description: 'Giữa chúng có một sự sợi dây. Nó buộc trái tim chúng ta lại với nhau vì vậy chúng ta luôn cảm thấy gần nhau dù có cách xa như thế nào?'
   },
   {
       id: 8,
       avatar: 'http://static.global.mnet.com/data/ucc/000/132/089',
       name: 'Crush số 8',
       description: 'Anh muốn tặng em trái tim này và em hãy giữ nó, bởi anh rất vụng về, anh sợ rằng anh sẽ làm mất hoặc dễ dàng tặng nó cho một ai khác'
   },
   {
       id: 9,
       avatar: 'https://qph.fs.quoracdn.net/main-qimg-bd14ad5123f2a0b5b7fd457a18e23de8',
       name: 'Crush số 9',
       description: 'Nếu em dám, hãy nắm lấy tay anh và dẫn anh đến trái tim của em. Anh muốn cảm nhận tình yêu của em.'
   }, 
   {
       id: 10,
       avatar: 'https://data.whicdn.com/images/148584794/large.jpg',
       name: 'Crush số 10',
       description: 'Anh thức dậy vào mỗi buổi sáng với sự phấn khích của một đứa trẻ vào ngày Giáng sinh, chỉ để biết rằng anh vẫn ở cạnh em.'
   }
];

Oke giờ hiện thị từng Item ra thôi, trước đó các bạn cài giúp mình 2 lib:

  • react-native install react-native-swipeout
  • react-native install react-native-vector-icons

react-native-swipeout là 1 lib hỗ trợ chúng ta có thể scroll sang trái, phải item, và nó chứa nhiều component bên trong phù hợp với mục đích của mình hiện tại.

react-native-vector-icons: là 1 lib hỗ trợ rất nhiều icon như: FontAwesome, IonIcons, MaterialIcons, AntDesign, ... các bạn qua 2 docs này lib này để hiểu thêm.

import React, { Component } from 'react'
import { View, Text, Image ,TouchableOpacity } from 'react-native'
import Swipeout from 'react-native-swipeout'
import Icon from 'react-native-vector-icons/FontAwesome5'
import styles from './style'

class Item extends Component {
   render() {
       const {item} = this.props

       return (
           <TouchableOpacity>
               <View style={styles.container}>
                   <View style={styles.bgAvatar}>
                       <Image 
                           source={{uri: item.avatar}}
                           style={styles.avatar}
                       />
                   </View>
                   <View style={styles.info}>
                       <Text style={styles.name}>{item.name}</Text>
                       <Text numberOfLines={1}>{item.description}</Text>
                   </View>
                   <View style={styles.bgSeen}>
                       <Image 
                           source={{uri: item.avatar}}
                           style={styles.avatarSeen}
                       />
                   </View>
               </View>
           </TouchableOpacity>
       )
   }
}

export default Item

thêm 1 chút style

...

bgAvatar: {
       flex: 2
   },
   avatar:{
       width: width*15/100,
       height: width*15/100,
       borderRadius: width*10/100,
   },
   info: {
       flex: 8,
       flexDirection: 'column',
       paddingLeft: 10,
       justifyContent: 'center'

   },
   name: {
       fontWeight: 'bold',
       color: 'black',
       fontSize: 16,
       paddingBottom: 3
   },
   bgSeen: {
       flex: 2,
       alignItems: 'center',
       justifyContent: 'center'
   },
   avatarSeen: {
       width: width*5/100,
       height: width*5/100,
       borderRadius: width*2.5/100,
   },

Oke, giờ ta thử run nên và xem kết quả nào 😄 😄, và đây chúng ta đã có giao diện khá giống với Messenger.

2. Swipeout

Muốn sử dụng Swipeout ta cần bọc tất cả code trong nó:

return {
    <Swipeout 
       {...swipeSettings}  
       backgroundColor='white'
   >
       <TouchableOpacity>
           ...
       </TouchableOpacity>
   </Swipeout>
}

Mình sẽ cấu hình tất cả trong swipeSettings

    ...
constructor(props) {
    super(props);
    this.state = {
        activeRowKey: null, //set item active
        numberOfRefresh: 0,
    };
}

_onOpen = () => {
    this.setState({
        activeRowKey: this.props.item.key
    });
}
_onClose = () => {
    if(this.state.activeRowKey != null) {
        this.setState({
            activeRowKey: null
        });
    }
}

render() {
    ...
    const swipeSettings = {
        autoClose: true, //sẽ tự động đóng khi ta click vào buton nào đó trong item được swipe
        onOpen: this._onOpen, //khi open swipe thì nên set row nào được active để tránh nhầm lẫn khi ta click sự kiện bên trong các item.
        onClose: this._onClose, //xóa row active
        
        //tiếp theo ta sẽ làm swipe phía bên trái
        left: [
                {
                    onPress: () => {},
                    component: (
                        <View style={styles.item}>
                            <View style={[styles.inItem, {backgroundColor: '#45B8AC'}]}>
                                <Icon name="camera" style={[styles.icon, {color: 'white'}]} />
                            </View>
                        </View>
                    ),
                    backgroundColor: 'white'
                },
                {
                    onPress: () => {},
                    component: (
                        <View style={styles.item}>
                            <Icon name="phone" style={styles.icon} />
                        </View>
                    ),
                    backgroundColor: 'white'
                },
                {
                    onPress: () => {},
                    component: (
                        <View
                            style={styles.item}
                        >
                            <Icon name="video" style={styles.icon} />
                        </View>
                    ),
                    backgroundColor: 'white'
                }
            ],
            rowId: this.props.index,
            sextionId: 1
       };
}

    return(
        ...
    )
}

Mình giải thích 1 chút về phần left phía trên:

gồm có 1 button đó là: camera, call, video, mỗi 1 {} là 1 button.

trong left có rất nhiều props như

  • component: ta có thể tạo component trong đó, truyền style thoải mái
  • backgroundColor: set backgroundColor cho button đó.
  • onPress: tương tự như props của button

Và còn nhiều props hữu ích các bạn có thể tham khảo trên Docs.

À chút thì quên mình cần thêm 1 chút style nhá:

...

item: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 100
},
inItem: {
    alignItems: 'center',
    justifyContent: 'center',
    width: 40, 
    height: 40, 
    borderRadius: 20
},
icon: {
    fontSize: width*6/100,
}

Oke Các bạn thử run nên à scroll Item sang bên trái và trải nghiệm nào

Tiếp theo chúng ta làm phần bên phải cũng tương tự với các button lần lượt là menu, notify, delete:


...
    left: [...],
    right: [
            {
                onPress: () => {},
                component: (
                    <View style={styles.item}>
                        <Icon name="bars" style={styles.icon} />
                    </View>
                ),
                backgroundColor: 'white'
            },
            {
                onPress: () => {},
                component: (
                    <View style={styles.item}>
                        <Icon name="bell" style={styles.icon} />
                    </View>
                ),
                backgroundColor: 'white'
            },
            {
                onPress: () => {},
                component: (
                    <View style={styles.item}>
                        <View style={[styles.inItem, {backgroundColor: '#E94B3C'}]}>
                            <Icon name="trash" style={[styles.icon, {color: 'white'}]} />
                        </View>
                    </View>
                ),
                backgroundColor: 'white',                   
            }
        ],
    ...

Và kết quả đã gần giống với những gì chúng ta mong muốn rồi phải không? 😄 Các bạn có để ý trên Messenger khi giữ lâu 1 message thì message đó swipe sang bên phải không 😄, cái này cũng dễ thôi.

Đơn giản bạn chỉ cần, thêm 1 props openRight có trong component root của lib, và check xem khi nào user giữ item 1 thời gian thì sẽ swipe sang bên phải

 constructor(props) {
        super(props);
        this.state = {
            activeRowKey: null,
            numberOfRefresh: 0,
            isRightOpen: false
        };
    }
    
_onLongPress = () => {
    this.setState({
        isRightOpen: true
    })
}

__scroll = () => {
    this.setState({
        isRightOpen: false
    })
}
    
    ...
    return (
            <Swipeout 
                {...swipeSettings}  
                openRight={this.state.isRightOpen}
                backgroundColor='white'
                scroll={this.__scroll}
            >
                <TouchableOpacity
                    onLongPress={this._onLongPress}
                >
                   ...
                </TouchableOpacity>
            </Swipeout>
        )       
    ...
    

Bài viết này của mình xin kết thúc tại đây, Thank you for watching!! 😉, mình rất hy vọng có thời gian để hoàn thành các chức năng còn lại, mong mọi người ủng hộ mình.

À quên nếu các bạn có suggest thêm feature nào thì comment cho mình biết nhá 😄 😄


All rights reserved

Bình luận

Đăng nhập để bình luận
Avatar
@xdangminhtruongx
thg 5 16, 2019 2:35 SA

(nguongmo)

Avatar
@longnk1301
thg 5 16, 2019 2:47 SA

đừng ngưỡng mộ crush của em 😦

Avatar
@Meoo
thg 8 16, 2019 8:04 SA

Em thắc mắc là bên index.js anh export default App. Nhưng trong App/index.js thì anh lại ko có class App. vậy làm sao nó chạy đc nhỉ?

Avatar
@longnk1301
thg 8 16, 2019 9:23 SA

Thank bạn đã quan tâm nhá!  Đây là cú pháp JSX bạn nhá, trong ES6 sẽ có 2 loài export: export hoặc export default

  • export default: trong 1 file chỉ có duy nhất 1 export default, dùng để export mặc định của file
  • export: trong 1 file có thể export nhiều variable, function, component
const A = return <View>...</View>

export default  A

bạn muốn sử dụng nó thì cần import theo cú pháp sau

import ABC from './path/..' // không quan trọng tên import là gì vì ta dùng export default

Với trường hợp thứ 2 bạn sử dụng export:

const A = return <View>...</View>

export  A

bạn sẽ cần import theo cách khác:

import { A } from './path/..' // cần import đúng tên biến mà mình muốn lấy
Avatar
@Meoo
thg 10 6, 2019 8:36 SA

Mấy bữa mình cũng ddnag vướng chỗ import { } hay import không co ngoặc. Cám ơn bác đã giải thích ạ

Avatar
@tnh
thg 10 28, 2019 7:41 SA

Screen Shot 2019-10-28 at 14.39.19.png

Cho em hỏi lỗi này là sao ạ?

Xem thêm (7)
Avatar
@longnk1301
thg 10 28, 2019 7:59 SA

à chắc mình hightlight tên file sai chỗ này, cảm ơn bạn nhá, bạn cứ tham khảo link git ở trên cho chính xác nhá

Avatar
@tnh
thg 10 28, 2019 8:01 SA

Mình cảm ơn ạ!

Avatar
+7
Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí