آموزش React Native: توسعه سوئیچ چند اسلایدری – به زبان ساده


در این نوشته به معرفی و توسعه یک کامپوننت کارکردی با انیمیشنهای 60 فریم بر ثانیه در فریمورک React Native میپردازیم. هدف ما این است که یک ماژول سوئیچِ قابل انتخاب سهتایی ایجاد کنیم که بتوان از آن برای تغییر دادن حالت یک کار استفاده کرد. در این نوشته موارد زیر آموزش داده خواهند شد:
- کامپوننتهای کارکردی بیحالت React
- API های انیمیت شده
- استفاده از PanResponder
- اجرای انیمیشنهای با نرخ 60 فریم بر ثانیه بدون افت فریم.
سرآغاز
در ابتدا با دستور زیر یک پروژه جدید React Native ایجاد میکنیم:
react-native init multiSwitch
راهاندازی اولیه
قبل از هر کاری باید یک پوشه مجزا به نام multi switch ایجاد کنیم تا همه کدهای مرتبط با این کامپوننت را در آن قرار دهیم.
یک فایل index.js به این پوشه اضافه میکنیم تا ماژول را اکسپورت کنیم و سپس میتوانیم فایلی به نام MultiSwitch.js ایجاد کنیم.
import React, { Component } from 'react'; import {View,} from 'react-native'; import styles from './styles'; export default class MultiSwitch extends Component { render() { return <view style={styles.container} />; } }
سپس به ویو خود یک سبک اولیه میدهیم تا کدنویسی خود را آغاز کنیم:
const Metrics = { containerWidth: width - 30, switchWidth: width / 2.7 }; const styles = StyleSheet.create({ container: { width: Metrics.containerWidth, height: 55, flexDirection: 'row', backgroundColor: Colors.mBackColor, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: Colors.mBorderColor, borderRadius: 27.5 },
برای این ویو یک borderRadius، ارتفاع و رنگ پسزمینه مناسب میدهیم. با تعیین رنگ حاشیه و عرض آن به مقدار 1 به اشکال سر و سامان میدهیم و آیکونهایی نیز که رویداد کلیک را در برمیگیرند اضافه میکنیم.
کامپوننت دکمه
const Button = props => { return ( <View> <TouchableOpacity style={styles.buttonStyle}> <Image source={getIcon(props.type, props.active)} /> </TouchableOpacity> </View> ); }; Button.propTypes = { type: PropTypes.string, }; export default Button;
عرض آن باید 3/1 ویوی دربرگیرندهاش باشد.
buttonStyle: { flex: 1, width: Metrics.containerWidth / 3, height: 54, justifyContent: 'center', alignItems: 'center' }
به روش زیر دکمهها را به ویوی اصلی اضافه میکنیم:
کد رندر سوئیچ
render() { return ( <View style={styles.container}> <Button type="Open" /> <Button type="In Progress" /> <Button type="Complete" /> <View> ); }
اینک ما یک سطح مقدماتی داریم که میتوانیم برای آن کدنویسی کنیم.
سوئیچ اسلایدری
در این مرحله میتوانیم از انیمیشن نیز استفاده کنیم. ابتدا یک آیکون اسلایدر مناسب برای ماژول ایجاد میکنیم. این ویو به عنوان سوئیچی عمل میکند که با یک انیمیشنِ نرم مقدار خود را تغییر میدهد. در ادامه Animated.View را اضافه میکنیم:
render() { return ( <View style={styles.container}> <Button type="Open" /> <Button type="In Progress" /> <Button type="Complete" /> <Animated.View {...this._panResponder.panHandlers} style={[ styles.switcher, { transform: [{ translateX: this.state.position }] } ]} /> </View> </View> ); }
بدین ترتیب دکمه سوئیچ انیمیت شده اضافه شده است. میتوان سبکهای مختلف شامل shadow و دیگر انواع سبکدهی را برای سوئیچ تعریف کرد:
سبک اسلایدر
switcher: { flexDirection: 'row', position: 'absolute', top: 0, left: 0, backgroundColor: Colors.white, borderRadius: 28, height: 53, alignItems: 'center', justifyContent: 'center', width: Metrics.switchWidth, elevation: 4, shadowOpacity: 0.31, shadowRadius: 10, shadowColor: Colors.shadowColor }
در نهایت دکمه سوئیچ ما به شکل زیر درمیآید:

PanResponder
در React Native میتوان از PanResponder برای شناسایی ژستهای چند لمسی و همچنین سوایپ کردن و دیگر انواع لمس استفاده کرد و بدین ترتیب اپلیکیشنهای native را به روشی زندهتر و شهودیتر طراحی نمود. اما راهاندازی و اجرایی نمودن این وضعیت ممکن است خستهکننده و تا حد زیادی دشوار باشد.
زمانی که منطق تشکیلدهنده responder را شناختید و با متدهای مختلف آن آشنا شدید، کار با آن آسانتر میشود و میتوانید ژستهای مختلفی را با آن بسازید.
اینها مواردی هستند که باید در ابتدا بدانید:
- onPanResponderGrant – زمانی فراخوانی میشود که ژست آغاز شود.
- onPanResponderMove – زمانی فراخوانی میشود که کاربر انگشت خود را روی صفحه بکشد.
- onPanResponderRelease - زمانی فراخوانی میشود که کاربر همه لمسها را رها کند.
متدهای زیر امکان گرفتن ژستهای لمسی مرتبط با هر ویو را فراهم میسازند:
- onStartShouldSetPanResponder: (evt, gestureState) => true,
- onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
- onMoveShouldSetPanResponder: (evt, gestureState) => true,
- onMoveShouldSetPanResponderCapture: (evt, gestureState) => true
به بیان خلاصه کاری که باید بکنیم این است که در صورت وجود هر گونه onPanResponderGrant ابتدا باید اسکرول را غیر فعال کنیم تا از تداخلهای احتمالی جلوگیری نماییم. سپس شروع به بهروزرسانی مقدار موقعیت و یافتن مکان نهایی توقف اسلایدر روی onPanResponderRelease میکنیم و سپس حالت را تنظیم میکنیم.
onPanResponderGrant: () => { // disable parent scroll if slider is inside a scrollview if (!this.isParentScrollDisabled) { this.props.disableScroll(false); this.isParentScrollDisabled = true; } },
gestureState.dx در موارد تغییر دخالت میکند
onPanResponderMove: (evt, gestureState) => { let finalValue = gestureState.dx + this.state.posValue; if ( finalValue >= 0 && finalValue <= this.state.thresholdDistance ) this.state.position.setValue( this.state.posValue + gestureState.dx ); },
محاسبه finalValue بر مبنای gestureState و جهت
onPanResponderRelease: (evt, gestureState) => { let finalValue = gestureState.dx + this.state.posValue; this.isParentScrollDisabled = false; this.props.disableScroll(true); if (gestureState.dx > 0) { if (finalValue >= 0 && finalValue <= 30) { this.notStartedSelected(); } else if (finalValue >= 30 && finalValue <= 121) { this.inProgressSelected(); } else if (finalValue >= 121 && finalValue <= 280) { if (gestureState.dx > 0) { this.completeSelected(); } else { this.inProgressSelected(); } } } else { if (finalValue >= 78 && finalValue <= 175) { this.inProgressSelected(); } else if (finalValue >= -100 && finalValue <= 78) { this.notStartedSelected(); } else { this.completeSelected(); } } },
ما به صورت افقی حرکت میکنیم از این رو باید از gestureState.dx برای دریافت مقدار استفاده کنیم. اگر جهت حرکت عمودی بود باید از gestureState.dy استفاده میکردیم. اگر gestureState.dx >0 باشد به این معنی است که به سمت جلو حرکت میکنیم و در صورت منفی بودن به معنی حرکت رو به عقب است.
کد فوق مسلماً بهترین روش برای محاسبه موقعیت نیست؛ اما زمانی که یک نمونه اولیه داشته باشیم که کار میکند، میتوانیم شروع به بهبود موارد مختلف بکنیم.
اینک همه محدودههایی که متناظر با یک بخش در اسلایدر هستند را داریم. بنابراین میتوانیم Animated.timing را برای پشتیبانی از انیمیشنهای اسلایدر به هر بخش اضافه کنیم تا وقتی کاربر روی هر بخش تَپ میکند واکنش نشان دهند. کدی مانند زیر برای محاسبه toValue استفاده میشود و اسلایدر را در طی زمان مشخصی تا رسیدن به مقدار نهایی انیمیت میکند.
منطق مقدماتی انتخاب
completeSelected = () => { Animated.timing(this.state.position, { toValue: Platform.OS === 'ios' ? this.state.mainWidth - this.state.switcherWidth : this.state.mainWidth - this.state.switcherWidth - 2, duration: this.state.duration }).start(); setTimeout(() => { this.setState({ posValue: Platform.OS === 'ios' ? this.state.mainWidth - this.state.switcherWidth : this.state.mainWidth - this.state.switcherWidth - 2, selectedPosition: 2 }); }, 100); if (this.state.isComponentReady) this.props.onStatusChanged('Complete'); };
با اندکی محاسبات مقدماتی و اصلاح باگ ما اینک یک کامپوننت اسلایدر داریم که به طرز بینقصی عمل میکند.

در تصویر متحرک زیر میتوانید کارکرد عملی سوئیچ اسلایدر طراحی شده را مشاهده کنید:
اگر این نوشته برای شما مفید بوده است، پیشنهاد ما استفاده از منابع آموزشی زیر است:
- مجموعه آموزشهای برنامهنویسی
- آموزش مقدماتی فریمورک React Native برای طراحی نرم افزارهای اندروید و iOS با زبان جاوا اسکریپت
- مجموعه آموزشهای پروژه محور برنامهنویسی
- مجموعه آموزشهای پایگاه داده و سیستم های مدیریت اطلاعات
- چگونه با React Native اپلیکیشن اندرویدی بنویسیم؟ — به زبان ساده
- ری اکت (React) — راهنمای جامع برای شروع به کار
- آموزش فریمورک React — ساخت یک سیستم طراحی با قابلیت استفاده مجدد
- واکشی (Fetch) کردن داده ها در اپلیکیشن های React — به زبان ساده
==