در این پست من به طور خلاصه در مورد موارد پایه و پیشرفته درمورد موضوعات انیمیشن ها در ریکت نیتیو و عمیق میشیم در مورد مشکلات واقعی که داشتیم و اینکه چجوری باید حلش کنیم. خوشبختانه این به شما این ایده رو میده که چجوری بقیه مشکلات رو هم حل کنید.
برنامه هایی که با ریکت نیتیو نوشته میشود هر روز بیشتر و بیشتر میشه اما هنوز سوال اصلی باقیست: آیا ریکت نیتیو رقیب شایسته ای برای اپ های native هست؟ بعد از همه چیز بزرگترین تفاوت میان اپ های نیتیو و ریکت نیتیو پرفورمنس یا عملکرد برنامه هست, و اما کجا این عملکرد نمود بیشتری داره؟ درست حدس زدی: انیمیشن ها
هر چیزی که توی اپ یه Transition داره مثل رفتاری که دکمه بعد از کلیک نشون میده یا نشون دادن یک Toast, باید یک انیمیشن نرم با نرخ ۶۰ فریم بر ثانیه داشته باشه!
وقتی برای مدتی با React native کار میکنی به یک سری چیز ها پی میبری که میتونی خارج از چارچوب فکر کنی, بعضاشون میتونه کاری کنه اپ حس بهتری بده.
اگه با قابلیت useNativeDriver
در کتابخونه RN Animated Library آشنا نیستید اصرار میکنم که در موردش بخونید.
کوتاه بگم: این قابلیت از ترد Native برای انجام تمام این جادو ها استفاده میکنه.
Animated.timing(this.state.fadeAnim, { toValue: 1, duration: 300, useNativeDriver: true, }).start();
اگه از useNativeDriver
استفاده نکنیم برای هر فریم از انیمیشن باید تمام مسیر رندر رو بریم و در آخر هم با یه انیمیشن کند و لگی روبرو میشیم
اما کار ما اینجا تموم نمیشه, پاس دادن این قابلیت فقط در بعضی موارد برای ما برد سادهای هست, برای مثال این قابلیت فقط با چیز های غیر ساختاری کار میکنه, مثل backgroundColor
, opacity
و transformation
ها.
اولین نصیحت من اینه که یاد بگیرید چجوری باید از Transform ها استفاده کنید. شما میتونیم اجزا رو جابجا کنید (Translate), بچرخونید (Rotate) و حتی بزرگ یا کویچک کنید (Scale) هر جوری که دوست دارید, وقتی این هارو توی دستاتون دارید میتونید یه عالمه از نیاز هاتون رو برطرف کنید.
بعضی کتابخونه ها هستن که کمک میکنن این انیمیشن هارو با کد کمتری حتی بتونید داشته باشید مثل:
react-native-animatable
بعضی وقتا جابجا کردن یا بزرگ کردن برای ما کافی نیست و ما یه همچین چیزایی رو میخوایم
ما نیاز داریم این انیمیشن رو وقتی داشته باشیم که محتوی اون عوض میشه, یا حالت لودینگ. با اینکه این انیمیشن ها ساده برای انجام دادن به نظر میرسن, اینجوری نیست. دو مشکل اینجا داریم که باید حل کنیم
useNativeDriver
انجام بشه همینجوری که قبلا گفتیم -- و ما نمیخوایم یه انیمیشن لگی داشته باشیم.onLayout
هست که یعنی ما باید وایستیم تا لیاوت (سایز دکمه) عوض بشه, به زمان گذشته برگردیم و به اون مقدار انیمیت کنیم, البته اگه بتونیم در زمان سفر کنیم :)راه نجات! LayoutAnimation. این ابزار عالی یک نجات دهنده است, البته نیاز نیست بگیم که استفاده ازش چقدر آسونه.
کوتاه بگیم, ما برای چرخه بعدی رندر layout animation رو تریگر میکنیم, این کار یک Transition خوب به تغییر layout اضافه میکنه که البته بستگی به تنظیماتی داره که ست میکنید. در این سناریو ما فقط تغییر سایز دکمه رو میخوایم و فقط همین, کارمون تمومه.
مراقب باشید! تریگر کردن این روی تمام تغییرات Layout تاثیر خواهد گذاشت, حتی اگه از داخل یه کامپوننت خاص تریگر بشه, برای اینکه از اتفاق افتادن غیر ضروری باید از این در لایفسایکل متد ها استفاده کنیم. مثل componentDidUpdate
باید یه چیزی شبیه این داشته باشیم...
componentDidUpdate(prevProps, prevState) { if (prevState.loading !== this.state.loading){ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); } } render() { return ( <Button label={this.state.loading ? '' : 'Submit'}> {this.state.loading && <Loader/>} </Button>) }
اگه این پست یک Progress Bar داشت ما الان روی ۹۰٪ گیر کرده بودیم و خیلی آروم میرفتیم تا تمومش کنیم.
هر چیزی که تا الان در موردش صحبت کردیم برای اپ های ساده - معمولی بوده و کار رو در میاره :) چیز بعدی که در موردش صحبت میکنیم یخورده پیشرفتهاست ولی همچنان باید خارج از چارچوب فکر کنیم.
بعد اینکه مختصر در مورد Animated
, useNativeDriver
و LayoutAnimation
صحبت کردیم مورد بعدی مون interpolation عه.
یکی از قابلیت های کاربردی و جالب در انیمیشن های ریکت نیتیو که بهتره بدونیمش اسمش Interpolation هست که به فارسی میشه بهش گفت الحاق ولی خب ما نمیگیم :دی
اما Animated
به ما این اجازه رو میده که بتونیم مقادیر انیمیشن رو داخلش وارد (Interpolate) کنیم و برای اینکه چجوری این Transition ها رفتار کنن یکم لاجیک بنویسیم. مثلا میخوام یه انیمیشن Fade درست کنم, من میخوام پراپ Opacity رو انیمیت کنم... تغییر مقدار این از ۱ به صفر باید یه چیزی تو این مایه ها باشه:
Animated.timing(this.state.fadeAnimation, { toValue: 1, duration: 300, }).start(); <View style={{opacity: this.state.fadeAnimation}}/>
تا زمانی که انقدر ساده میخوایم انجام بدیم این یک مورد معمولی و خوبه, اما بعضی وقتا میخوایم در رنج ها پیچیده تری بیایم این کار رو بکنیم.
بیاید بگیم میخوایم یه عقربه ساعت رو با استفاده از Transform Rotate داخل یه دایره بچرخونیم.
در این مورد مقدایر ما که استفاده میکنیم درجه هاست (0deg–360deg
). چون که یه Animated Value نمیتونه این مقدار رو نگه داره ما به سادگی بجاش از عدد های معمولی ۰ تا ۳۶۰ استفاده میکنیم, بعد این مقداریر رو تبدیل یا تفسیر (interpolate) میکنیم به درجه ها, که یه چیزی شبیه به این میشه:
renderClockHand() { const clockSize = 150; const rotation = this.timeAnimation.interpolate({ inputRange: [0, 360], outputRange: ['0deg', '360deg'], }); return ( // rotating container <Animated.View width={clockSize} height={clockSize} style={{transform: [{rotate: rotation}]}}> // clock hand <View width={clockSize / 2} height={clockSize / 2} style={{borderRightWidth: 3}} /> </Animated.View> ); }
متاسفانه, ریکت نیتیو از transform-origin پشتیبانی نمیکنه, برای همین ما کانترینر رو داریم میچرخونیم و عقربه ثابت هستش, برای اینکه حس جابجا شدن عقربه رو بده.
این Interpolation یک Transition Functuion برای انیمیشن شما ایجاد میکنه. که میتونه Liner باشه مثل کاری که ما اینجا برای ساعت اینجام دادیم, یا غیر خطی (Non-Liner) باشه, بسته به این که به inputRange
و outputRange
چی داده میشه.
تا جایی که میتونید از انیمیشن ها در ریکت نیتیو استفاده کنید, به عنوان یک برنامهنویس سمت کاربر این وظیفه اجتماعی ماست که زندگی کاربرا رو چه بسا کوچیک, بهتر کنیم, حداقل وقتی توی گوشی رو نگاه کردن حالشون خوب بشه.
با تشکر از آقای ایتان شرابی بابت این پست که من سعی کردم به زبان خودمون ترجمه کنم فقط