کتابخانه React Native Motion و کاربردهای آن — از صفر تا صد
گذار انیمیت شده یا Animated Transition یکی از چالشهای توسعهدهندههای اپلیکیشن در فریمورک ریاکت نیتیو محسوب میشود. انیمیشنها امروزه بخشی جداییناپذیر از رابط کاربری اپلیکیشنهای موبایل محسوب میشوند و استفاده از آنها در فریمورکهای چند پلتفرمی مانند ریاکت نیتیو نیز ضروری است.
در تصویر فوق چندین انیمیشن را شاهد هستیم که شامل عنوان نوارابزار (پیدا/پنهان)، نوار تحتانی (پیدا/پنهان)، جا به جایی آیتم انتخاب شده، پنهانسازی همه موارد دیگر، نمایش جزییات آیتم و حتی موارد بیشتر است.
همگامسازی انیمیشنها
نکته دشوار این گذار، همگامسازی همه این انیمیشنها با یکدیگر است. ما نمیتوانیم «صفحه لیست» (List Page) را واقعاً حذف کرده و «صفحه جزییات» (Detail Page) را نمایش دهیم، زیرا باید منتظر بمانیم تا همه انیمیشنها به پایان برسند. ضمناً مطمئناً همه ما طرفدار کد تمیز هستیم که نگهداری آسانی داشته باشد.
اگر تاکنون اقدام به پیادهسازی انیمیشن در پروژههای خود کرده باشید، میدانید که کد به طور معمول خیلی زود شلوغ و بههمریخته میشود. تعدد متغیرهای کمکی، محاسبات پیچیده و موارد دیگر موجب این وضعیت میشوند. به همین دلیل است که در این راهنما میخواهیم از کتابخانه react-native-motion (+) استفاده کنیم.
معرفی React-native-motion
آیا میتوانید انیمیشن عنوان نوارابزار را ببینید؟ کافی است عنوان را کمی جا به جا کنیم و شفافیت آن را بین 0 تا 1 تغییر دهیم. کار دشواری به نظر نمیرسد، اما برای انجام همین کار ساده باید کدهای زیادی مانند زیر بنویسید و این کار حتی پیش از آن که عملاً بتوانید شروع به نوشتن رابط کاربری برای کامپوننت بکنید ضروری است.
1class TranslateYAndOpacity extends PureComponent {
2 constructor(props) {
3 // ...
4 this.state = {
5 opacityValue: new Animated.Value(opacityMin),
6 translateYValue: new Animated.Value(translateYMin),
7 };
8 // ...
9 }
10 componentDidMount() {
11 // ...
12 this.show(this.props);
13 // ...
14 }
15 componentWillReceiveProps(nextProps) {
16 if (!this.props.isHidden && nextProps.isHidden) {
17 this.hide(nextProps);
18 }
19 if (this.props.isHidden && !nextProps.isHidden) {
20 this.show(nextProps);
21 }
22 }
23 show(props) {
24 // ...
25 Animated.parallel([
26 Animated.timing(opacityValue, { /* ... */ }),
27 Animated.timing(translateYValue, { /* ... */ }),
28 ]).start();
29 }
30 hide(props) {
31 // ...
32 Animated.parallel([
33 Animated.timing(opacityValue, { /* ... */ }),
34 Animated.timing(translateYValue, { /* ... */ }),
35 ]).start();
36 }
37 render() {
38 const { opacityValue, translateYValue } = this.state;
39
40 const animatedStyle = {
41 opacity: opacityValue,
42 transform: [{ translateY: translateYValue }],
43 };
44
45 return (
46 <Animated.View style={animatedStyle}>{this.props.children}</Animated.View>
47 );
48 }
49}
اینک نگاهی به شیوه استفاده از کتابخانه react-native-motion به این منظور خواهیم داشت. میدانیم که انیمیشنها در اغلب موارد کاملاً اختصاصی هستند. همچنین میدانیم که ریاکت نیتیو، یک API انیمیت بسیار قدرتمند ارائه کرده است. در هر صورت بهتر است کتابخانهای داشته باشیم که انیمیشنهای ابتدایی داشته باشد.
1import { TranslateYAndOpacity } from 'react-native-motion';
2
3class ToolbarTitle extends PureComponent {
4 render() {
5 return (
6 <TranslateYAndOpacity duration={250}>
7 <View>
8 // ...
9 </View>
10 </TranslateYAndOpacity>
11 );
12 }
13}
عنصر مشترک
مهمترین مشکل این چالش، جابهجایی آیتم لیست انتخاب شده است. این همان آیتمی است که بین صفحه لیست و صفحه جزییات مشترک است. چگونه میتوانیم آیتم را از FlatList به ابتدای صفحه جزییات انتقال دهیم؛ در حالی که عنصر مربوطه در عمل دارای «موقعیتیابی مطلق» (absolutely position) است. انجام این کار با استفاده از react-native-motion بسیار آسان است.
1// List items page with source of SharedElement
2import { SharedElement } from 'react-native-motion';
3
4class ListPage extends Component {
5 render() {
6 return (
7 <SharedElement id="source">
8 <View>{listItemNode}</View>
9 </SharedElement>
10 );
11 }
12}
ما عنصر منبع SharedElement را روی صفحه لیست مشخص ساختهایم. اینک باید تقریباً همین کار را برای عنصر مقصد روی صفحه جزییات انجام دهیم تا موقعیتی که میخواهیم عنصر مشترک به آن جا برود مشخص شود.
1// Detail page with a destination shared element
2import { SharedElement } from 'react-native-motion';
3
4class DetailPage extends Component {
5 render() {
6 return (
7 <SharedElement sourceId="source">
8 <View>{listItemNode}</View>
9 </SharedElement>
10 );
11 }
12}
طرز کار این کتابخانه چگونه است؟
چگونه میتوانیم یک عنصر با «موقعیتیابی نسبی» (relatively position) را از یک صفحه به صفحه دیگر انتقال دهیم؟ در واقع چنین کاری ممکن نیست. طرز کار SharedElement به صورت زیر است:
- موقعیت عنصر منبع را دریافت میکنیم.
- موقعیت عنصر مقصد را دریافت میکنیم (بدیهی است که بدون وجود این مرحله انیمیشن نمیتواند آغاز شود).
- یک کلون از عنصر مشترک ایجاد میکنیم (نکته اصلی همین جا است).
- یک لایه حدید روی صفحه ایجاد میکنیم.
- عنصر کلون شده را که روی عنصر منبع قرار میگیرد رندر میکنیم.
- جا به جایی به موقعیت مقصد را آغاز میکنیم.
- زمانی که به موقعیت مقصد رسیدیم، عنصر کلون شده را حذف میکنیم.
شما احتمالاً میتوانید تصور کنید که 3 عنصر در یک گره مشترک در یک لحظه وجود دارند. دلیل این مسئله آن است که صفحه لیست در طی این انیمیشن جابجایی، از سوی صفحه جزییات پوشانده شده است. به همین دلیل است که میتوانیم هر 3 عنصر را ببینیم. اما ما میخواهیم این توهم ایجاد شود که آیتم منبع اصلی واقعاً جا به جا میشود.
اینک میتوانید نقطههای A و B را ببینید. این همان مدتی است که انیمیشن در حال اجرا است. میتوانید ببینید که SharedElement برخی رویدادهای مفید را اجرا میکند. در این مثال، از رویدادهای WillStart و DidFinish استفاده میکنیم. تعیین شفافیت عناصر منبع و مقصد به صورت 0 در زمان جابجایی به مقصد بر عهده شماست و در انتها یعنی زمانی که انیمیشن به پایان رسید باید شفافیت عنصر مقصد به میزان 1 تعیین شود. کتابخانه react-native-motion همچنان نیاز به کار بیشتری دارد. این کتابخانه قطعاً نسخه نهایی و پایداری محسوب نمیشود، اما برای شروع بسیار امیدبخش است.
اگر این نوشته برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای طراحی و توسعه پروژههای وب
- آموزش مقدماتی فریمورک React Native برای طراحی نرم افزارهای اندروید و iOS با زبان جاوا اسکریپت
- مجموعه آموزشهای برنامهنویسی
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- ۶ روش آسان برای سرعت بخشیدن به اپلیکیشن های React Native
- چگونه با React Native اپلیکیشن اندرویدی بنویسیم؟ — به زبان ساده
==