تطوير الويب وسطح المكتب سريع الاستجابة مع Flutter
نشرت: 2022-03-10هذا البرنامج التعليمي ليس مقدمة إلى Flutter نفسها. هناك الكثير من المقالات ومقاطع الفيديو والعديد من الكتب المتاحة عبر الإنترنت مع مقدمات بسيطة ستساعدك على تعلم أساسيات Flutter. بدلاً من ذلك ، سنغطي الهدفين التاليين:
- الحالة الحالية لتطوير Flutter غير المحمول وكيف يمكنك تشغيل كود Flutter في المتصفح ، على كمبيوتر مكتبي أو كمبيوتر محمول ؛
- كيفية إنشاء تطبيقات سريعة الاستجابة باستخدام Flutter ، حتى تتمكن من رؤية قوتها - خاصةً كإطار عمل ويب - عند العرض الكامل ، وتنتهي بملاحظة حول التوجيه استنادًا إلى عنوان URL.
دعنا ندخله!
ما المقصود بالرفرفة ، ولماذا هي مهمة ، وما الذي تطورت إليه ، وأين تتجه
Flutter هو أحدث إطار عمل لتطوير التطبيقات من Google. تتصور Google أنها شاملة للجميع: ستمكّن من تنفيذ نفس الرمز على الهواتف الذكية من جميع العلامات التجارية ، وعلى الأجهزة اللوحية ، وعلى أجهزة الكمبيوتر المكتبية والمحمولة كتطبيقات أصلية أو كصفحات ويب.
إنه مشروع طموح للغاية ، لكن Google حققت نجاحًا مذهلاً حتى الآن ولا سيما في جانبين: في إنشاء إطار عمل مستقل حقًا عن النظام الأساسي لتطبيقات Android و iOS الأصلية التي تعمل بشكل رائع وجاهزة تمامًا للاستخدام الإنتاجي ، وفي إنشاء واجهة رائعة -نهاية إطار عمل ويب يمكنه مشاركة 100٪ من الكود مع تطبيق Flutter متوافق.
في القسم التالي ، سنرى ما الذي يجعل التطبيق متوافقًا وما هي حالة تطوير Flutter غير المحمول حتى الآن.
تطوير غير المحمول مع رفرفة
تم الإعلان عن التطوير غير المحمول باستخدام Flutter لأول مرة بطريقة مهمة في Google I / O 2019. يدور هذا القسم حول كيفية جعله يعمل ومتى يعمل.
كيفية تمكين تطوير الويب وسطح المكتب
لتمكين تطوير الويب ، يجب أن تكون أولاً على قناة Flutter التجريبية. هناك طريقتان للوصول إلى هذه النقطة:
- قم بتثبيت Flutter مباشرة على القناة التجريبية عن طريق تنزيل أحدث إصدار تجريبي مناسب من أرشيف SDK.
- إذا كان لديك Flutter مثبتًا بالفعل ، فانتقل إلى القناة التجريبية باستخدام
$ flutter channel beta
، ثم قم بإجراء التبديل نفسه عن طريق تحديث إصدار Flutter (وهو في الواقع عبارة عنgit pull
في مجلد تثبيت Flutter) مع$ flutter upgrade
.
بعد ذلك ، يمكنك تشغيل هذا:
$ flutter config --enable-web
يعد دعم سطح المكتب أكثر تجريبية ، خاصة بسبب نقص الأدوات لنظامي Linux و Windows ، مما يجعل تطوير المكونات الإضافية على وجه الخصوص مشكلة كبيرة ، ونظرًا لحقيقة أن واجهات برمجة التطبيقات المستخدمة من أجله مخصصة للاستخدام لإثبات صحة المفهوم وليس من أجل إنتاج. هذا بخلاف تطوير الويب ، الذي يستخدم مترجم dart2js الذي تم تجربته واختباره لإصدارات الإصدارات ، والتي لا يتم دعمها حتى لتطبيقات سطح المكتب الأصلية لنظامي التشغيل Windows و Linux.
ملاحظة : يعد دعم نظام التشغيل macOS أفضل قليلاً من دعم نظامي التشغيل Windows و Linux ، لكنه لا يزال غير جيد مثل دعم الويب وليس بجودة الدعم الكامل لأنظمة الأجهزة المحمولة.
لتمكين دعم تطوير سطح المكتب ، تحتاج إلى التبديل إلى قناة الإصدار master
باتباع نفس الخطوات الموضحة سابقًا للقناة beta
. بعد ذلك ، قم بتنفيذ ما يلي عن طريق استبدال <OS_NAME>
linux
بنظام Linux أو windows
أو macos
:
$ flutter config --enable-<OS_NAME>-desktop
في هذه المرحلة ، إذا كانت لديك مشكلات في أي من الخطوات التالية التي سأصفها لأن أداة Flutter لا تفعل ما أقول إنه يجب أن تفعله ، فإن بعض الخطوات الشائعة لتحرّي الخلل وإصلاحه هي كالتالي:
- قم بتشغيل
flutter doctor
للتحقق من المشكلات. يتمثل أحد الآثار الجانبية لأمر Flutter هذا في أنه يجب عليه تنزيل أي أدوات يحتاجها لا يتوفر بها. - قم بتشغيل
flutter upgrade
. - قم بإيقاف تشغيله وتشغيله مرة أخرى. قد تكون إجابة الدعم الفني القديمة من المستوى 1 لإعادة تشغيل جهاز الكمبيوتر الخاص بك هي فقط ما تحتاجه حتى تتمكن من الاستمتاع بالثروات الكاملة لـ Flutter.
تشغيل وبناء تطبيقات الويب Flutter
دعم الويب Flutter ليس سيئًا على الإطلاق ، وهذا ينعكس في سهولة تطوير الويب.
تشغيل هذا ...
$ flutter devices
… يجب أن يظهر على الفور إدخالاً لشيء مثل هذا:
Web Server • web-server • web-javascript • Flutter Tools
بالإضافة إلى ذلك ، يجب أن يتسبب تشغيل متصفح Chrome في عرض Flutter لإدخاله أيضًا. تشغيل flutter run
على مشروع Flutter متوافق (المزيد عن ذلك لاحقًا) عندما يظهر "الجهاز المتصل" الوحيد هو خادم الويب سيتسبب في بدء Flutter خادم ويب على localhost:<RANDOM_PORT>
، والذي سيسمح لك بالوصول إلى Flutter الخاص بك تطبيق الويب من أي متصفح.
إذا قمت بتثبيت Chrome ولكنه لا يظهر ، فأنت بحاجة إلى تعيين متغير البيئة CHROME_EXECUTABLE
على المسار إلى ملف Chrome القابل للتنفيذ.
تشغيل وبناء تطبيقات Flutter Desktop
بعد تمكين دعم Flutter لسطح المكتب ، يمكنك تشغيل تطبيق Flutter محليًا على محطة عمل التطوير لديك باستخدام flutter run -d <OS_NAME>
، مع استبدال <OS_NAME>
بنفس القيمة التي استخدمتها عند تمكين دعم سطح المكتب. يمكنك أيضًا إنشاء ثنائيات في دليل build
flutter build <OS_NAME>
.
قبل أن تتمكن من القيام بأي من ذلك ، يجب أن يكون لديك دليل يحتوي على ما يحتاج Flutter لبناءه لمنصتك. سيتم إنشاء هذا تلقائيًا عندما تنشئ مشروعًا جديدًا ، لكنك ستحتاج إلى إنشائه لمشروع حالي باستخدام flutter create .
. أيضًا ، واجهات برمجة تطبيقات Linux و Windows غير مستقرة ، لذلك قد تضطر إلى إعادة إنشائها لتلك الأنظمة الأساسية إذا توقف التطبيق عن العمل بعد تحديث Flutter.
متى يكون التطبيق متوافقًا؟
ما الذي قصدته طوال الوقت عند الإشارة إلى أن تطبيق Flutter يجب أن يكون "مشروعًا متوافقًا" حتى يعمل على سطح المكتب أو الويب؟ ببساطة ، أعني أنه يجب ألا يستخدم أي مكون إضافي لا يحتوي على تنفيذ خاص بالنظام الأساسي للنظام الأساسي الذي تحاول البناء عليه.
لتوضيح هذه النقطة تمامًا للجميع وتجنب سوء الفهم ، يرجى ملاحظة أن مكون Flutter الإضافي عبارة عن حزمة Flutter معينة تحتوي على رمز خاص بالنظام الأساسي ضروري لتوفير ميزاته.
على سبيل المثال ، يمكنك استخدام حزمة url_launcher
التي طورتها Google بقدر ما تريد (وقد ترغب في ذلك ، نظرًا لأن الويب مبني على ارتباطات تشعبية).
مثال على الحزمة التي طورتها Google والتي قد يمنع استخدامها تطوير الويب هو path_provider
، والذي يستخدم للحصول على مسار التخزين المحلي لحفظ الملفات فيه. هذا مثال على حزمة ، بالمناسبة ، لا تفيد أي تطبيق ويب ، لذا فإن عدم القدرة على استخدامها ليس مشكلة حقيقية ، باستثناء حقيقة أنك بحاجة إلى تغيير الكود الخاص بك من أجل للعمل على الويب إذا كنت تستخدمه.
على سبيل المثال ، يمكنك استخدام حزمة shared_preferences ، والتي تعتمد على HTML localStorage
على الويب.
تعتبر التحذيرات المماثلة صالحة فيما يتعلق بأنظمة سطح المكتب: عدد قليل جدًا من المكونات الإضافية متوافقة مع الأنظمة الأساسية لسطح المكتب ، وبما أن هذا سمة متكررة ، يجب القيام بمزيد من العمل على جانب سطح المكتب أكثر مما هو ضروري حقًا على Flutter للويب.
إنشاء تخطيطات مستجيبة في رفرفة
بسبب ما وصفته أعلاه ومن أجل التبسيط ، سأفترض بالنسبة لبقية هذا المنشور أن النظام الأساسي المستهدف هو الويب ، لكن المفاهيم الأساسية تنطبق على تطوير سطح المكتب أيضًا.
دعم الويب له فوائد ومسؤوليات. قد يبدو أن تكون مجبرًا إلى حد كبير على دعم أحجام الشاشة المختلفة بمثابة عيب ، ولكن ضع في اعتبارك أن تشغيل التطبيق في متصفحات الويب يمكّنك من رؤية كيف سيبدو تطبيقك بسهولة على الشاشات ذات الأحجام المختلفة ونسب العرض إلى الارتفاع ، دون الحاجة إلى التشغيل بشكل منفصل محاكيات الأجهزة المحمولة.
الآن ، لنتحدث عن الكود. كيف يمكنك جعل تطبيقك مستجيبًا؟
هناك نوعان من المنظورات التي يتم من خلالها إجراء هذا التحليل:
- "ما الأدوات المصغّرة التي أستخدمها أو التي يمكنني استخدامها والتي يمكنها أو يجب أن تتكيف مع الشاشات ذات الأحجام المختلفة؟"
- "كيف يمكنني الحصول على معلومات حول حجم الشاشة ، وكيف يمكنني استخدامها عند كتابة رمز واجهة المستخدم؟"
سنجيب على السؤال الأول لاحقًا. دعنا نتحدث أولاً عن الأخير ، لأنه يمكن التعامل معه بسهولة شديدة وهو في قلب القضية. هناك طريقتان للقيام بذلك:
- تتمثل إحدى الطرق في الحصول على المعلومات من
MediaQueryData
الخاصة بجذرMediaQuery
InheritedWidget
، والذي يجب أن يكون موجودًا في شجرة عناصر واجهة المستخدم حتى يعمل تطبيق Flutter (إنه جزء منMaterialApp/WidgetsApp/CupertinoApp
) ، والتي يمكنك الحصول عليها ، تمامًا مثل أيInheritedWidget
أخرى ، معMediaQuery.of(context)
، والتي لها خاصيةsize
، وهي من النوعSize
، وبالتالي لهاwidth
height
من النوعdouble
. - الطريقة الأخرى هي استخدام
LayoutBuilder
، وهو عنصر واجهة مستخدمbuilder
(تمامًا مثلStreamBuilder
أوFutureBuilder
) يمرر إلى وظيفة Builder (جنبًا إلى جنب معcontext
) كائنBoxConstraints
الذي يحتوي على خصائصminHeight
وmaxHeight
وminWidth
وmaxWidth
.
في ما يلي مثال على DartPad باستخدام MediaQuery
للحصول على قيود ، يكون الرمز الخاص بها كما يلي:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${MediaQuery.of(context).size.width}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${MediaQuery.of(context).size.height}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ); }
وإليك طريقة تستخدم LayoutBuilder
لنفس الشيء:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${constraints.maxWidth}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${constraints.maxHeight}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ) ); }
الآن ، دعنا نفكر في الأدوات التي يمكن أن تتكيف مع القيود.
قبضة الجميع ، دعنا نفكر في الطرق المختلفة لتخطيط العديد من الحاجيات وفقًا لحجم الشاشة.
القطعة التي تتكيف بسهولة هي GridView
. في الواقع ، لا يحتاج GridView
الذي تم إنشاؤه باستخدام مُنشئ GridView.extent
إلى أن تكون مشاركتك مستجيبة ، كما ترون في هذا المثال البسيط للغاية:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); }
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); }
يمكنك استيعاب محتوى بأحجام مختلفة عن طريق تغيير maxCrossAxisExtent
.
خدم هذا المثال في الغالب بغرض إظهار وجود مُنشئ GridView.extent
GridView
، ولكن الطريقة الأكثر ذكاءً للقيام بذلك تتمثل في استخدام GridView.builder
مع SliverGridDelegateWithMaxCrossAxisExtent
، في هذه الحالة حيث تظهر الحاجيات في الشبكة يتم إنشاؤها ديناميكيًا من بنية بيانات أخرى ، كما ترى في هذا المثال:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => Scaffold( body: GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ) ); }
مثال على تكيف GridView مع الشاشات المختلفة هو صفحتي المقصودة الشخصية ، وهي عبارة عن تطبيق ويب Flutter بسيط للغاية يتكون من GridView
مع مجموعة من Cards
، تمامًا مثل رمز المثال السابق ، باستثناء أن Cards
أكثر تعقيدًا وأكبر قليلاً .
سيكون التغيير البسيط للغاية الذي يمكن إجراؤه على التطبيقات المصممة للهواتف هو استبدال Drawer
بقائمة دائمة على اليسار عندما يكون هناك مساحة.
على سبيل المثال ، يمكن أن يكون لدينا ListView
للأدوات ، مثل ما يلي ، والذي يُستخدم للتنقل:
class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); }
على الهاتف الذكي ، هناك مكان شائع لاستخدامه يكون داخل Drawer
(يُعرف أيضًا باسم قائمة الهامبرغر).
قد تكون البدائل لذلك هي BottomNavigationBar
أو TabBar
، بالاقتران مع TabBarView
، ولكن مع كليهما سيتعين علينا إجراء تغييرات أكثر مما هو مطلوب مع الدرج ، لذلك سنلتزم بالدرج.
لإظهار Drawer
الذي يحتوي على Menu
التي رأيناها سابقًا على الشاشات الأصغر فقط ، يمكنك كتابة رمز يشبه المقتطف التالي ، والتحقق من العرض باستخدام MediaQuery.of(context)
وتمرير كائن Drawer
إلى Scaffold
فقط إذا كان أقل من بعض قيمة العرض التي نعتقد أنها مناسبة لتطبيقنا:
Scaffold( appBar: AppBar(/* ... \*/), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: /* ... \*/ )
الآن ، دعنا نفكر في body
Scaffold
. كنموذج للمحتوى الرئيسي لتطبيقنا ، سنستخدم GridView
الذي أنشأناه سابقًا ، والذي نحتفظ به في عنصر واجهة مستخدم منفصل يسمى Content
لتجنب الالتباس:
class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
على الشاشات الأكبر حجمًا ، قد يكون الجسم نفسه عبارة عن Row
يعرض عنصرين: Menu
، التي تقتصر على عرض ثابت ، ويملأ Content
بقية الشاشة.
على الشاشات الأصغر ، سيكون body
بالكامل هو Content
.
سنقوم بتغليف كل شيء في SafeArea
وعناصر واجهة مستخدم Center
لأنه في بعض الأحيان ، ينتهي تطبيق Flutter على الويب ، خاصة عند استخدام Row
s و Column
s ، خارج منطقة الشاشة المرئية ، ويتم إصلاح ذلك باستخدام SafeArea
و / أو Center
.
هذا يعني أن body
Scaffold
سيكون كما يلي:
SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) )
إليك كل ذلك معًا:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: HomePage() ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
هذه هي معظم الأشياء التي ستحتاج إليها كمقدمة عامة لواجهة المستخدم سريعة الاستجابة في Flutter. سيعتمد الكثير من تطبيقاته على واجهة المستخدم المحددة لتطبيقك ، ومن الصعب تحديد ما يمكنك فعله بالضبط لجعل تطبيقك مستجيبًا ، ويمكنك اتباع العديد من الأساليب وفقًا لتفضيلاتك. الآن ، على الرغم من ذلك ، دعنا نرى كيف يمكننا تقديم مثال أكثر اكتمالاً في تطبيق سريع الاستجابة ، والتفكير في عناصر التطبيق الشائعة وتدفقات واجهة المستخدم.
وضعه في السياق: جعل التطبيق مستجيبًا
حتى الآن ، لدينا شاشة فقط. دعنا نوسع ذلك إلى تطبيق من شاشتين مع التنقل القائم على URL!
إنشاء صفحة تسجيل دخول متجاوبة
من المحتمل أن يحتوي تطبيقك على صفحة تسجيل دخول. كيف يمكننا أن نجعل ذلك مستجيبًا؟
عادةً ما تكون شاشات تسجيل الدخول على الأجهزة المحمولة متشابهة تمامًا مع بعضها البعض. المساحة المتوفرة ليست كثيرة. عادةً ما يكون مجرد Column
به بعض Padding
حول عناصر واجهة المستخدم الخاصة به ، ويحتوي على TextField
s لكتابة اسم مستخدم وكلمة مرور وزر لتسجيل الدخول. لذا ، فهو معيار جيد (على الرغم من أنه لا يعمل ، حيث يتطلب ذلك ، من بين أشياء أخرى ، يمكن أن تكون صفحة تسجيل الدخول TextEditingController
لكل TextField
) لتطبيق جوال كما يلي:
Scaffold( body: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () {} ) ] ), ), )
يبدو جيدًا على جهاز محمول ، لكن تلك TextField
العريضة جدًا تبدأ في الظهور على جهاز لوحي ، ناهيك عن شاشة أكبر. ومع ذلك ، لا يمكننا تحديد عرض ثابت فقط لأن الهواتف لها أحجام شاشة مختلفة ، ويجب أن نحافظ على درجة من المرونة.
على سبيل المثال ، من خلال التجربة ، قد نجد أن الحد الأقصى للعرض يجب أن يكون 500. حسنًا ، سنقوم بتعيين constraints
Container
على 500 (لقد استخدمت Container
بدلاً من Padding
في المثال السابق لأنني كنت أعرف إلى أين سأذهب مع هذا ) ونحن على ما يرام ، أليس كذلك؟ ليس حقًا ، لأن ذلك قد يتسبب في التصاق أدوات تسجيل الدخول بالجانب الأيسر من الشاشة ، والذي قد يكون أسوأ من تمديد كل شيء. لذلك ، نحن نلتف في عنصر واجهة مستخدم Center
، مثل هذا:
Center( child: Container( constraints: BoxConstraints(maxWidth: 500), padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column(/* ... \*/) ) )
يبدو هذا جيدًا بالفعل ، ولم نضطر حتى إلى استخدام LayoutBuilder
أو MediaQuery.of MediaQuery.of(context).size
. دعنا نذهب خطوة أخرى إلى الأمام لجعل هذا يبدو جيدًا للغاية ، على الرغم من ذلك. سيبدو أفضل ، في رأيي ، إذا كان الجزء الأمامي مفصولًا بطريقة ما عن الخلفية. يمكننا تحقيق ذلك من خلال إعطاء لون الخلفية لما يوجد خلف Container
مع أدوات الإدخال ، والحفاظ على Container
الأمامية بيضاء. لجعلها تبدو أفضل قليلاً ، دعنا نمنع Container
من التمدد إلى أعلى وأسفل الشاشة على الأجهزة الكبيرة ، ونمنحها زوايا مستديرة ، ونمنحها انتقالًا متحركًا لطيفًا بين التخطيطين.
كل ذلك يتطلب الآن LayoutBuilder
خارجية لتعيين لون الخلفية وإضافة حشوة في جميع أنحاء Container
Container
فقط على الجوانب على الشاشات الأكبر حجمًا. أيضًا ، لإجراء تغيير في مقدار المساحة المتروكة متحركًا ، نحتاج فقط إلى تحويل تلك Container
الخارجية إلى AnimatedContainer
، الأمر الذي يتطلب duration
للرسوم المتحركة ، والتي سنقوم بتعيينها على نصف ثانية ، وهي Duration(milliseconds: 500)
في الشفرة.
هذا المثال لصفحة تسجيل دخول سريعة الاستجابة:
class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : EdgeInsets.all(30.0), child: Center( child: Container( padding: EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => HomePage() ) ); } ) ] ), ), ) ); } ) ); }
كما ترى ، لقد قمت أيضًا بتغيير RaisedButton
في الضغط إلى رد onPressed
إلى شاشة تسمى HomePage
الرئيسية (والتي يمكن أن تكون ، على سبيل المثال ، العرض الذي أنشأناه سابقًا باستخدام GridView
وقائمة أو درج). الآن ، على الرغم من ذلك ، جزء التنقل هذا هو ما سنركز عليه.
المسارات المسماة: جعل التنقل في تطبيقك أشبه بتطبيق ويب مناسب
من الشائع أن تمتلك تطبيقات الويب القدرة على تغيير الشاشات بناءً على عنوان URL. على سبيل المثال ، يجب أن يمنحك الانتقال إلى https://appurl/login
شيئًا مختلفًا عن https://appurl/somethingelse
. في الواقع ، يدعم Flutter المسارات المسماة ، والتي لها غرضان:
- في تطبيق الويب ، لديهم بالضبط تلك الميزة التي ذكرتها في الجملة السابقة.
- في أي تطبيق ، يسمحون لك بتحديد مسارات لتطبيقك مسبقًا ومنحهم أسماء ، ثم يكونون قادرين على الانتقال إليها فقط عن طريق تحديد أسمائهم.
للقيام بذلك ، نحتاج إلى تغيير مُنشئ MaterialApp
إلى مُنشئ يبدو كما يلي:
MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } );
وبعد ذلك يمكننا التبديل إلى مسار مختلف باستخدام Navigator.pushNamed(context, routeName)
و Navigator.pushReplacementNamed(context, routeName)
، بدلاً من Navigator.push(context, route)
و Navigator.pushReplacement(context, route)
.
هذا ما تم تطبيقه على التطبيق الافتراضي الذي أنشأناه في بقية هذه المقالة. لا يمكنك حقًا رؤية المسارات المسماة قيد التنفيذ في DartPad ، لذا يجب عليك تجربة ذلك على جهازك الخاص باستخدام flutter run
، أو التحقق من المثال أثناء العمل:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } ); } class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : const EdgeInsets.all(30.0), child: Center( child: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacementNamed( context, "/home" ); } ) ] ), ), ) ); } ) ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ), FlatButton( onPressed: () {Navigator.pushReplacementNamed( context, "/login");}, child: ListTile( leading: Icon(Icons.exit_to_app), title: Text("Log Out"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
فصاعدًا مع مغامرة الرفرفة الخاصة بك
يجب أن يمنحك ذلك فكرة عما يمكنك فعله باستخدام Flutter على الشاشات الأكبر ، وتحديدًا على الويب. إنه إطار جميل وسهل الاستخدام للغاية ، كما أن دعمه الشديد عبر الأنظمة الأساسية يجعله أكثر أهمية للتعلم والبدء في الاستخدام. لذا ، انطلق وابدأ في الوثوق في Flutter لتطبيقات الويب أيضًا!
مزيد من الموارد
- "قذائف سطح المكتب" ، جيثب
الحالة الحالية والمحدثة دائمًا لـ Flutter على سطح المكتب - "دعم سطح المكتب لـ Flutter" ، Flutter
معلومات حول منصات سطح المكتب المدعومة بالكامل - "دعم الويب لـ Flutter" ، Flutter
معلومات حول Flutter للويب - "جميع العينات" ، رفرفة
قائمة منسقة من نماذج Flutter وتطبيقاتها