آموزشگاه برنامه نویسی تحلیل داده
آموزشگاه برنامه نویسی تحلیل داده

آموزش انیمیشن ها در React Native

دوره های مرتبط با این مقاله

آموزش انیمیشن ها در React Native

انیمیشن ها در ایجاد یک تجربه کاربری فوق العاده، بسیار موثرند. انیمیشن به شما امکان انتقال حس حرکت را در UI می دهد. React Native دو سیستم مکمل برای ایجاد انیمیشن را در اختیار می گذارد: Animated و LayoutAnimation.

Animated API

این API نمایش گستره ای از حرکات و الگوهای تعاملی را، با performance خوب، به سادگی امکان پذیر می کند. Animated روی ارتباط بین ورودی و خروجی ها ، با transform های قابل تنظیم تمرکز دارد، و از متدهای ساده ی start/stop برای کنترل زمانی اجرای انیمیشن، استفاده می کند.

Animated شش component با قابلیت متحرک سازی در اختیار می گذارد: View، Image، ScrollView، FlatList، و SectionList. شما می توانید component های متحرک خود را با استفاده از Animated.createAnimatedComponent() داشته باشید.

برای مثال در کد زیر، یک view container، هنگام load شدن، از effect fade in استفاده می کند:


import React from 'react';
import { Animated, Text, View } from 'react-native';
class FadeInView extends React.Component {
  state = {
    fadeAnim: new Animated.Value(0),  // Initial value for opacity: 0
  }
  componentDidMount() {
    Animated.timing(                  // Animate over time
      this.state.fadeAnim,            // The animated value to drive
      {
        toValue: 1,                   // Animate to opacity: 1 (opaque)
        duration: 10000,              // Make it take a while
      }
    ).start();                        // Starts the animation
  }
  render() {
    let { fadeAnim } = this.state;
    return (
                        < Animated.View                 // Special animatable View
        style={{
          ...this.props.style,
          opacity: fadeAnim,         // Bind opacity to animated value
        }}
       >
        {this.props.children}
      < /Animated.View >
    );
  }
}
// You can then use your `FadeInView` in place of a `View` in your components:
export default class App extends React.Component {
  render() {
    return (
                        < View style={ {flex: 1, alignItems: 'center', justifyContent: 'center'}} >
                        < FadeInView style={ {width: 250, height: 50, backgroundColor: 'powderblue'}} >
                        < Text style={ {fontSize: 28, textAlign: 'center', margin: 10}} >Fading in< /Text >
        < /FadeInView >
      < /View >
    )
  }
}

در این کد، در سازنده ی FadeInView یک Animated.Value جدید به نام fadeAnim به عنوان بخشی از state، مقداردهی اولیه می شود. property opacity در View به این مقدار نگاشت می شود. پشت صحنه، مقدار عددی جدا می شود و برای مقداردهی به opacity استفاده می شود.

وقتی component بارگذاری می شود، مقدار opacity صفر است. مقدار fadeAnim به آرامی به یک می رسد و هرچیزی که وابسته به آن باشد (در اینجا opacity) update می کند. این کار بصورتی بهینه انجام می شود و سریعتر از فراخوانی setState و render دوباره component، عمل می کند. از آنجا که کل تنظیمات گویاست، می توانیم بهینه سازی های بیشتری انجام دهیم که تنظیمات را serialize کند و انیمیشن را روی یک thread با اولویتی بالا، اجرا کند.

config انیمیشن ها

انیمیشن ها به میزان زیادی قابل تنظیم هستند. توابع آماده و سفارشی easing function، delays، durations، decay factors، spring constants، و توابع دیگر همه می توانند برای هرساخت نوع انیمیشنی، تغییر کنند.

Animated انواع مختلف انیمیشن را در اختیار می گذارد، رایج ترین آن Animated.timing() است که متحرک سازی یک مقدار در زمان با استفاده از توابع آماده ی easing function مختلف، یا توابع سفارشی خودتان را ممکن می کند. این توابع برای نمایش کاهش و افزایش تدریجی سرعت حرکت اشیا به کار می رود.

به طور پیش فرض، timing از یک منحنی easeInOut برای نمایش افزایش تدریجی سرعت تا رسیدن به نهایت سرعت، استفاده می کند و به کاهش تدریجی سرعت تا توقف کامل منجر می شود. میتوانید از طریق پاس دادن پارامتر easing، تابع easing function مختلفی را برای این کار مشخص کنید. تعیین duration خاص یا حتی delay قبل از شروع حرکت هم ممکن است.

برای مثال، اگر بخواهیم یک انیمیشن دو ثانیه ای از یک شی بسازیم که قبل از حرکت به مکان نهایی اش، کمی عقب برود:


Animated.timing(this.state.xPosition, {
  toValue: 100,
  easing: Easing.back(),
  duration: 2000,
}).start();

نگاهی به بخش config انیمیشن ها در مستندات Animated API بیندازید.

ساخت انیمیشن

حرکت ها می توانند با هم ادغام شوند و پشت سرهم یا همزمان پخش شوند. انیمیشن های ترتیبی می توانند بلافاصله پس از اتمام انمیشن قبلی بخش شوند، یا پس از کمی تاخیر شروع شوند. Animated API متدهایی مثل sequence()، و delay() دارد که هرکدام می توانند آرایه ای از انیمیشن ها برای اجرا بگیرند و متدهای start()/stop() آن ها را برحسب نیاز فراخوانی کنند.

برای مثال، این انیمیشن حرکت می کند، متوقف می شود، و سپس همزمان درحال چرخش شروع به حرکت می کند:


Animated.sequence([
  // decay, then spring to start and twirl
  Animated.decay(position, {
    // coast to a stop
    velocity: {x: gestureState.vx, y: gestureState.vy}, // velocity from gesture release
    deceleration: 0.997,
  }),
  Animated.parallel([
    // after decay, in parallel:
    Animated.spring(position, {
      toValue: {x: 0, y: 0}, // return to start
    }),
    Animated.timing(twirl, {
      // and twirl
      toValue: 360,
    }),
  ]),
]).start(); // start the sequence group

اگر یکی از انیمیشن ها دچار وقفه شود یا چیزی مانع کامل شدنش شود، همه ی انیمیشن های گروه متوقف می شوند. برای جلوگیری از این الگو، Animated.parallel یک گزینه ی stopTogether دارد که می توان آن را false کرد.

ترکیب animated values

میتوانید animated values ها را از طریق متدهای addition، multiplication، division، یا modulo ترکیب کنید و animated values های جدیدی بدست آورید.

مواردی هست که یک animated values برای محاسبه به عکس یک animated values احتیاج دارد. برای مثال هنگام وارونه کردن مقیاس (2x-->0.5x).


const a = new Animated.Value(1);
const b = Animated.divide(1, a);
Animated.spring(a, {
  toValue: 2,
}).start();

درون یابی (Interpolation )

هر property ممکن است اول در یک interpolation قرار بگیرد. یک interpolation گستره ی ورودی را به یک گستره ی خروجی نگاشت می کند، معمولا از تناسبی خطی برای این کار استفاده می کند، اما می توان توابع دیگر easing function هم به آن داد. به طور پیش فرض، منحنی را فراتر از گستره ی داده شده امتداد می دهد، اما می توانید آن را محدود به مقدار خروجی کنید.

یک مثال ساده برای نگاشت گستره ی 0-1 به گستره ی 0-100 به صورت زیر خواهد بود:


value.interpolate({
  inputRange: [0, 1],
  outputRange: [0, 100],
});

برای مثال ممکن است به Animated.Value در گستره ی 0 تا 1 نگاه کنید، ولی بخواهید مکان را از 0px تا 150px متغیر در نظر بگیرید و opacity را از 0 تا 1. این کار براحتی با تغییر style در مثال بالا به شکل زیر امکان پذیر است:


style={{
    opacity: this.state.fadeAnim, // Binds directly
    transform: [{
      translateY: this.state.fadeAnim.interpolate({
        inputRange: [0, 1],
        outputRange: [150, 0]  // 0 : 150, 0.5 : 75, 1 : 0
      }),
    }],
  }}

مثال:


value.interpolate({
  inputRange: [-300, -100, 0, 100, 101],
  outputRange: [300, 0, 1, 0, 0],
});

که به جدول زیر نگاشت می شود:


 Input | Output
-------|-------
  -400|    450
  -300|    300
  -200|    150
  -100|      0
   -50|    0.5
     0|      1
    50|    0.5
   100|      0
   101|      0
   200|      0

تابع Interpolate() نگاشت به string را نیز پشتیبانی می کند، و به این طریق علاوه بر مقادیر دارای واحد، متحرک سازی رنگ ها را هم ممکن می کند. برای مثال اگر بخواهید یک چرخش را نمایش دهید:


value.interpolate({
  inputRange: [0, 360],
  outputRange: ['0deg', '360deg'],
});

تابع Interpolate() توابع تصادفی easing function را نیز پشتیبانی می کند که اکثرا در ماژول Easing پیاده سازی شده اند.

تابع Interpolate() برای برون یابی outputRange نیز قابل تنظیم است. می توانید با استفاده از extrapolate، extrapolateLeft و extrapolateRight برون یابی را تنظیم کنید. مقدار پیش فرض extend است اما می توانید از clamp برای محدود کردن مقدار خروجی به outputRange استفاده کنید.

دنبال کردن مقادیر متغیر

value ها می توانند از مقادیر دیگر پیروی کنند. کافی است مقدار toValue یک انیمیشن را به جای یک عدد به یک animated value دیگر set کنید. برای مثال، یک انیمیشن "Chat Heads" مثل چیزی که توسط Messenger در android استفاده شده را می توان توسط یک spring() وابسته به یک animated value دیگر، یا یک timing() و یک duration = 0 پیاده سازی کرد. آن ها می توانند با interpolation هم ترکیب شوند:


 Animated.spring(follower, {toValue: leader}).start();
Animated.timing(opacity, {
  toValue: pan.x.interpolate({
    inputRange: [0, 300],
    outputRange: [1, 0],
  }),
}).start();

Animated value های leader و follower با استفاده از Animated.ValueXY() پیاده سازی می شوند. ValueXY راهی کاربردی هنگام کار با تعاملات دوبعدی مثل panning و dragging هستند. ValueXY یک wrapper ساده است که دو نمونه Animated.Value دارد و توابعی که با آن ها کار کند، همین موجب شده اغلب جایگزین Value شود. درمثال بالا، به ما امکان دنبال کردن مقادیر x و y را می دهد.

دنبال کردن حرکات

حرکاتی مثل panning و scroll کردن، و event های دیگر را می توان توسط Animated.event مستقیما به animated value ها نگاشت کرد.

برای مثال، هنگام کار با scroll افقی، برای نگاشت event.nativeEvent.contentOffset.x به scroll که یک Animated.Value است، به شکل زیر عمل می کنیم:


onScroll={Animated.event(
   // scrollX = e.nativeEvent.contentOffset.x
   [{ nativeEvent: {
        contentOffset: {
          x: scrollX
        }
      }
    }]
 )}

هنگام استفاده از PanResponder، می توانید از کد بعدی برای بدست آوردن مقادیر x وy از gestureState.dx و gestureState.dy استفاده کنید. به اولین مقدار داخل آرایه، null داده ایم چرا که فقط با دومین ارگومان ارسال شده به PanResponder کار داریم، یعنی gestureState است که اهمیت دارد.


onPanResponderMove={Animated.event(
  [null, // ignore the native event
  // extract dx and dy from gestureState
  // like 'pan.x = gestureState.dx, pan.y = gestureState.dy'
  {dx: pan.x, dy: pan.y}
])}

واکنش به مقدار فعلی animation value

احتمالا به این موضوع توجه کرده اید که هیچ راه مشخصی برای خواندن مقدار فعلی درحین animate کردن وجود ندارد، چرا که این مقداربه دلایل بهینه سازی فقط در زمان اجرا قابل خواندن است. اگر می خواهید در واکنش به مقدار فعلی کدی جاوااسکریپتی اجرا کنید، دو راه دارید:


  • spring.stopAnimation(callback) انیمیشن را متوقف می کند و Callback را با مقدار نهایی فراخوانی می کند. این کار هنگام ایجاد gesture transitions مناسب است.
  • spring.addListener(callback) را به صورت نامقارن درحین اجرای انیمیشن با مقدار فعلی فراخوانی می کند. این کار هنگام انجام تغییر در state مناسب است، چرا که تغییرات اساسی state ، در مقایسه با حرکات پیوسته ای مثل panning که نیازمند اجرا در 60 fps است، نسبت به کندی درحد چند frame حساسیت کمتری دارند.

Animated طوری طراحی شده که کاملا serializable باشد و انیمیشن ها بتوانند مستقل از event loop جاوااسکریپت با performance بالا اجرا شوند. این موضوع روی API تاثیر می گذارد. برای یافتن راهکاری هایی برای دور زدن این محدودیت ها، Animated.Value.addListener را ببیینید. اما از آنجا که ممکن است روی performance تاثیرخوبی نداشته باشد، زیاد از آن استفاده نکنید.

استفاده از native driver

Animated API طوری طراحی شده که serializable باشد. با استفاده از native driver، همه چیز درمورد انیمیشن را قبل از شروع اجرای آن، به native می فرستیم و به کد native اجازه می دهیم انیمیشن را روی UI thread اجرا کند، بدون اینکه برای هر frame مجبور شود روی bridge برود. وقتی انیمیشن شروع شد، JS thread می تواند block شود، بدون اینکه تاثیری روی انیمیشن داشته باشد.

استفاده از native driver برای انیمیشن های عادی بسیار ساده است. فقط useNativeDriver: true را به config انیمیشن هنگام شروع آن اضافه کنید:


Animated.timing(this.state.animatedValue, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true, // < -- Add this
}).start();

Animated value ها فقط با یک driver سازگارند، پس اگر برای یک animate روی یک مقدار از native driver استفاده کردید، همه انیمیشن ها روی آن مقدار، باید از native driver استفاده کنند.

native driver با Animated.event نیز کار می کند. این به ویژه برای زمانی که انیمیشن ها مکان scroll را دنبال می کنند کارآمد است. چرا که به دلیل ذات async بودن React Native، بدون native driver انیمیشن همواره یک frame از حرکت scroll عقب است.


< Animated.ScrollView // < -- Use the Animated ScrollView wrapper
                            scrollEventThrottle={1} // < -- Use 1 here to make sure no events are ever missed
                                                           onScroll={Animated.event(
                                                           [
                                                           {
                                                           nativeEvent {
                                                           contentOffset {y this.state.animatedValue},
                                                           },
                                                           },
                                                           ],
                                                           {useNativeDriver true}, // < -- Add this
                                                                                          )}  >
  {content}
< /Animated.ScrollView   >

می توانید با اجرای RNTester app ، native driver را در عمل ببینید. source code آن نیز قابل دسترس است.

هشدار ها

همه کارهایی که می توان با Animated انجام داد فعلا توسط native driver پشتیبانی نمی شود. محدودیت اصلی آن است که فقط می توان non-layout property را animate کرد، برای مثال transform و opacity کار می کند، اما flexbox و property های مربوط به positioning کار نمی کند. استفاده از Animated.event فقط با direct events ها، و نه bubbling event ها، کار می کند. این به این معناست که با PanResponder کار نمی کند، اما با مواردی مثل ScrollView#onScroll کار می کند.

هنگامی که یک انیمیشن درحال اجراست، جلوی render شدن آیتم های بیشتر در component VirtualizedList را می گیرد. اگر می خواهید در حین scroll کردن بین آیتم های درون لیست، انیمشین طولانی یا تکرارشونده ای اجرا شود، از isInteraction: false استفاده کنید.

به خاطر داشته باشید

هنگام استفاده از استایل های transform مثل rotate و rotateX، و غیره، از وجود perspective اطمینان حاصل کنید. فعلا بعضی انیمیشن ها در android بدون آن render نمیشود. مثلا:


< Animated.View style={{
    transform: [
      {scale: this.state.scale},
      {rotateY: this.state.rotateY},
      {perspective: 1000}, // without this line this Animation will not render on Android while working fine on iOS
    ],
  }} / >

مثال های بیشتر

application RNTester مثال های بیشتری از Animated را در عمل نشان میدهد:


LayoutAnimation API

LayoutAnimation به شما امکان config کردن create و update انیمیشن هایی را که در همه viewها در دور بعدی render/layout استفاده می شوند را، می دهد. برای بروزرسانی flexbox layout مناسب است. به خصوص برای زمان هایی که تغییرات در layout، parentهای آن را هم ممکن است تحت تاثیر قرار دهد.

توجه کنید با وجود اینکه LayoutAnimation قوی است و میتواند کاربردی باشد، کنترل کمتری نسبت به Animated و دیگر کتابخانه های انیمیشین در اختیار توسعه دهندگان می گذارد. اگر کاری که مد نظرتان است توسط این کتابخانه ممکن نیست، از کتابخانه های دیگر استفاده کنید.

توجه کنید برای اینکه روی android جواب بگیرید باید flag های زیر را روی UIManager مقداردهی کنید:


import React from 'react';
import {
  NativeModules,
  LayoutAnimation,
  Text,
  TouchableOpacity,
  StyleSheet,
  View,
} from 'react-native';
const { UIManager } = NativeModules;
UIManager.setLayoutAnimationEnabledExperimental &&
  UIManager.setLayoutAnimationEnabledExperimental(true);
export default class App extends React.Component {
  state = {
    w: 100,
    h: 100,
  };
  _onPress = () = > {
    // Animate the update
    LayoutAnimation.spring();
    this.setState({w: this.state.w + 15, h: this.state.h + 15})
  }
  render() {
    return (
                        < View style={styles.container} >
                        < View style={[styles.box, {width this.state.w, height this.state.h}]} / >
                        < TouchableOpacity onPress={this._onPress} >
                        < View style={styles.button} >
                        < Text style={styles.buttonText} >Press me!< /Text >
          < /View >
        < /TouchableOpacity >
      < /View >
    );
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 200,
    height: 200,
    backgroundColor: 'red',
  },
  button: {
    backgroundColor: 'black',
    paddingHorizontal: 20,
    paddingVertical: 15,
    marginTop: 15,
  },
  buttonText: {
    color: '#fff',
    fontWeight: 'bold',
  },
});

این مثال از یک مقدار از پیش تعیین شده استفاده می کند. می توانید انیمیشن ها را مطابق نظرتان تغییر دهید. برای اطلاعات بیشتر LayoutAnimation.js

نکات دیگر

requestAnimationFrame

ممکن است با requestAnimationFrame در مرورگر آشنا باشید. یک تابع به عنوان تنها آرگومانش می پذیرد و آن را قبل از repaint بعدی صدا می زند. این یکی از المانهای اساسی سازنده انیمیشن هاست. عموما نباید این تابع را خودتان صدا بزنید، animation API ها خودupdate frame را برای شما مدیریت می کنند.

setNativeProps

همان طور که در قبلا اشاره شد، setNativeProps به ما اجازه ی دستکاری مستقیم property های component هایی که براساس viewهای native هستند، را می دهد. بدون اینکه لازم باشد setState و render دوباره انجام شود.

می توانیم از این موضوع در مثال Rebound برای update کردن مقیاس استفاده کنیم – این ممکن است کاربردی باشد اگر آن component که می خواهیم آپدیت کنیم درون component دیگری باشد و با shouldComponentUpdate ، optimize نشده باشد.

اگر انیمیشن هایتان frame از دست میدهند (زیر 60 frame در ثانیه اجرا میشوند)، از setNativeProps یا shouldComponentUpdate برای بهبود performance آن ها استفاده کنید. راه دیگر این است که با استفاده از NativeDriver، به جای اجرای آن ها روی thread جاوااسکریپتی، آن را روی UI thread اجرا کنید. با استفاده از InteractionManager کارهایی که از نظر محاسباتی سنگین هستند را به بعد از اتمام انیمیشن موکول کنید. می توانید rate frame را با استفاده از ابزار "FPS Monitor" مانیتور کنید.

  • 581
  •    0
  • تاریخ ارسال :   1398/05/31

دانشجویان گرامی اگر این مطلب برای شما مفید بود لطفا ما را در GooglePlus محبوب کنید
رمز عبور: tahlildadeh.com یا www.tahlildadeh.com
ارسال دیدگاه نظرات کاربران
شماره موبایل دیدگاه
عنوان پست الکترونیک

ارسال

آموزشگاه برنامه نویسی تحلیل داده
آموزشگاه برنامه نویسی تحلیل داده

تمامی حقوق این سایت متعلق به آموزشگاه تحلیل داده می باشد .