پرسپکتیو ۳ بعدی در فلاتر — راهنمای کاربردی

۱۲۶ بازدید
آخرین به‌روزرسانی: ۱۵ مهر ۱۴۰۲
زمان مطالعه: ۶ دقیقه
پرسپکتیو ۳ بعدی در فلاتر — راهنمای کاربردی

در این مقاله یک برنامه ساده می‌سازیم که شیوه استفاده از ویجت Transform فلاتر برای ارائه یک پرسپکتیو 3 بعدی را نشان می‌دهد. «پرسپکتیو» (Perspective) یک بازنمایی گرافیکی آسان در فلاتر است که با استفاده از آن می‌توان کارهایی انجام داد که حتی پیاده‌سازی‌اش در سیستم‌های مبتنی بر ویجت‌های نیتیو نیز دشوار است. با ما همراه باشید تا با پرسپکتیو 3 بعدی در فلاتر آشنا شوید.

در تصویر زیر اپلیکیشنی را می‌بینید که می‌خواهیم بسازیم. دایره سفید کوچک موقعیت انگشت کاربر را روی صفحه نشان می‌دهد.

شروع

مثال مورد بررسی ما با اپلیکیشن آشنای پیش‌فرض فلاتر آغاز می‌شود. برای ساخت این اپلیکیشن باید از دستور flutter create و یا IDE برای تولید یک پروژه فلاتر استفاده کنید. ما می‌خواهیم دو چیز جدید به این پروژه پیش‌فرض اضافه کنیم که یکی ویجت Transform و دیگری ویجت GestureDetector است.

ابتدا ویجت transform را اضافه خواهیم کرد:

1// v1: move default app to separate function with fixed name
2// Add transform widget, rotate and perspective
3import 'package:flutter/material.dart';
4
5void main() => runApp(MyApp());
6
7class MyApp extends StatelessWidget {
8  @override
9  Widget build(BuildContext context) {
10    return MaterialApp(
11      title: 'Perspective',
12      theme: ThemeData(
13        primarySwatch: Colors.blue,
14      ),
15      home: MyHomePage(),
16    );
17  }
18}
19
20class MyHomePage extends StatefulWidget {
21  MyHomePage({Key key}) : super(key: key); // changed
22
23  @override
24  _MyHomePageState createState() => _MyHomePageState();
25}
26
27class _MyHomePageState extends State<MyHomePage> {
28  int _counter = 0;
29  Offset _offset = Offset(0.4, 0.7); // new
30
31  void _incrementCounter() {
32    setState(() {
33      _counter++;
34    });
35  }
36
37  @override
38  Widget build(BuildContext context) {
39    return Transform(  // Transform widget
40      transform: Matrix4.identity()
41        ..setEntry(3, 2, 0.001) // perspective
42        ..rotateX(_offset.dy)
43        ..rotateY(_offset.dx),
44      alignment: FractionalOffset.center,
45      child: _defaultApp(context),
46    );
47  }
48
49  _defaultApp(BuildContext context) {  // new
50    return Scaffold(
51      appBar: AppBar(
52        title: Text('The Matrix 3D'), // changed
53      ),
54      body: Center(
55        child: Column(
56          mainAxisAlignment: MainAxisAlignment.center,
57          children: [
58            Text(
59              'You have pushed the button this many times:',
60            ),
61            Text(
62              '$_counter',
63              style: Theme.of(context).textTheme.display1,
64            ),
65          ],
66        ),
67      ),
68      floatingActionButton: FloatingActionButton(
69        onPressed: _incrementCounter,
70        tooltip: 'Increment',
71        child: Icon(Icons.add),
72      ),
73    );
74  }
75
76}

با اجرای کد فوق، اپلیکیشن پیش‌فرض نمایش می‌یابد که کمی در راستای 3 بعدی با پرسپکتیو می‌چرخد:

همچنین برخی موارد که نیاز نبود را حذف کرده‌ایم که شامل همه کامنت‌های اضافی و همه کلیدواژه‌های new می‌شود. برای مقاصد آموزشی بخش layout یعنی متد build مربوط به MyHomePageState_ اپلیکیشن پیش‌فرض را به متد مجزایی به نام defaultApp_ در خطوط 49 تا 74 جابجا کرده‌ایم. همچنین به منظور ساده‌سازی عنوان AppBar را در خط 52 تعیین کرده‌ایم و از ارسال آن به صورت پارامتر MyHomePage خودداری می‌کنیم.

ویجت Transform

ویجت Transform (+) در خطوط 30 تا 46 کد فوق اضافه شده است. در این بخش آن را با دقت بیشتری مورد بررسی قرار می‌دهیم. ویجت Transform یک ماتریس سه‌بعدی چرخش می‌گیرد که یک Matrix4 (+) است. دلیل این که ماتریس 3 بعدی ارائه می‌کنیم، این است که فلاتر علاوه بر گرافیک دوبعدی امکان مدیریت گرافیک‌های 3 بعدی را نیز دارد.

امروزه اغلب گوشی‌های هوشمند مجهز به پردازنده‌های گرافیکی هستند که بسیار سریع محسوب می‌شوند و برای گرافیک‌های 3 بعدی بهینه‌سازی شده‌اند. این بدان معنی است که گرافیک‌های 3 بعدی کاملاً سریع هستند. در نتیجه تقریباً هر آن چیزی که روی صفحه می‌بینید، به صورت 3 بعدی رندر می‌شود و این شامل حتی گرافیک‌های 2 بعدی نیز می‌شود.

تنظیم ماتریس تبدیل به ما امکان می‌دهد که هر آن چه را روی صفحه می‌بینیم، حتی به صورت 3 بعدی ویرایش کنیم. برای ایجاد این ماتریس، کار خود را از یک ماتریس همانی (خط 40 کد فوق) آغاز می‌کنیم و سپس آن را تبدیل می‌کنیم. تبدیل‌ها خاصیت جابه‌جایی‌پذیری ندارند، بنابراین باید آن‌ها را با ترتیب صحیحی اعمال کنیم. ماتریس کامل نهایی به GPU ارسال می‌شود تا اشیایی که رندر می‌شوند، تبدیل شوند.

تبدیل‌ها یک موضوع پیچیده هستند، اما اگر می‌خواهید در مورد آن‌ها اطلاعات بیشتری کسب کنید، می‌توانید از هر مقاله مقدماتی در مورد گرافیک 3 بعدی برای مثال در مورد ماتریس‌های تبدیل (+) و مختصات همگن (+) ‌استفاده کنید.

پرسپکتیو

نخستین تبدیل (در خط 41) اقدام به پیاده‌سازی پرسپکتیو می‌کند. پرسپکتیو (+) موجب می‌شود که اشیایی که دورتر هستند، کوچک‌تر به نظر برسند. ردیف 3 و ستون 2 روی مقدار 0.001 تعیین می‌شود تا همه چیز بر مبنای مسافتش کوچک‌نمایی شود.

شاید بپرسید این عدد 0.001 از کجا می‌آید. در واقع این عدد منطق خاصی ندارد، می‌تواند این را تغییر دهید تا مقدار پرسپکتیو کاهش یا افزایش یابد و حالتی شبیه به زوم کردن با لنز دوربین دارد. هر چه این عدد بزرگ‌تر باشد، پرسپکتیو عمق بیشتری می‌یابد که موجب می‌شود به شیء دیده‌شده نزدیک‌تر شوید.

فلاتر یک تابع به نام makePerspectiveMatrix ارائه کرده است، اما این متد شامل آرگومان‌هایی است که نسبت ابعادی، عمق میدان و صفحه‌های نزدیک و دور را تنظیم می‌کند، بنابراین عنصر مورد نیاز ماتریس را به طور مستقیم تنظیم می‌کنیم.

چرخش‌ها

در خطوط 42 و 43 کد فوق دو چرخش بر مبنای مقدار متغیر offset_ اعمال می‌کنیم. از خط 39 کد به بعد از این متغیر برای ردگیری موقعیت انگشت کاربر استفاده کرده‌ایم. جالب است که چرخش X بر مبنای آفست Y و چرخش Y بر مبنای آفست X است.

پرسپکتیو 3 بعدی در فلاتر

به تصویر فوق که فلش‌های سبزرنگی برای نمایش محورهای X و Y اضافه شده است، توجه کنید. مبدأ پیش‌فرض این محورها گوشه بالا-چپ صفحه نمایش است، اما در خط 44 کد برنامه، مبدأ را روی مرکز صفحه تنظیم کرده‌ایم.

چرخش حول یک محور تعریف شده است، بنابراین rotateX، چرخش را حول محور X تعریف می‌کند که در جهت Y (رو به پایین) می‌چرخاند. به طور مشابه rotate اشیا را در جهت X (چپ به راست) چرخش می‌دهد. به همین جهت است که rotateX به وسیله offset.dy_ و rotateY به وسیله offset.dx_ کنترل می‌شود.

یک محور Z نیز وجود دارد که مبدأ آن سطح صفحه نمایش است و به صورت عمود از میان صفحه عبور کرده و به پشت گوشی می‌رود. به این ترتیب هر چه اشیا در جهت مثبت محور Z بیشتر حرکت کنند، از دید کاربر دورتر می‌شوند. در نتیجه متد rotate در صفحه متناظر با صفحه نمایش می‌چرخد.

تعامل

دومین و آخرین چیزی که باید به کد فوق اضافه کنیم، ویجت GestureDetector است. این کار در فلاتر بسیار آسان است.

1// v2: add Gesture detector
2import 'package:flutter/material.dart';
3
4void main() => runApp(MyApp());
5
6class MyApp extends StatelessWidget {
7  @override
8  Widget build(BuildContext context) {
9    return MaterialApp(
10      title: 'Perspective',
11      theme: ThemeData(
12        primarySwatch: Colors.blue,
13      ),
14      home: MyHomePage(),
15    );
16  }
17}
18
19class MyHomePage extends StatefulWidget {
20  MyHomePage({Key key}) : super(key: key); // changed
21
22  @override
23  _MyHomePageState createState() => _MyHomePageState();
24}
25
26class _MyHomePageState extends State<MyHomePage> {
27  int _counter = 0;
28  Offset _offset = Offset.zero; // changed
29
30  void _incrementCounter() {
31    setState(() {
32      _counter++;
33    });
34  }
35
36  @override
37  Widget build(BuildContext context) {
38    return Transform(  // Transform widget
39      transform: Matrix4.identity()
40        ..setEntry(3, 2, 0.001) // perspective
41        ..rotateX(0.01 * _offset.dy) // changed
42        ..rotateY(-0.01 * _offset.dx), // changed
43      alignment: FractionalOffset.center,
44      child: GestureDetector( // new
45        onPanUpdate: (details) => setState(() => _offset += details.delta),
46        onDoubleTap: () => setState(() => _offset = Offset.zero),
47        child: _defaultApp(context),
48      )
49    );
50  }
51
52  _defaultApp(BuildContext context) {
53    return Scaffold(
54      appBar: AppBar(
55        title: Text('The Matrix 3D'), // changed
56      ),
57      body: Center(
58        child: Column(
59          mainAxisAlignment: MainAxisAlignment.center,
60          children: [
61            Text(
62              'You have pushed the button this many times:',
63            ),
64            Text(
65              '$_counter',
66              style: Theme.of(context).textTheme.display1,
67            ),
68          ],
69        ),
70      ),
71      floatingActionButton: FloatingActionButton(
72        onPressed: _incrementCounter,
73        tooltip: 'Increment',
74        child: Icon(Icons.add),
75      ),
76    );
77  }
78
79}

در خط 28 کد فوق، offset_ با عدد 0 مقداردهی شده است. خطوط 44 تا 48 یک GestureDetector تعریف می‌کنند که دو نوع ژست را شناسایی می‌کند. یکی ژست pan یعنی کشیدن انگشت روی اطراف صفحه است و دیگری ژست دابل-تپ (دو ضربه متوالی روی صفحه) است. در خط 45 مقدار جابجایی انگشت بر حسب پیکسل به offset_ اضافه می‌شود. در خط 46 offset_ در زمانی که کاربر روی صفحه دابل-تپ کند به مقدار صفر ریست می‌شود. برای هر دوی این ژست‌ها، offset_ طوری زمان‌بندی‌شده که صفحه رسم مجدد شود.

در نهایت خطوط 41 و 42 کد فوق، طوری ویرایش شده‌اند که آفست (بر حسب پیکسل) با ضریب 0.01 مقیاس‌بندی شود تا استفاده از چرخش آسان‌تر شود. چرخش بر حسب رادیان است،‌ یعنی یک چرخش کامل شامل 2π یا تقریباً 6.28 است. بنابراین یک چرخش کامل نیازمند حرکت انگشت روی صفحه به میزان 628 پیکسل است. شما می‌توانید مقدار این ضریب را دست‌کاری کنید تا حساسیت چرخش به حرکت انگشت کاهش یا افزایش یابد. ضمناً پارامتر rotate منفی شده است، زیرا زمانی که انگشت به سمت راست حرکت می‌کند، تصویر در جهت پادساعت‌گرد حول محور Y می‌چرخد، چون محور Y به صورت وارونه است.

سخن پایانی

نکته جالب در مورد فلاتر این است که همه چیز به جای پلتفرم در داخل خود اپلیکیشن قرار دارد و این شامل ویجت‌ها و رندر مجدد نیز می‌شود. بدین ترتیب انعطاف‌پذیری زیادی ایجاد می‌شود. در این مورد می‌توانیم به سادگی به قابلیت‌های قدرتمند ارائه شده از سوی GPU دسترسی پیدا کنیم و حتی یک ویجت به این منظور وجود دارد. تغییرهایی که انجام دادیم صرفاً شامل 13 خط کد است!

لازم به اشاره است که وقتی اپلیکیشن را حول محورهای X و Y چرخاندید، دیگر ضربه زدن روی FAB برای افزایش شمارنده، دشوار خواهد شد. فلاتر اغلب تبدیل‌ها شامل مقیاس‌بندی و rotate را جبران می‌کند، بنابراین UI در این موارد همچنان به درستی کار می‌کند، اما در چرخش‌های کامل 3 بعدی برخی مشکلات وجود خواهد داشت. این موردی است که باید روی آن کار کنیم. توجه کنید که هیچ یک از اشیای این اپلیکیشن در عمل اشیای 3 بعدی نیستند. این‌ها اشیای دوبعدی مسطحی هستند، اما می‌توانیم آن‌ها را در فضای 3 بعدی به چرخش درآوریم. با استفاده از این امکان فلاتر، می‌توان کارهای خلاقانه زیادی انجام داد. برای نمونه در تصویر زیر یکی از کاربردهای آن را برای ایجاد یک انیمیشن تا خوردن می‌بینید:

پرسپکتیو 3 بعدی در فلاتر

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
flutter
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *