Giới thiệu

Chắc nhiều bạn đã thấy nhiều app có 1 list view với 1 header mà khi bạn scroll xuống dưới thì chiều cao header cũng ngắn lại và thêm 1 số animation khác.
Nay mình sẽ hướng dẫn làm hiệu ứng đó sử dụng Animated API trong react-native.

Bí mật hậu trường

Ý tưởng đằng sau là chúng ta sẽ tạo 1 header nằm trên scrollview sử dụng position ‘absolute’.
Chiều cao của header sẽ được điều chỉnh dựa vào offset Y của scrollView khi scroll. Offset Y này được lấy từ callback onScroll của scrollview.

Thực hiện

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
this.scrollY = new Animated.Value(0)

<Animated.ScrollView
contentContainerStyle={styles.contentContainer}
scrollEventThrottle={8} // target 120fps
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.scrollY}}}],
{useNativeDriver: true}
)}>
<Contents />
</Animated.ScrollView>
<HeaderView
scrollY={this.scrollY}
style={styles.headerView} />

const styles = StyleSheet.create({
contentContainer: {
paddingTop: MAX_HEIGHT
},
headerView: {
position: 'absolute',
top: 0, left: 0, right: 0
height: MAX_HEIGHT
}
})

Ở trên scrollY là biến dùng để quản lý animation khi scroll, biến này được gán giá trị với contentOffset y của scrollView.
scrollEventThrottle dùng để điều khiển tần suất số lần gọi scroll event khi scroll,
hay nói cách khác là định nghĩa bao nhiêu lần trong 1s chúng ta muốn thực thi event onScroll,
giá trị càng nhỏ thì độ chính xác càng cao nhưng sẽ làm ảnh hưởng scroll performance của scrollview.

Ở Header View:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const MIN_HEIGHT = 65
const MAX_HEIGHT = 205
const SCROLLABLE_HEIGHT = MAX_HEIGHT - MIN_HEIGHT

class HeaderView extends React.PureComponent {
state = {
headerTranslateY: this.props.scrollY.interpolate({
inputRange: [0, SCROLLABLE_HEIGHT],
outputRange: [0, - SCROLLABLE_HEIGHT],
extrapolate: 'clamp',
}),
}

render() {
return <Animated.View style={[styles.container,
{transform: [{translateY: this.state.headerTranslateY}]}]}>
<Content />
</Animated.View>
}
}

Chú ý trong interpolate ta thêm extrapolate: ‘clamp’
nhằm ngăn không cho output value vượt ra ngoài ouputRange. Giá trị mặc định của extrapolate là extend

Và đây là kết quả:
Animation

Cải thiện

Ở trên chúng ta chỉ sử dụng translateY aimation, chúng ta nên kết hợp các animation khác như opacity, scale.. để headerView trở nên cool hơn.
Việc sử dụng {useNativeDriver: true} như ở trên rất quan trọng nếu bạn muốn app của bạn không bị lag khi có nhiều xử lý chiếm nhiều cpu.
Bạn hãy thử bỏ đi sử dụng useNativeDriver và thêm đoạn code dưới vào app, bạn sẽ thấy sự khác biệt.

1
2
3
4
5
6
7
8
9
runCPUburner = () => {
const timestamp = Date.now() + 160;
while(Date.now() < timestamp) {};
requestAnimationFrame(this.runCPUburner)
}

componentDidMount(){
this.runCPUburner()
}