تطوير الويب وسطح المكتب سريع الاستجابة مع Flutter

نشرت: 2022-03-10
ملخص سريع لقد أحدث Flutter بالفعل تأثيرًا كبيرًا على مشهد تطوير الأجهزة المحمولة. الآن يتم استخدام أجهزة أكبر أيضًا. إليك ما تحتاج إلى معرفته لتكون جاهزًا للقيام بمهمة تطوير تطبيقات الويب وسطح المكتب باستخدام إطار العمل الرائع عبر الأنظمة الأساسية هذا.

هذا البرنامج التعليمي ليس مقدمة إلى Flutter نفسها. هناك الكثير من المقالات ومقاطع الفيديو والعديد من الكتب المتاحة عبر الإنترنت مع مقدمات بسيطة ستساعدك على تعلم أساسيات Flutter. بدلاً من ذلك ، سنغطي الهدفين التاليين:

  1. الحالة الحالية لتطوير Flutter غير المحمول وكيف يمكنك تشغيل كود Flutter في المتصفح ، على كمبيوتر مكتبي أو كمبيوتر محمول ؛
  2. كيفية إنشاء تطبيقات سريعة الاستجابة باستخدام 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 للويب.

إنشاء تخطيطات مستجيبة في رفرفة

بسبب ما وصفته أعلاه ومن أجل التبسيط ، سأفترض بالنسبة لبقية هذا المنشور أن النظام الأساسي المستهدف هو الويب ، لكن المفاهيم الأساسية تنطبق على تطوير سطح المكتب أيضًا.

دعم الويب له فوائد ومسؤوليات. قد يبدو أن تكون مجبرًا إلى حد كبير على دعم أحجام الشاشة المختلفة بمثابة عيب ، ولكن ضع في اعتبارك أن تشغيل التطبيق في متصفحات الويب يمكّنك من رؤية كيف سيبدو تطبيقك بسهولة على الشاشات ذات الأحجام المختلفة ونسب العرض إلى الارتفاع ، دون الحاجة إلى التشغيل بشكل منفصل محاكيات الأجهزة المحمولة.

الآن ، لنتحدث عن الكود. كيف يمكنك جعل تطبيقك مستجيبًا؟

هناك نوعان من المنظورات التي يتم من خلالها إجراء هذا التحليل:

  1. "ما الأدوات المصغّرة التي أستخدمها أو التي يمكنني استخدامها والتي يمكنها أو يجب أن تتكيف مع الشاشات ذات الأحجام المختلفة؟"
  2. "كيف يمكنني الحصول على معلومات حول حجم الشاشة ، وكيف يمكنني استخدامها عند كتابة رمز واجهة المستخدم؟"

سنجيب على السؤال الأول لاحقًا. دعنا نتحدث أولاً عن الأخير ، لأنه يمكن التعامل معه بسهولة شديدة وهو في قلب القضية. هناك طريقتان للقيام بذلك:

  1. تتمثل إحدى الطرق في الحصول على المعلومات من MediaQueryData الخاصة بجذر MediaQuery InheritedWidget ، والذي يجب أن يكون موجودًا في شجرة عناصر واجهة المستخدم حتى يعمل تطبيق Flutter (إنه جزء من MaterialApp/WidgetsApp/CupertinoApp ) ، والتي يمكنك الحصول عليها ، تمامًا مثل أي InheritedWidget أخرى ، مع MediaQuery.of(context) ، والتي لها خاصية size ، وهي من النوع Size ، وبالتالي لها width height من النوع double .
  2. الطريقة الأخرى هي استخدام 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 المسارات المسماة ، والتي لها غرضان:

  1. في تطبيق الويب ، لديهم بالضبط تلك الميزة التي ذكرتها في الجملة السابقة.
  2. في أي تطبيق ، يسمحون لك بتحديد مسارات لتطبيقك مسبقًا ومنحهم أسماء ، ثم يكونون قادرين على الانتقال إليها فقط عن طريق تحديد أسمائهم.

للقيام بذلك ، نحتاج إلى تغيير مُنشئ 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 وتطبيقاتها