Contoh Animasi Praktis dalam React Native
() translation by (you can also view the original English article)
Dalam tutorial ini, Anda akan belajar bagaimana menerapkan animasi yang sering digunakan dalam aplikasi mobile. Secara khusus, Anda akan belajar bagaimana menerapkan animasi yang:
- Provide visual feedback: misalnya, ketika pengguna menekan tombol, Anda ingin menggunakan animasi untuk menunjukkan kepada pengguna bahwa tombol tersebut benar-benar ditekan.
- Show the current system status: ketika melakukan proses yang tidak selesai secara instan (misalnya ketika mengunggah foto atau mengirim email), Anda ingin menampilkan animasi sehingga pengguna memiliki gagasan berapa lama proses akan berlangsung.
- Visually connect transition states: ketika pengguna menekan tombol untuk membawa sesuatu ke depan layar, transisi ini harus dianimasikan agar pengguna tahu dari mana asal elemen tersebut.
- Grab the user's attention: ketika ada pemberitahuan penting, Anda dapat menggunakan animasi untuk menarik perhatian pengguna.
Tutorial ini adalah sekuel untuk Animate Your React Native App post. Jadi jika Anda baru dalam animasi di React Native, pastikan untuk memeriksanya terlebih dahulu, karena beberapa konsep yang akan digunakan dalam tutorial ini dijelaskan secara lebih terperinci di sana.
Juga, jika Anda ingin mengikuti, Anda dapat menemukan kode sumber lengkap yang digunakan dalam tutorial ini di GitHub repo.
Apa yang sedang kita bangun
Kita akan membangun aplikasi yang mengimplementasikan setiap jenis animasi yang saya sebutkan sebelumnya. Secara khusus, kita akan membuat halaman-halaman berikut, yang masing-masing akan mengimplementasikan animasi untuk tujuan yang berbeda.
- News Page: menggunakan isyarat untuk memberikan umpan balik visual dan menunjukkan status sistem saat ini.
- Buttons Page: menggunakan tombol untuk memberikan umpan balik visual dan menunjukkan status sistem saat ini.
- Progress Page: menggunakan bilah progres untuk menunjukkan status sistem saat ini.
- Expand Page: secara visual menghubungkan status transisi menggunakan gerakan memperluas dan menyusut.
- AttentionSeeker Page: menggunakan gerakan eye-catching untuk menarik perhatian pengguna.
Jika Anda ingin melihat preview dari masing-masing animasi, periksa Imgur album ini.
Mendirikan Proyek
Mulai dengan membuat proyek Native React baru:
1 |
react-native init RNPracticalAnimations |
Setelah proyek dibuat, navigasi di dalam folder yang baru dibuat, buka file package.json
, dan tambahkan yang berikut ke dependencies
:
1 |
"react-native-animatable": "^0.6.1", |
2 |
"react-native-vector-icons": "^3.0.0" |
Jalankan npm install
untuk menginstal kedua paket tersebut. react-native-animatable digunakan untuk mengimplementasikan animasi dengan mudah, dan react-native-vector-icons digunakan untuk membuat ikon untuk halaman ekspansi nanti. Jika Anda tidak ingin menggunakan ikon, Anda bisa tetap menggunakan komponen Text
. Jika tidak, ikuti petunjuk pemasangan of react-native-vector-icons pada their GitHub page.
Membangun Aplikasi
Buka file index.android.js
atau file index.ios.js
dan ganti konten yang ada dengan yang berikut:
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
AppRegistry
|
5 |
} from 'react-native'; |
6 |
|
7 |
import NewsPage from './src/pages/NewsPage'; |
8 |
import ButtonsPage from './src/pages/ButtonsPage'; |
9 |
import ProgressPage from './src/pages/ProgressPage'; |
10 |
import ExpandPage from './src/pages/ExpandPage'; |
11 |
import AttentionSeekerPage from './src/pages/AttentionSeekerPage'; |
12 |
|
13 |
class RNPracticalAnimation extends Component { |
14 |
render() { |
15 |
return ( |
16 |
<NewsPage /> |
17 |
);
|
18 |
}
|
19 |
}
|
20 |
|
21 |
AppRegistry.registerComponent('RNPracticalAnimation', () => RNPracticalAnimation); |
Setelah selesai, pastikan untuk membuat file yang sesuai sehingga Anda tidak mendapatkan kesalahan apa pun. Semua file yang akan kita kerjakan disimpan di bawah direktori src
. Di dalam direktori tersebut adalah folder berikut:
-
components
: komponen reusable yang akan digunakan oleh komponen atau halaman lain. -
img
: gambar yang akan digunakan di seluruh aplikasi. Anda bisa mendapatkan gambar dari GitHub repo. -
pages
: halaman-halaman aplikasi.
Halaman Berita
Mari kita mulai dengan Halaman berita.

Pertama, tambahkan komponen yang akan kita gunakan:
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
Text, |
6 |
View, |
7 |
Animated, |
8 |
Easing, |
9 |
ScrollView, |
10 |
RefreshControl
|
11 |
} from 'react-native'; |
12 |
|
13 |
import NewsItem from '../components/NewsItem'; |
Anda seharusnya sudah terbiasa dengan sebagian besar dari ini, kecuali untuk RefreshControl
dan komponen NewsItem
kustom, yang akan kita buat nanti. RefreshControl
digunakan untuk menambahkan fungsionalitas "pull to refresh" dalam komponen ScrollView
atau ListView
. Jadi sebenarnya yang akan menangani gerakan menggesek ke bawah dan animasi untuk kita. Tidak perlu menerapkan milik kita sendiri. Ketika Anda mendapatkan lebih banyak pengalaman dalam menggunakan React Native, Anda akan melihat bahwa animasi sebenarnya dibangun untuk beberapa components, dan tidak perlu menggunakan kelas Animated
untuk mengimplementasikan sendiri.
Buat komponen yang akan menampung seluruh halaman:
1 |
export default class NewsPage extends Component { |
2 |
...
|
3 |
}
|
Di dalam constructor
, menginisialisasi nilai animasi untuk menyimpan opasitas saat ini (opacityValue
) dari item berita. Kita ingin agar berita tidak terlalu buram saat item berita sedang disegarkan. Ini memberi pengguna sebuah ide bahwa mereka tidak dapat berinteraksi dengan seluruh halaman sementara item berita sedang disegarkan. is_news_refreshing
digunakan sebagai saklar untuk menunjukkan apakah item berita sedang disegarkan atau tidak.
1 |
constructor(props) { |
2 |
super(props); |
3 |
this.opacityValue = new Animated.Value(0); |
4 |
this.state = { |
5 |
is_news_refreshing: false, |
6 |
news_items: [ |
7 |
{
|
8 |
title: 'CTO Mentor Network – a virtual peer-to-peer network of CTOs', |
9 |
website: 'ctomentor.network', |
10 |
url: 'https://ctomentor.network/' |
11 |
},
|
12 |
{
|
13 |
title: 'The No More Ransom Project', |
14 |
website: 'nomoreransom.org', |
15 |
url: 'https://www.nomoreransom.org/' |
16 |
},
|
17 |
{
|
18 |
title: 'NASA Scientists Suggest We’ve Been Underestimating Sea Level Rise', |
19 |
website: 'vice.com', |
20 |
url: 'http://motherboard.vice.com/read/nasa-scientists-suggest-weve-been-underestimating-sea-level-rise' |
21 |
},
|
22 |
{
|
23 |
title: 'Buttery Smooth Emacs', |
24 |
website: 'facebook.com', |
25 |
url: 'https://www.facebook.com/notes/daniel-colascione/buttery-smooth-emacs/10155313440066102/' |
26 |
},
|
27 |
{
|
28 |
title: 'Elementary OS', |
29 |
website: 'taoofmac.com', |
30 |
url: 'http://taoofmac.com/space/blog/2016/10/29/2240' |
31 |
},
|
32 |
{
|
33 |
title: 'The Strange Inevitability of Evolution', |
34 |
website: 'nautil.us', |
35 |
url: 'http://nautil.us/issue/41/selection/the-strange-inevitability-of-evolution-rp' |
36 |
},
|
37 |
]
|
38 |
}
|
39 |
}
|
Fungsi opacity
() adalah salah satu yang akan memicu animasi untuk mengubah opacity.
1 |
opacity() { |
2 |
this.opacityValue.setValue(0); |
3 |
Animated.timing( |
4 |
this.opacityValue, |
5 |
{
|
6 |
toValue: 1, |
7 |
duration: 3500, |
8 |
easing: Easing.linear |
9 |
}
|
10 |
).start(); |
11 |
}
|
Di dalam fungsi render ()
, tentukan bagaimana nilai opasitas akan berubah. Di sini, outputRange
adalah [1, 0, 1]
, yang berarti bahwa itu akan mulai pada opasitas penuh, kemudian pergi ke nol opacity, dan kemudian kembali ke opacity penuh lagi. Sebagaimana didefinisikan di dalam opacity ()
fungsi, transisi ini akan dilakukan selama 3,500 milidetik (3,5 detik).
1 |
render() { |
2 |
|
3 |
const opacity = this.opacityValue.interpolate({ |
4 |
inputRange: [0, 0.5, 1], |
5 |
outputRange: [1, 0, 1] |
6 |
});
|
7 |
|
8 |
...
|
9 |
}
|
Komponen <RefreshControl
> ditambahkan ke <ScrollView
>. Ini memanggil fungsi refreshNews ()
kapan saja pengguna menggesek ke bawah saat mereka berada di bagian atas daftar (ketika scrollY
adalah 0
). Anda dapat menambahkan colors
prop untuk menyesuaikan warna animasi refresh.
1 |
return ( |
2 |
<View style={styles.container}> |
3 |
<View style={styles.header}> |
4 |
</View> |
5 |
<ScrollView |
6 |
refreshControl={ |
7 |
<RefreshControl |
8 |
colors={['#1e90ff']} |
9 |
refreshing={this.state.is_news_refreshing} |
10 |
onRefresh={this.refreshNews.bind(this)} |
11 |
/> |
12 |
}
|
13 |
style={styles.news_container}> |
14 |
...
|
15 |
</ScrollView> |
16 |
</View> |
17 |
);
|
Di dalam <ScrollView
>, gunakan komponen<Animated.View
> dan atur style
ke nilai opacity
:
1 |
<Animated.View style={[{opacity}]}> |
2 |
{ this.renderNewsItems() } |
3 |
</Animated.View> |
Fungsi refreshNews()
memanggil fungsi opacity()
dan memperbaharui nilai is_news_refreshing
untuk true
. Hal ini memungkinkan komponen <RefreshControl
>mengetahui bahwa animasi refresh seharusnya sudah ditampilkan. Setelah itu, menggunakan setTimeout ()
untuk memperbarui nilai is_news_refreshing
kembali ke false
setelah 3.500 milidetik (3,5 detik). Ini akan menyembunyikan refresh animasi dari tampilan. Pada saat itu, animasi opacity juga harus dilakukan karena kita menetapkan nilai yang sama untuk durasi dalam fungsi opacity
sebelumnya.
1 |
refreshNews() { |
2 |
this.opacity(); |
3 |
this.setState({is_news_refreshing: true}); |
4 |
setTimeout(() => { |
5 |
this.setState({is_news_refreshing: false}); |
6 |
}, 3500); |
7 |
}
|
renderNewsItems ()
mengambil larik item berita yang kami nyatakan sebelumnya di dalam constructor()
dan menuliskan masing-masing menggunakan <NewsItem
> komponen.
1 |
renderNewsItems() { |
2 |
return this.state.news_items.map((news, index) => { |
3 |
return ( |
4 |
<NewsItem key={index} index={index} news={news} /> |
5 |
);
|
6 |
});
|
7 |
}
|
NewsItem Component
Komponen NewsItem
(src/components/NewsItem.js
) menjadikan judul dan situs berita dan membungkus mereka di dalam <Button
>komponen sehingga mereka dapat berinteraksi dengannya.
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
Text, |
6 |
View, |
7 |
} from 'react-native'; |
8 |
|
9 |
import Button from './Button'; |
10 |
|
11 |
const NewsItem = ({ news, index }) => { |
12 |
|
13 |
function onPress(news) { |
14 |
//do anything you want
|
15 |
}
|
16 |
|
17 |
return ( |
18 |
<Button |
19 |
key={index} |
20 |
noDefaultStyles={true} |
21 |
onPress={onPress.bind(this, news)} |
22 |
>
|
23 |
<View style={styles.news_item}> |
24 |
<Text style={styles.title}>{news.title}</Text> |
25 |
<Text>{news.website}</Text> |
26 |
</View> |
27 |
</Button> |
28 |
);
|
29 |
}
|
30 |
|
31 |
const styles = StyleSheet.create({ |
32 |
news_item: { |
33 |
flex: 1, |
34 |
flexDirection: 'column', |
35 |
paddingRight: 20, |
36 |
paddingLeft: 20, |
37 |
paddingTop: 30, |
38 |
paddingBottom: 30, |
39 |
borderBottomWidth: 1, |
40 |
borderBottomColor: '#E4E4E4' |
41 |
},
|
42 |
title: { |
43 |
fontSize: 20, |
44 |
fontWeight: 'bold' |
45 |
}
|
46 |
});
|
47 |
|
48 |
export default NewsItem; |
Button Component
The Button
component (src/components/Button.js
) menggunakan komponen TouchableHighlight
untuk membuat sebuah button. Alat peraga underlayColor
digunakan untuk menentukan warna lapisan bawah ketika tombol ditekan. Ini adalah cara React Native's built-in menyediakan umpan balik visual; kemudian di bagian Buttons Page, kita akan melihat cara lain button dapat memberikan umpan balik visual.
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
Text, |
6 |
TouchableHighlight, |
7 |
} from 'react-native'; |
8 |
|
9 |
const Button = (props) => { |
10 |
|
11 |
function getContent() { |
12 |
if(props.children){ |
13 |
return props.children; |
14 |
}
|
15 |
return <Text style={props.styles.label}>{props.label}</Text> |
16 |
}
|
17 |
|
18 |
return ( |
19 |
<TouchableHighlight |
20 |
underlayColor="#ccc" |
21 |
onPress={props.onPress} |
22 |
style={[ |
23 |
props.noDefaultStyles ? '' : styles.button, |
24 |
props.styles ? props.styles.button : '']} |
25 |
>
|
26 |
{ getContent() } |
27 |
</TouchableHighlight> |
28 |
);
|
29 |
}
|
30 |
|
31 |
const styles = StyleSheet.create({ |
32 |
button: { |
33 |
alignItems: 'center', |
34 |
justifyContent: 'center', |
35 |
padding: 20, |
36 |
borderWidth: 1, |
37 |
borderColor: '#eee', |
38 |
margin: 20 |
39 |
}
|
40 |
});
|
41 |
|
42 |
export default Button; |
Kembali ke komponen NewsPage
, tambahkan styling:
1 |
const styles = StyleSheet.create({ |
2 |
container: { |
3 |
flex: 1, |
4 |
},
|
5 |
header: { |
6 |
flexDirection: 'row', |
7 |
backgroundColor: '#FFF', |
8 |
padding: 20, |
9 |
justifyContent: 'space-between', |
10 |
borderBottomColor: '#E1E1E1', |
11 |
borderBottomWidth: 1 |
12 |
},
|
13 |
news_container: { |
14 |
flex: 1, |
15 |
}
|
16 |
});
|
Buttons Page

Button page (src / pages / ButtonsPage.js
) menunjukkan tiga jenis button: button yang biasa digunakan yang disorot,button yang menjadi sedikit lebih besar, dan button yang menunjukkan status operasi saat ini. Memulai dengan menambahkan komponen yang diperlukan:
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
View
|
6 |
} from 'react-native'; |
7 |
|
8 |
import Button from '../components/Button'; |
9 |
import ScalingButton from '../components/ScalingButton'; |
10 |
import StatefulButton from '../components/StatefulButton'; |
Sebelumnya, Anda melihat bagaimana the Button
component bekerja, jadi kita hanya akan fokus pada dua button lain.
Scaling Button Component
Pertama, mari kita lihat pada button skala (src/components/ScalingButton.js
). Tidak seperti buttonl yang kita gunakan sebelumnya, ini menggunakan komponen TouchableWithoutFeedback
built-in untuk membuat button. Sebelumnya, kami menggunakan komponen TouchableHighlight
, yang dilengkapi dengan semua lonceng dan peluit untuk sesuatu yang dianggap sebagai button. Anda dapat memikirkan TouchableWithoutFeedback
sebagai button bare-bones di mana Anda harus menentukan semua yang perlu dilakukan ketika pengguna mengetuknya. Ini sangat cocok untuk kasus penggunaan kita karena kita tidak perlu khawatir tentang perilaku button default yang menghalangi animasi yang ingin kita terapkan.
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
Text, |
6 |
Animated, |
7 |
Easing, |
8 |
TouchableWithoutFeedback
|
9 |
} from 'react-native'; |
Sama seperti komponen Button
, ini akan menjadi komponen fungsional karena kita tidak benar-benar perlu bekerja dengan negara.
1 |
const ScalingButton = (props) => { |
2 |
...
|
3 |
}
|
Di dalam komponen, buat nilai animasi yang akan menyimpan skala button saat ini.
1 |
var scaleValue = new Animated.Value(0); |
Tambahkan fungsi yang akan memulai animasi skala . Kita tidak ingin aplikasi tampak lambat, jadi buat duration
serendah mungkin tetapi juga cukup tinggi sehingga pengguna dapat melihat apa yang terjadi. 300
milidetik adalah titik awal yang baik, tetapi merasa bebas untuk bermain-main dengan nilai.
1 |
function scale() { |
2 |
scaleValue.setValue(0); |
3 |
Animated.timing( |
4 |
scaleValue, |
5 |
{
|
6 |
toValue: 1, |
7 |
duration: 300, |
8 |
easing: Easing.easeOutBack |
9 |
}
|
10 |
).start(); |
11 |
}
|
Tentukan bagaimana button akan diskalakan (outputRange
) tergantung pada nilai saat ini (inputRange
). Kita tidak ingin itu menjadi terlalu besar sehingga kita tetap dengan 1,1
sebagai nilai tertinggi. Ini berarti akan lebih besar 0,1
dari ukuran aslinya di tengah (0,5
) seluruh animasi.
1 |
const buttonScale = scaleValue.interpolate({ |
2 |
inputRange: [0, 0.5, 1], |
3 |
outputRange: [1, 1.1, 1] |
4 |
});
|
5 |
|
6 |
return ( |
7 |
<TouchableWithoutFeedback onPress={onPress}> |
8 |
<Animated.View style={[ |
9 |
props.noDefaultStyles ? styles.default_button : styles.button, |
10 |
props.styles ? props.styles.button : '', |
11 |
{
|
12 |
transform: [ |
13 |
{scale: buttonScale} |
14 |
]
|
15 |
}
|
16 |
]}
|
17 |
>
|
18 |
{ getContent() } |
19 |
</Animated.View> |
20 |
</TouchableWithoutFeedback> |
21 |
);
|
Fungsi onPress
() menjalankan animasi skala terlebih dahulu sebelum memanggil metode yang diteruskan oleh pengguna melalui alat peraga.
1 |
function onPress() { |
2 |
scale(); |
3 |
props.onPress(); |
4 |
}
|
Fungsi getContent ()
menampilkan komponen anak jika tersedia. Jika tidak, komponen Text
yang berisi label
props ditampilkan.
1 |
function getContent() { |
2 |
if(props.children){ |
3 |
return props.children; |
4 |
}
|
5 |
return <Text style={props.styles.label}>{ props.label }</Text>; |
6 |
}
|
Tambahkan gaya dan ekspor button:
1 |
const styles = StyleSheet.create({ |
2 |
default_button: { |
3 |
alignItems: 'center', |
4 |
justifyContent: 'center' |
5 |
},
|
6 |
button: { |
7 |
alignItems: 'center', |
8 |
justifyContent: 'center', |
9 |
padding: 20, |
10 |
borderWidth: 1, |
11 |
borderColor: '#eee', |
12 |
margin: 20 |
13 |
},
|
14 |
});
|
15 |
|
16 |
export default ScalingButton; |
Stateful Button Component
Berikutnya adalah stateful button (src/components/StatefulButton.js
). Saat ditekan, button ini akan mengubah warna latar belakangnya dan menampilkan gambar pemuatan hingga operasi selesai.
Memuat gambar yang kita akan menggunakan adalah animasi gif. Secara default, React Native on Android tidak mendukung gif animasi. Untuk membuatnya bekerja, Anda harus mengedit android/app/build.gradle file
dan menambahkan compile 'com.facebook.fresco:animated-gif:0.12.0'
di bawah dependencies
seperti:
1 |
dependencies { |
2 |
//default dependencies here |
3 |
|
4 |
compile 'com.facebook.fresco:animated-gif:0.12.0' |
5 |
} |
Jika Anda menggunakan iOS, gif animasi harus bekerja secara default.
Kembali ke komponen button stateful, seperti button scaling, ini menggunakan komponen TouchableWithoutFeedback
untuk membuat button karena itu juga akan mengimplementasikan animasinya sendiri.
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
View, |
6 |
Image, |
7 |
Text, |
8 |
TouchableWithoutFeedback, |
9 |
Animated
|
10 |
} from 'react-native'; |
Tidak seperti button skala, komponen ini akan menjadi komponen berbasis kelas penuh karena mengelola negara sendiri.
Di dalam constructor
(), buat nilai animasi untuk menyimpan warna latar belakang saat ini. Setelah itu, inisialisasi status yang berfungsi sebagai saklar untuk menyimpan status tombol saat ini. Secara default, ini disetel ke false
. Setelah pengguna mengetuk button, itu akan diperbarui menjadi true
dan hanya akan disetel ke false
lagi setelah proses imajiner selesai dijalankan.
1 |
export default class StatefulButton extends Component { |
2 |
|
3 |
constructor(props) { |
4 |
super(props); |
5 |
this.colorValue = new Animated.Value(0); |
6 |
this.state = { |
7 |
is_loading: false |
8 |
}
|
9 |
}
|
10 |
}
|
Di dalam fungsi render ()
, tentukan warna latar belakang berbeda yang akan digunakan berdasarkan nilai saat ini dari nilai animasi.
1 |
render() { |
2 |
|
3 |
const colorAnimation = this.colorValue.interpolate({ |
4 |
inputRange: [0, 50, 100], |
5 |
outputRange: ['#2196f3', '#ccc', '#8BC34A'] |
6 |
});
|
7 |
|
8 |
...
|
9 |
}
|
Selanjutnya, membungkus semuanya dalam komponen TouchableWithoutFeedback
, dan dalam <Animated.View
>adalah tempat warna latar belakang animasi diterapkan. Kita juga merender citra pemuat jika nilai is_loading
saat ini true
. Label button juga berubah berdasarkan nilai ini.
1 |
return ( |
2 |
<TouchableWithoutFeedback onPress={this.onPress.bind(this)}> |
3 |
<Animated.View style={[ |
4 |
styles.button_container, |
5 |
this.props.noDefaultStyles ? '' : styles.button, |
6 |
this.props.styles ? this.props.styles.button : '', |
7 |
{
|
8 |
backgroundColor: colorAnimation |
9 |
},
|
10 |
]}> |
11 |
{
|
12 |
this.state.is_loading && |
13 |
<Image |
14 |
style={styles.loader} |
15 |
source={require('../img/ajax-loader.gif')} |
16 |
/> |
17 |
}
|
18 |
<Text style={this.props.styles.label}> |
19 |
{ this.state.is_loading ? 'loading...' : this.props.label} |
20 |
</Text> |
21 |
</Animated.View> |
22 |
</TouchableWithoutFeedback> |
23 |
);
|
Ketika button ditekan, pertama-tama menjalankan fungsi yang dilewatkan melalui props sebelum melakukan animasi.
1 |
onPress() { |
2 |
this.props.onPress(); |
3 |
this.changeColor(); |
4 |
}
|
Fungsi changeColor()
bertanggung jawab untuk memperbarui negara dan menghidupkan warna latar belakang button. Di sini kita akan mengasumsikan bahwa proses akan mengambil 3.000 milidetik (3 detik). Tapi dalam skenario dunia nyata, Anda tidak dapat selalu tahu berapa lama proses yang akan berlangsung. Yang dapat Anda lakukan adalah menjalankan animasi untuk jangka waktu yang lebih pendek dan kemudian memanggil fungsi changeColor
() secara rekursif hingga proses selesai.
1 |
changeColor() { |
2 |
this.setState({ |
3 |
is_loading: true |
4 |
});
|
5 |
|
6 |
this.colorValue.setValue(0); |
7 |
Animated.timing(this.colorValue, { |
8 |
toValue: 100, |
9 |
duration: 3000 |
10 |
}).start(() => { |
11 |
this.setState({ |
12 |
is_loading: false |
13 |
});
|
14 |
});
|
15 |
}
|
Tambahkan Gaya:
1 |
const styles = StyleSheet.create({ |
2 |
button_container: { |
3 |
flexDirection: 'row', |
4 |
alignItems: 'center', |
5 |
backgroundColor: '#2196f3' |
6 |
},
|
7 |
button: { |
8 |
alignItems: 'center', |
9 |
justifyContent: 'center', |
10 |
padding: 20, |
11 |
borderWidth: 1, |
12 |
borderColor: '#eee', |
13 |
margin: 20 |
14 |
},
|
15 |
loader: { |
16 |
width: 16, |
17 |
height: 16, |
18 |
marginRight: 10 |
19 |
}
|
20 |
});
|
Kembali pada halaman Buttons : buat komponen, render ketiga jenis button , dan tambahkan gaya mereka.
1 |
export default class ButtonsPage extends Component { |
2 |
press() { |
3 |
//do anything you want
|
4 |
}
|
5 |
|
6 |
render() { |
7 |
return ( |
8 |
<View style={styles.container}> |
9 |
<Button |
10 |
underlayColor={'#ccc'} |
11 |
label="Ordinary Button" |
12 |
onPress={this.press.bind(this)} |
13 |
styles={{button: styles.ordinary_button, label: styles.button_label}} /> |
14 |
|
15 |
<ScalingButton |
16 |
label="Scaling Button" |
17 |
onPress={this.press.bind(this)} |
18 |
styles={{button: styles.animated_button, label: styles.button_label}} /> |
19 |
|
20 |
<StatefulButton |
21 |
label="Stateful Button" |
22 |
onPress={this.press.bind(this)} |
23 |
styles={{button: styles.stateful_button, label: styles.button_label}} /> |
24 |
</View> |
25 |
);
|
26 |
}
|
27 |
}
|
28 |
|
29 |
const styles = StyleSheet.create({ |
30 |
container: { |
31 |
flex: 1, |
32 |
flexDirection: 'column', |
33 |
padding: 30 |
34 |
},
|
35 |
ordinary_button: { |
36 |
backgroundColor: '#4caf50', |
37 |
},
|
38 |
animated_button: { |
39 |
backgroundColor: '#ff5722' |
40 |
},
|
41 |
button_label: { |
42 |
color: '#fff', |
43 |
fontSize: 20, |
44 |
fontWeight: 'bold' |
45 |
}
|
46 |
});
|
Progress Page

Laman Kemajuan (src / pages / ProgressPage.js
) menampilkan animasi kemajuan kepada pengguna selama proses yang berjalan lama. Kami akan mengimplementasikan sendiri alih-alih menggunakan komponen bawaan karena React Native tidak memiliki cara terpadu untuk menerapkan animasi bilah kemajuan. Jika Anda tertarik, berikut adalah tautan ke dua komponen bilah progres yang ada di dalamnya:
Untuk membangun halaman Kemajuan kita, mulailah dengan mengimpor komponen yang kita butuhkan:
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
Text, |
6 |
View, |
7 |
Animated, |
8 |
Dimensions
|
9 |
} from 'react-native'; |
Kita menggunakan Dimensi
untuk mendapatkan lebar perangkat. Dari situ, kita bisa menghitung lebar yang tersedia untuk progress bar. Kita akan melakukannya dengan mengurangi jumlah paddings kiri dan kanan yang akan kita tambahkan ke kontainer, dan juga batas kiri dan kanan yang akan kita tambahkan ke bilah progres bar.
1 |
var { width } = Dimensions.get('window'); |
2 |
var available_width = width - 40 - 12; |
Untuk rumus di atas agar masuk akal, mari lewati langsung ke gaya:
1 |
const styles = StyleSheet.create({ |
2 |
container: { |
3 |
flex: 1, |
4 |
padding: 20, |
5 |
justifyContent: 'center' |
6 |
},
|
7 |
progress_container: { |
8 |
borderWidth: 6, |
9 |
borderColor: '#333', |
10 |
backgroundColor: '#ccc' |
11 |
},
|
12 |
progress_status: { |
13 |
color: '#333', |
14 |
fontSize: 20, |
15 |
fontWeight: 'bold', |
16 |
alignSelf: 'center' |
17 |
}
|
18 |
});
|
The container
memiliki padding
20 di setiap sisi — sehingga kita kurangi 40 dari the available_width
. The progress_container
memiliki batas 6 di setiap sisi, jadi kami hanya menggandakan itu lagi dan mengurangi 12 dari lebar bilah kemajuan.
1 |
export default class ProgressPage extends Component { |
2 |
|
3 |
constructor(props) { |
4 |
super(props); |
5 |
this.progress = new Animated.Value(0); |
6 |
this.state = { |
7 |
progress: 0 |
8 |
};
|
9 |
}
|
10 |
|
11 |
}
|
Membuat komponen, dan di dalam konstruktor buat nilai animasi untuk menyimpan nilai animasi saat ini untuk bilah kemajuan.
Saya mengatakan "values" karena kali ini kita akan menggunakan nilai animasi tunggal ini untuk menganimasi lebar dan warna background kemajuan. Anda akan melihat ini dalam tindakan nanti.
Selain itu, Anda juga perlu menginisialisasi kemajuan saat ini di negara bagian.
1 |
export default class ProgressPage extends Component { |
2 |
|
3 |
constructor(props) { |
4 |
super(props); |
5 |
this.progress = new Animated.Value(0); |
6 |
this.state = { |
7 |
progress: 0 |
8 |
};
|
9 |
}
|
10 |
|
11 |
}
|
Di dalam fungsi render ()
, progress_container
bertindak sebagai wadah untuk bilah progres, dan <Animated.View
> di dalamnya adalah bilah progres aktual yang lebar dan warna latar belakangnya akan berubah tergantung pada kemajuan saat ini. Di bawah ini, kami juga memberikan kemajuan saat ini dalam bentuk teks (0% untuk 100%).
1 |
render() { |
2 |
return ( |
3 |
<View style={styles.container}> |
4 |
<View style={styles.progress_container}> |
5 |
<Animated.View |
6 |
style={[this.getProgressStyles.call(this)]} |
7 |
>
|
8 |
</Animated.View> |
9 |
</View> |
10 |
<Text style={styles.progress_status}> |
11 |
{ this.state.progress } |
12 |
</Text> |
13 |
</View> |
14 |
);
|
15 |
}
|
Gaya untuk progress bar dikembalikan oleh fungsi getProgressStyles
(). Di sini kita sedang menggunakan nilai animasi dari sebelumnya untuk menghitung lebar dan latar belakang warna. Hal ini dilakukan agar tidak perlu membuat nilai animasi yang terpisah untuk setiap animasi karena kita sedang interpolasi nilai yang sama pula. Jika kita menggunakan dua nilai-nilai terpisah, kita akan perlu memiliki dua animasi secara paralel, yang kurang efisien.
1 |
getProgressStyles() { |
2 |
var animated_width = this.progress.interpolate({ |
3 |
inputRange: [0, 50, 100], |
4 |
outputRange: [0, available_width / 2, available_width] |
5 |
});
|
6 |
//red -> orange -> green
|
7 |
const color_animation = this.progress.interpolate({ |
8 |
inputRange: [0, 50, 100], |
9 |
outputRange: ['rgb(199, 45, 50)', 'rgb(224, 150, 39)', 'rgb(101, 203, 25)'] |
10 |
});
|
11 |
|
12 |
return { |
13 |
width: animated_width, |
14 |
height: 50, //height of the progress bar |
15 |
backgroundColor: color_animation |
16 |
}
|
17 |
}
|
Animasi segera dijalankan setelah komponen dipasang. Mulailah dengan menetapkan nilai awal kemajuan, dan kemudian menambahkan pendengar untuk nilai kemajuan saat ini. Hal ini memungkinkan kita untuk memperbarui negara setiap kali perubahan nilai kemajuan. Kita menggunakan parseInt ()
, jadi nilai progresnya dikonversi ke bilangan bulat. Setelah itu, kita mulai animasi dengan durasi 7.000 milidetik (7 detik). Setelah itu selesai, kita mengubah teks progress untuk selesai!
1 |
componentDidMount() { |
2 |
this.progress.setValue(0); |
3 |
this.progress.addListener((progress) => { |
4 |
this.setState({ |
5 |
progress: parseInt(progress.value) + '%' |
6 |
});
|
7 |
});
|
8 |
|
9 |
Animated.timing(this.progress, { |
10 |
duration: 7000, |
11 |
toValue: 100 |
12 |
}).start(() => { |
13 |
this.setState({ |
14 |
progress: 'done!' |
15 |
})
|
16 |
});
|
17 |
}
|
Memperluas Halaman

Laman perluasan (src / pages / ExpandPage.js
) menunjukkan cara menghubungkan status transisi secara visual menggunakan gerakan yang meluas dan menyusut. Penting untuk menunjukkan kepada pengguna bagaimana elemen tertentu muncul. Ini menjawab pertanyaan dari mana elemen itu berasal dan apa perannya dalam konteks saat ini. Seperti biasa, mulai dengan mengimpor hal-hal yang kita butuhkan:
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
Text, |
6 |
View, |
7 |
Animated
|
8 |
} from 'react-native'; |
9 |
|
10 |
import Icon from 'react-native-vector-icons/FontAwesome'; |
11 |
import ScalingButton from '../components/ScalingButton'; |
Di dalam constructor()
, buat nilai animasi yang akan menyimpan posisi-y saat ini dari menu. Idenya adalah memiliki kotak besar yang cukup untuk memuat semua item menu.
Awalnya, kotak itu akan memiliki nilai negatif untuk bottom
bawah. Ini berarti bahwa hanya ujung seluruh kotak akan ditampilkan secara default. Setelah pengguna mengetuk menu, seluruh kotak akan terlihat seolah-olah diperluas, padahal kenyataannya kita hanya mengubah bottom
bawah sehingga semuanya ditampilkan.
Anda mungkin bertanya-tanya mengapa kita menggunakan pendekatan ini daripada hanya menskalakan kotak untuk mengakomodasi semua anak-anaknya. Itu karena kita hanya perlu menskala atribut ketinggian. Pikirkan apa yang terjadi pada gambar ketika Anda hanya menyesuaikan tinggi atau lebar mereka saja — mereka terlihat memanjang. Hal yang sama akan terjadi pada elemen-elemen di dalam kotak.
Kembali ke constructor
(), kita juga menambahkan bendera negara yang menunjukkan apakah menu saat ini diperluas atau tidak. Kita memerlukan ini karena kita perlu menyembunyikan tombol untuk memperluas menu jika menu sudah diperluas.
1 |
export default class ExpandPage extends Component { |
2 |
|
3 |
constructor(props) { |
4 |
super(props); |
5 |
this.y_translate = new Animated.Value(0); |
6 |
this.state = { |
7 |
menu_expanded: false |
8 |
};
|
9 |
}
|
10 |
|
11 |
...
|
12 |
}
|
Di dalam fungsi render ()
, tentukan bagaimana bottom
bawah akan diterjemahkan. InputRange
adalah 0
dan 1
, dan outputRange
adalah 0
dan -300
. Jadi jika y_translate
memiliki nilai 0
, tidak ada yang akan terjadi karena ekuivalen outputRange
adalah 0
. Tetapi jika nilainya menjadi 1
, posisi bottom
menu diterjemahkan ke -300
dari posisi semula.
Perhatikan tanda negatif, karena jika itu hanya 300
, kotak akan turun lebih jauh. Jika angka negatif, sebaliknya akan terjadi.
1 |
render() { |
2 |
const menu_moveY = this.y_translate.interpolate({ |
3 |
inputRange: [0, 1], |
4 |
outputRange: [0, -300] |
5 |
});
|
6 |
|
7 |
...
|
8 |
}
|
Agar ini lebih masuk akal, mari lewati ke gaya:
1 |
const styles = StyleSheet.create({ |
2 |
container: { |
3 |
flex: 10, |
4 |
flexDirection: 'column' |
5 |
},
|
6 |
body: { |
7 |
flex: 10, |
8 |
backgroundColor: '#ccc' |
9 |
},
|
10 |
footer_menu: { |
11 |
position: 'absolute', |
12 |
width: 600, |
13 |
height: 350, |
14 |
bottom: -300, |
15 |
backgroundColor: '#1fa67a', |
16 |
alignItems: 'center' |
17 |
},
|
18 |
tip_menu: { |
19 |
flexDirection: 'row' |
20 |
},
|
21 |
button: { |
22 |
backgroundColor: '#fff' |
23 |
},
|
24 |
button_label: { |
25 |
fontSize: 20, |
26 |
fontWeight: 'bold' |
27 |
}
|
28 |
});
|
Perhatikan gaya footer_menu
. height
totalnya ditetapkan menjadi 350
, dan posisi bottom
adalah -300
, yang berarti hanya 50
teratas yang ditampilkan secara default. Ketika menerjemahkan animasi dijalankan untuk memperluas menu, posisi bottom
berakhir dengan nilai 0
. Mengapa? Karena jika Anda masih ingat aturan ketika mengurangi angka negatif, dua tanda minus menjadi positif. Jadi (-300) - (-300)
menjadi (-300) + 300
.
Kita semua tahu apa yang terjadi ketika menambahkan angka positif dan negatif: mereka membatalkan satu sama lain. Jadi posisi bottom
akhirnya menjadi 0
, dan seluruh menu akan ditampilkan.
Kembali ke fungsi render
(), kita memiliki konten utama (body
) dan menu footer, yang merupakan salah satu yang akan diperluas dan menyusut. The translateY
transform digunakan untuk menerjemahkan posisinya di sumbu Y. Karena seluruh container
memiliki flex: 10
dan body
juga flex: 10
, titik awal sebenarnya di bagian paling bawah layar.
1 |
return ( |
2 |
<View style={styles.container}> |
3 |
<View style={styles.body}></View> |
4 |
<Animated.View |
5 |
style={[ |
6 |
styles.footer_menu, |
7 |
{
|
8 |
transform: [ |
9 |
{
|
10 |
translateY: menu_moveY |
11 |
}
|
12 |
]
|
13 |
}
|
14 |
]}
|
15 |
>
|
16 |
|
17 |
...
|
18 |
|
19 |
</Animated.View> |
20 |
</View> |
21 |
);
|
Di dalam <Animated.View
>adalah tip_menu
dan menu lengkap. Jika menu diperluas, kami tidak ingin menu tip ditampilkan, jadi kami hanya merendernya jika menu_expanded
disetel ke false
.
1 |
{
|
2 |
!this.state.menu_expanded && |
3 |
<View style={styles.tip_menu}> |
4 |
<ScalingButton onPress={this.openMenu.bind(this)} noDefaultStyles={true}> |
5 |
<Icon name="ellipsis-h" size={50} color="#fff" /> |
6 |
</ScalingButton> |
7 |
</View> |
8 |
}
|
Di sisi lain, kami hanya ingin menampilkan menu lengkap jika menu_expanded
disetel ke true
. Setiap button akan mengecilkan menu kembali ke posisi semula.
1 |
{
|
2 |
!this.state.menu_expanded && |
3 |
<View style={styles.tip_menu}> |
4 |
<ScalingButton onPress={this.openMenu.bind(this)} noDefaultStyles={true}> |
5 |
<Icon name="ellipsis-h" size={50} color="#fff" /> |
6 |
</ScalingButton> |
7 |
</View> |
8 |
}
|
Saat membuka menu, status harus diperbarui terlebih dahulu sehingga menu tersembunyi akan ditampilkan. Hanya setelah selesai, animasi terjemahan akan dijalankan. Ini menggunakan Animated.spring
sebagai lawan dari Animated.timing
untuk menambahkan sedikit main-main ke animasi. Semakin tinggi nilai yang Anda berikan pada friction
, semakin sedikit pemantulan di sana. Ingat untuk tidak berlebihan pada animasi Anda karena alih-alih membantu pengguna, mereka bisa menjadi jengkel.
1 |
openMenu() { |
2 |
this.setState({ |
3 |
menu_expanded: true |
4 |
}, () => { |
5 |
this.y_translate.setValue(0); |
6 |
Animated.spring( |
7 |
this.y_translate, |
8 |
{
|
9 |
toValue: 1, |
10 |
friction: 3 |
11 |
}
|
12 |
).start(); |
13 |
});
|
14 |
}
|
hideMenu ()
melakukan kebalikan dari showMenu ()
, jadi kita cukup membalikkan apa yang dilakukannya:
1 |
hideMenu() { |
2 |
this.setState({ |
3 |
menu_expanded: false |
4 |
}, () => { |
5 |
this.y_translate.setValue(1); |
6 |
Animated.spring( |
7 |
this.y_translate, |
8 |
{
|
9 |
toValue: 0, |
10 |
friction: 4 |
11 |
}
|
12 |
).start(); |
13 |
});
|
14 |
}
|
Halaman AttentionSeeker

Last but not least adalah halaman attentionseeker (src / pages / AttentionSeekerPage.js).
Saya tahu bahwa tutorial ini sudah cukup panjang, jadi untuk mempersingkatnya, mari gunakan paket react-native-animatable untuk mengimplementasikan animasi untuk halaman ini.
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
Text, |
6 |
View
|
7 |
} from 'react-native'; |
8 |
|
9 |
import * as Animatable from 'react-native-animatable'; |
10 |
import ScalingButton from '../components/ScalingButton'; |
Buat larik yang berisi jenis animasi dan warna latar belakang yang akan digunakan untuk setiap kotak:
1 |
var animations = [ |
2 |
['bounce', '#62B42C'], |
3 |
['flash', '#316BA7'], |
4 |
['jello', '#A0A0A0'], |
5 |
['pulse', '#FFC600'], |
6 |
['rotate', '#1A7984'], |
7 |
['rubberBand', '#435056'], |
8 |
['shake', '#FF6800'], |
9 |
['swing', '#B4354F'], |
10 |
['tada', '#333333'] |
11 |
];
|
Membuat komponen:
1 |
export default class AttentionSeekerPage extends Component { |
2 |
|
3 |
...
|
4 |
}
|
Fungsi render ()
menggunakan fungsi renderBoxes ()
untuk membuat tiga baris yang akan membuat masing-masing tiga kotak.
1 |
render() { |
2 |
return ( |
3 |
<View style={styles.container}> |
4 |
<View style={styles.row}> |
5 |
{ this.renderBoxes(0) } |
6 |
</View> |
7 |
|
8 |
<View style={styles.row}> |
9 |
{ this.renderBoxes(3) } |
10 |
</View> |
11 |
|
12 |
<View style={styles.row}> |
13 |
{ this.renderBoxes(6) } |
14 |
</View> |
15 |
</View> |
16 |
);
|
17 |
}
|
Fungsi renderBoxes()
menuliskan kotak animasi. Ini menggunakan indeks awal yang disediakan sebagai argumen untuk mengekstrak bagian tertentu dari array dan membuatnya secara individual.
Di sini kita menggunakan komponen<Animatable.View
>bukan <Animated.View
>. Ini menerima animation
dan iterationCount
Jumlah sebagai alat peraga. The animation
menentukan jenis animasi yang ingin Anda lakukan, dan iterationCount
menentukan berapa kali Anda ingin menjalankan animasi. Dalam hal ini, kita hanya ingin bug pengguna sampai mereka menekan kotak.
1 |
renderBoxes(start) { |
2 |
var selected_animations = animations.slice(start, start + 3); |
3 |
return selected_animations.map((animation, index) => { |
4 |
return ( |
5 |
|
6 |
<ScalingButton |
7 |
key={index} |
8 |
onPress={this.stopAnimation.bind(this, animation[0])} |
9 |
noDefaultStyles={true} |
10 |
>
|
11 |
<Animatable.View |
12 |
ref={animation[0]} |
13 |
style={[styles.box, { backgroundColor: animation[1] }]} |
14 |
animation={animation[0]} |
15 |
iterationCount={"infinite"}> |
16 |
<Text style={styles.box_text}>{ animation[0] }</Text> |
17 |
</Animatable.View> |
18 |
</ScalingButton> |
19 |
|
20 |
);
|
21 |
});
|
22 |
}
|
stopAnimation()
berhenti kotak dari menjadi animasi. Ini menggunakan "ref" untuk mengidentifikasi setiap kotak secara unik sehingga mereka dapat dihentikan secara individual.
1 |
stopAnimation(animation) { |
2 |
this.refs[animation].stopAnimation(); |
3 |
}
|
Akhirnya, tambahkan gaya:
1 |
const styles = StyleSheet.create({ |
2 |
container: { |
3 |
flex: 1, |
4 |
flexDirection: 'column', |
5 |
padding: 20 |
6 |
},
|
7 |
row: { |
8 |
flex: 1, |
9 |
flexDirection: 'row', |
10 |
justifyContent: 'space-between' |
11 |
},
|
12 |
box: { |
13 |
alignItems: 'center', |
14 |
justifyContent: 'center', |
15 |
height: 100, |
16 |
width: 100, |
17 |
backgroundColor: '#ccc' |
18 |
},
|
19 |
box_text: { |
20 |
color: '#FFF' |
21 |
}
|
22 |
});
|
Kesimpulan
Dalam tutorial ini, Anda telah belajar bagaimana menerapkan beberapa animasi yang biasa digunakan dalam aplikasi seluler. Secara khusus, Anda telah belajar bagaimana menerapkan animasi yang memberikan umpan balik visual, menunjukkan status sistem saat ini, secara visual menghubungkan status transisi, dan menarik perhatian pengguna.
Seperti biasa, masih banyak yang harus dipelajari saat terkait animasi. Misalnya, kita masih belum menyentuh bidang-bidang berikut:
- Cara melakukan animasi pada gerakan pengguna tertentu seperti menyeret, menjentikkan, mencubit, dan menyebar. Misalnya, ketika pengguna menggunakan gerakan sebar, Anda harus menggunakan animasi skala untuk menunjukkan bagaimana elemen yang terlibat menjadi lebih besar.
- Bagaimana menganimasikan transisi berbagai elemen dari satu negara ke negara lain. Misalnya, ketika menampilkan daftar foto, Anda mungkin ingin melakukan animasi terhuyung untuk menunda pemunculan semua foto.
- Intro animasi untuk pengguna pertama kali aplikasi. Video dapat digunakan sebagai alternatif, tetapi ini juga tempat yang bagus untuk menerapkan animasi.
Mungkin saya akan membahas beberapa topik tersebut di tutorial selanjutnya. Sementara itu, lihat beberapa kursus dan tutorial kita lainnya tentang React Native!
Build a Social App With React Native
Get Started With React Native Layouts
Animate Your React Native App
Creating a Dictionary App Using React Native for Android