Tạo animation đơn giản cho React Native ScrollView

0
379
Bài này thuộc phần 9 của 9 phần trong series React Native Training cho người mới

Ở bài viết trước, mình có chia sẻ cách sử dụng LayoutAnimation để tạo hiệu ứng cho ứng dụng React Native. Bài viết này mình chia sẻ một tip đặc biệt tạo Animation dành riêng cho React Native ScrollView.

Thực ra có rất nhiều cách để tạo animation cho ScrollView, bài viết này mình sẽ hướng dẫn các bạn sử dụng Animated API để tạo animation rất đơn giản mà hiệu quả.

Đây là kết quả cuối cùng

 Tạo animation đơn giản cho React Native ScrollView

Giải thích qua về thuật toán tạo animation

Ý tưởng là để tạo header có thể đè lên scrollview đó là sử dụng position: ‘absolute và thiết lập margin top của scrollview một khoảng đúng bằng chiều cao của phần header. Sau đó, chúng ta sẽ tạo animation cho chiều cao header dùng ScrollView scroll position.

Tiến hành code

Đầu tiên là phải được một ScrollView cùng với dữ liệu để phục vụ tạo hiệu ứng ở bước sau:

import React, {Component} from 'react';
import {
  Animated,
  Image,
  ScrollView,
  StyleSheet,
  Text,
  View,
} from 'react-native';

export default class ScrollableHeader extends Component {

  _renderScrollViewContent() {
    const data = Array.from({length: 30});
    return (
      <View style={styles.scrollViewContent}>
        {data.map((_, i) =>
          <View key={i} style={styles.row}>
            <Text>{i}</Text>
          </View>
        )}
      </View>
    );
  }

  render() {
    return (
      <View style={styles.fill}>
        <ScrollView
          style={styles.fill}
        >
          {this._renderScrollViewContent()}
        </ScrollView>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  fill: {
    flex: 1,
  },
  row: {
    height: 40,
    margin: 16,
    backgroundColor: '#D3D3D3',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Tiếp theo, chúng ta cần tạo header View và thêm margin cho phần content của ScrollView để phần content không nằm dưới header. Chúng ta vẫn thêm title cho phần header như bình thường.

Hãy thêm View vào sau ScrollView. Mục đích là để sử dụng Animated.View để tạo animation

...
<ScrollView>
  ...
</ScrollView>
<Animated.View style={styles.header}>
  <View style={styles.bar}>
    <Text style={styles.title}>Title</Text>
  </View>
</Animated.View>
...

Còn đây là styles:

...
header: {
  position: 'absolute',
  top: 0,
  left: 0,
  right: 0,
  backgroundColor: '#03A9F4',
  overflow: 'hidden',
},
bar: {
  marginTop: 28,
  height: 32,
  alignItems: 'center',
  justifyContent: 'center',
},
title: {
  backgroundColor: 'transparent',
  color: 'white',
  fontSize: 18,
},
scrollViewContent: {
  marginTop: HEADER_MAX_HEIGHT,
},
...

Chúng ta vẫn sẽ định nghĩa một số giá trị hằng số cho các kích thước header sẽ được sử dụng cho việc nội suy giá trị vị trí trong việc cuộn (scroll).

Chúng ta định nghĩa một số constant về kích thước cho header, mục đích để phục vụ việc interpolate các position item khi scroll.

const HEADER_MAX_HEIGHT = 200;
const HEADER_MIN_HEIGHT = 60;
const HEADER_SCROLL_DISTANCE = HEADER_MAX_HEIGHT — HEADER_MIN_HEIGHT;

Tiền hành chạy thử thì chúng ta sẽ được giao diện như bên dưới:

 Tạo animation đơn giản cho React Native ScrollView

Implement phần quan trọng nhất: Animation

React Native có những API tạo animation rất mạnh mẽ, nó cho phép tạo hiệu ứng một giá trị nhưng cũng đồng thời gắn giá trị của nó vào một sự kiện(event).

Trong bài tutorial này, chúng ta sẽ gắn một giá trị động (animated value) vào Y scroll position  của ScrollView.

Để làm điều đó, mình sử dụng Animated.event với ScrollView onScroll props. Đại khái như bên dưới

...

constructor(props) {
  super(props);

  this.state = {
    scrollY: new Animated.Value(0),
  };
}

...
  
render() {
  const headerHeight = this.state.scrollY.interpolate({
    inputRange: [0, HEADER_SCROLL_DISTANCE],
    outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
    extrapolate: 'clamp',
  });
  
  ...

  <ScrollView
    style={styles.fill}
    scrollEventThrottle={16}
    onScroll={Animated.event(
      [{nativeEvent: {contentOffset: {y: this.state.scrollY}}}]
    )}
  >
    ...
  </ScrollView>
  <Animated.View style={[styles.header, {height: headerHeight}]}>
    ...
  </Animated.View>
  ...
}

Mình giải thích qua một chút đoạn code ở trên:

Đầu tiên, chúng ta tạo ra Animated.Value, đó là một giá trị mà có thể tạo hiệu ứng animation bằng cách sử dụng API Animated.

Sau đó, chúng bind animated value vào ScrollView scroll position. Đơn giản là chúng ta sử dụng Animated.event với việc ánh xạ tới thuộc tính của đối tượng event mà có thể bind giá trị animated value . Trong trường hợp này, nó chính là <eventObject>.nativeEvent.contentOffset.y.

Sau đó, mình sử dụng hàm interpolate giá trị Animated.Value để ánh xạ scroll position với chiều cao header mong muốn. Điều mình muốn là khi vị trí cuộn ở 0, header ở mức HEADER_MAX_HEIGHT và khi vị trí cuộn đã di chuyển đến chênh lệch giữa HEADER_MAX_HEIGHTHEADER_MIN_HEIGHT, header ở mức HEADER_MIN_HEIGHT.

Cuối cùng chúng ta chỉ cần thiết lập chiều cao với giá trị animated cho header view.

Các bạn để ý thuộc tính scrollEventThrottle, mình đang để là 16 để các event được gửi thường xuyên hơn và để animation được mượt mà hơn.

Khi mà header di chuyển với scroll position và sử dụng kỹ thuật tương tự, chúng ta có thể tạo thêm nhiều hiệu ứng khác cho header trong khi người dùng scroll.

Sáng tạo với nhiều hiệu ứng hơn

 Tạo animation đơn giản cho React Native ScrollView

Mình sẽ sáng tạo một chút nhé

Để header bớt nhàm chán, mình sẽ thêm hình ảnh một con mèo với hiệu ứng parallax. Để làm điều này chúng ta hãy thêm hai giá trị: interpolated animated và một Image component

...

const imageOpacity = this.state.scrollY.interpolate({
  inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE],
  outputRange: [1, 1, 0],
  extrapolate: 'clamp',
});
const imageTranslate = this.state.scrollY.interpolate({
  inputRange: [0, HEADER_SCROLL_DISTANCE],
  outputRange: [0, -50],
  extrapolate: 'clamp',
});

...

<ScrollView>
  ...
</ScrollView>
<Animated.View style={[styles.header, {height: headerHeight}]}>
  <Animated.Image
    style={[
      styles.backgroundImage,
      {opacity: imageOpacity, transform: [{translateY: imageTranslate}]},
    ]}
    source={require('./images/cat.jpg')}
  />
  <Animated.View>
    ...
  </Animated.View>
</Animated.View>

...

backgroundImage: {
  position: 'absolute',
  top: 0,
  left: 0,
  right: 0,
  width: null,
  height: HEADER_MAX_HEIGHT,
  resizeMode: 'cover',
},
  
...
Bài tập về nhà
Phần dành cho các bạn: Thêm hiệu ứng mở rộng title khi người dùng scroll xuống dưới. Các bạn thử code xem nhé, nếu có khó khăn thì comment bên dưới để mọi người cùng giúp đỡ

Tổng kết

API Animated giúp cho việc tạo animation phức tạp trở nên dễ dàng hơn bao giờ hết. Nó còn cho phép giữ state vô cùng đơn giản. Tất cả đều dựa trên một value,và animation phức tạp đều được xử lý trong hàm render với interpolations.

Qua bài viết này chúng ta đã hiểu hơn về React Native rồi đấy. Với API Animated, ứng dụng của bạn sẽ đẹp hơn rất nhiều. Mình rất hi vọng bài viết sẽ giúp ích cho các bạn, đừng quên comment ủng hộ mình nhé

Xem tiếp các bài trong Series
Phần trước: React Native custom font – Cách tùy chỉnh font cho ứng dụng

Bình luận. Đặt câu hỏi cũng là một cách học

avatar
  Theo dõi bình luận  
Thông báo