fbpx

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 hiệu ứng chuyển động rất đơn giản mà hiệu quả cho controller ScrollView trong lập trình React Native.

Giải thích qua về thuật toán tạo hiệu ứng 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 fake để 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:

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_HEIGHT và HEADER_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 cho Scroll View

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',
},
  
...

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é

Source Code: https://app.box.com/s/cseyyqbngqbowys7tiy7bixv8fwx987g

Bài viết được trích dẫn từ nguồn: https://vntalking.com/tao-animation-cho-react-native-scrollview.html

About the author

Kevin Dang

Hey there! My name is Kevin Dang, I am website, software, mobile app develop, web admin system. Expert living in Hồ Chí Minh (Việt Nam). I am very interested in digital marketing with: SEO, Facebook, Google Ads ... This blog is where I will share the experiences, techniques and knowledge I have learned.