תחי ישראל - אין לנו ארץ אחרת

תחי ישראל -אין לנו ארץ אחרת

flutter - קלאסים ווידג'טים

מחבר:
בתאריך:

במדריך זה נלמד כיצד להשתמש בקלאסים וווידג'טים של flutter לפיתוח אפליקציות. ממשק האפליקציה כולל מספר כפתורים שלחיצה עליהם גורמת להצגת מידע בהתאם לכפתור עליו לוחץ המשתמש:

the look and feel of the flutter app

להורדת תיקייה מכווצת עם קבצי הפרויקט אותו נפתח במדריך

תחילת עבודה על פרויקט Flutter

ניצור פרויקט flutter חדש על ידי הקלדת בטרמינל של הפקודה ליצירת פרויקט חדש ואת שם הפרויקט. עבור הפרויקט בחרתי את השם "cars_basic_app":

$ flutter create cars_basic _app

נפתח את תיקיית הפרויקט באמצעות vscode, ונחליף את תוכן הקובץ main.dart בקוד בסיסי של אפליקציה:

lib/main.dart

import 'package:flutter/material.dart';
 
void main() {
 runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
 const MyApp({super.key});
 
 // This widget is the root of your application.
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Cars basic app',
   );
 }
}
  • הפונקציה main רצה ראשונה באפליקציית flutter ומריצה את runApp שקוראת לקלאסים אשר מציגים מידע למשתמש הודות להיותם מצוידים במתודה build.
  • הפונקציה build מקבלת כפרמטר BuildContext אשר מכיל את המידע אודות מיקומו של הווידג'ט בעץ הווידג'טים widget tree
  • בתוך המתודה build ישנו ווידג'ט MaterialApp שנותן לדף מראה של Material (ספרייה של גוגל שמקנה לאפליקציות אנדרואיד את המראה האופייני). בתוך הווידג'ט נשלב מיד את הווידג'טים הנוספים שיעשו את הממשק למשתמש.

הממשק למשתמש UI של האפליקציה מורכב מווידג'טים. שני סוגים עיקריים הם stateful לעומת stateless. לווידג'טים המיועדים להשתנות במהלך חיי האפליקציה נשתמש בסוג stateful.

 

ווידג'ט ראשון משלנו custom widget

ניצור קלאס מסוג StatefulWidget ששמו "MyCars" שתפקידו להציג את המידע על הפריט הנבחר ואת רשימת/טור הכפתורים. נתחיל להקליד stful כדי ש-vscode יציע לנו אפשרויות השלמה אוטומטית מתוכם נבחר באפשרות הראשונה Flutter Stateful Widget:

choose flutter stateful widget from the drop in the IDE

נלחץ על האפשרות הראשונה כדי לקבל שלד של StatefulWidget:

skeleton of stateful Flutter widget in the IDE

נמלא את שם הווידג'ט כדי שיהיה "MyCars":

class MyCars extends StatefulWidget {
 const MyCars({super.key});
 
 @override
 State createState() => _MyCarsState();
}
 
class _MyCarsState extends State {
 @override
 Widget build(BuildContext context) {
   return Container();
 }
}
  • תפקיד הווידג'טים להציג מידע למשתמש באמצעות המתודה build.

לווידג'ט נקצה קובץ משלו. זו לא חובה להקצות לכל ווידג'ט קובץ נפרד אבל בשביל קוד קריא כדאי לשקול לחלק את האפליקציה למספר קבצים. בפרט אם הווידג'ט מכיל שורות קוד רבות. יתרון חשוב של חלוקה לווידג'טים הוא ש-flutter יכול לעדכן רק חלק מהמסך מה שחוסך במשאבים ומשפר את ביצועי האפליקציה. את הווידג'טים נמקם בתוך תיקייה נפרדת widgets שאותה נוסיף לתוך התיקייה lib. בתוך התיקייה החדשה נשים את הווידג'ט MyCars בתוך קובץ שקראתי לו list_cars_widget.dart:

lib/widgets/list_cars_widget.dart

import 'package:flutter/material.dart';
 
class MyCars extends StatefulWidget {
 const MyCars({super.key});
 
 @override
 State createState() => _MyCarsState();
}
 
class _MyCarsState extends State {
 @override
 Widget build(BuildContext context) {
   return Container();
 }
}

נייבא את הקלאס החדש לתוך הקובץ הראשי של האפליקציה main.dart:

lib/main.dart

import 'package:flutter/material.dart';
import './widgets/list_cars_widget.dart';
  • מקובל למקם קודם את הספריות של flutter ורק אחר כך את הקלאסים שאנחנו כתבנו.

כדי שהאפליקציה תציג את הווידג'ט החדש נגדיר שדף הבית של האפליקציה יקח את המראה שלו מקלאס הווידג'ט על ידי מיפוי התכונה home של MaterialApp על הפונקציה MyCars:

lib/main.dart

class MyApp extends StatelessWidget {
 const MyApp({super.key});
 
 // This widget is the root of your application.
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Mוultiple classes app',
     home: const MyCars(),
   );
 }
}

מיד נוסיף מידע על מכוניות אותן תציג האפליקציה. בשביל להציג מידע על מכוניות נשתמש באב טיפוס, class עם שדות הדרושים לתיאור מכונית. הקלאס Car יגדיר את התכונות הדרושות מאובייקט מסוג זה באפליקציה.

 

הוספת קלאס מודל

ניצור תיקייה חדשה בשם models שתכיל את הקלאסים ונשים אותה בתוך התיקייה libs. לתוך התיקייה נוסיף קובץ car.dart בתוכו נכתוב את הקלאס:

libs/models/car.dart

class Car {
 final int id;
 final String name;
 final double price;
 
 Car({
   required this.id,
   required this.name,
   required this.price,
 });
}
  • השדות הדרושים למכונית הם מזהה מספרי id, מחרוזת name ומספר עשרוני בשביל המחיר.

בקובץ הווידג'ט נכלול את הקישור לקובץ הקלאס בתור תלות נוספת:

lib/widgets/list_cars_widget.dart

import 'package:flutter/material.dart';
import '../models/car.dart';

נוסיף רשימה של מכוניות בה כל פריט יהיה שייך לסוג Car:

lib/widgets/list_cars_widget.dart

class _MyCarsState extends State {
 List cars = [
   Car(id: 0, name: "Tesla", price: 320000),
   Car(id: 1, name: "BMW", price: 489999),
   Car(id: 2, name: "Mercedes", price: 599999),
 ];
  // …
 }

 

בחירת המידע להצגה ומילת המפתח late

נבחר להציג את פרטי המכונית הראשונה כברירת מחדל. נגדיר איזו מכונית להציג באמצעות משתנה selectedCar אותו נגדיר late:

late Car selectedCar;
  • המשתנה מוגדר late היות והוא יקבל ערך בזמן שהתוכנית רצה run time להבדיל מזמן הקומפילציה, תהליך הפיכת הקוד שכתב המתכנת לשפת מכונה, compilation time.
  • משתנה מסוג late חייב לקבל ערך. 

בשביל להציב ערך למשתנה נגדיר בתוך המתודה build ערך ברירת מחדל (0 כדי להציג את הפריט הראשון ברשימה):

lib/widgets/list_cars_widget.dart

class _MyCarsState extends State {
 List cars = [
   Car(id: 0, name: "Tesla", price: 320000),
   Car(id: 1, name: "BMW", price: 489999),
   Car(id: 2, name: "Mercedes", price: 599999),
 ];
 
 late Car selectedCar;
 var selectedCarIndex = 0;
 
 @override
 Widget build(BuildContext context) {
   selectedCar = cars[selectedCarIndex];
 
   
   return Container();
 }
}

 

ווידג'טים לבניית ממשק המשתמש

Scaffold הוא קלאס של Material שיוצר את מראה הדף הבסיסי. נגדיר שהמתודה-build תחזיר ווידג'ט Scaffold:

lib/widgets/list_cars_widget.dart

class _MyCarsState extends State {
 List cars = [
   Car(id: 0, name: "Tesla", price: 320000),
   Car(id: 1, name: "BMW", price: 489999),
   Car(id: 2, name: "Mercedes", price: 599999),
 ];
 
 late Car selectedCar;
 var selectedCarIndex = 0;
 
 
 @override
 Widget build(BuildContext context) {
   selectedCar = cars[selectedCarIndex];
 
   return Scaffold();
 }
}

נוסיף שני חלקים ל-Scaffold: appBar ו-body.

נוסיף AppBar בשביל הפאנל העליון:

return Scaffold(
     appBar: AppBar(
       backgroundColor: Color.fromARGB(255, 33, 150, 243),
       title: Text("My Cars"),
       centerTitle: true,
     ),
),

נוסיף ריפוד בתוך גוף הווידג'ט Scaffold כדי להרחיק מהשוליים:

return Scaffold(
     body: Padding(
       padding: const EdgeInsets.fromLTRB(30.0, 40.0, 30.0, 0.0),
     ),
   );

בתוך הווידג'ט Scaffold שמבנה את מתווה הדף layout צריך להיות אלמנט המציג את המכונית הנבחרת, ומתחתיו אחד מתחת לשני כפתורים המאפשרים בחירה. האלמנט הבסיסי ביותר להצגת אלמנטים אחד מתחת לשני במבנה של עמודה הוא Column:

return Scaffold(
     body: Padding(
       padding: const EdgeInsets.fromLTRB(30.0, 40.0, 30.0, 0.0),
       child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [],
       ),
     ),
   );
  • הווידג'ט Column הוא אחד החשובים של flutter. נשתמש בו כדי למקם ווידג'טים אחד מעל לשני.
  • במידה ונרצה למקם ווידג'טים אחד לצד השני, לרוחב המסך, נשתמש בווידג'ט Row.
  • התכונה crossAxisAlignment מגדירה כיצד האלמנטים בתוך הווידג'ט אמורים להתיישר. הגדרתי ערך start לתכונה כדי שהאלמנטים יתיישרו לשמאל.

תיעוד כל קטלוג הווידג'טים של Flutter, ויש רבים, קיים ב-Widget catalog

הווידג'ט Column כולל רשימת ווידג'טים ילדים children. בתור הילד הראשון, האלמנט הראשון ברשימה, נגדיר שיציג את שם המכונית הנבחרת בתוך הווידג'ט Text:

return Scaffold(
     body: Padding(
       padding: const EdgeInsets.fromLTRB(30.0, 40.0, 30.0, 0.0),
       child: Column(
         children: [
           Text(
             selectedCar.name,
             textAlign: TextAlign.center,
             style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24, color: Colors.black),
           ),
         ],
       ),
     ),
   );
  • אחת התכונות של הווידג'ט Text היא TextAlign שאחראית על יישור השורה. במקרה זה, השורה מתיישרת למרכז.
  • התכונה style מאפשרת לעצב את הטקסט. באמצעותה הוספנו עובי fontWeight והגדרנו את גודל הטקסט fontStyle ואת הצבע color.

כדי לראות את כל תכונות הווידג'ט נעמיד את הסמן על שם הווידג'ט כדי לראות רשימה:

Text Flutter widget has multiple properties that we can use to develop our flutter app

ווידג'ט Text ואינטרפולציה

אנחנו יכולים להציג מידע עשיר הכולל כמה משתנים בשילוב טקסט בתוך הווידג'ט Text על ידי שימוש באינטרפולציה. לשם כך, נוסיף לפני שמות המשתנים $, את הביטויים נקיף בסוגריים מסולסלים. לדוגמה, נציג את שם המכונית ואת המחיר בתוך אותה שורת טקסט:

Text(
  '${selectedCar.name} ${selectedCar.price} NIS',
   textAlign: TextAlign.center,
),

נריץ את האפליקציה על ידי לחיצה על Run בתפריט הראשי של ה-IDE ומתוך התפריט הנפתח נבחר Run without debugging:

Run drop menu in the VScode IDE

כדי להריץ את האימולטור שמציג את האפליקציה:

first look at the bare bone of the app

נוסיף ווידג'ט נוסף לרשימה - כפתור שיציג את שם המכונית הראשונה ברשימה:

lib/widgets/list_cars_widget.dart

return Scaffold(
     body: Padding(
       padding: const EdgeInsets.fromLTRB(30.0, 40.0, 30.0, 0.0),
       child: Column(
         children: [
           Text(
             '${selectedCar.name} ${selectedCar.price} NIS',
             textAlign: TextAlign.center,
           ),
           ElevatedButton(
             onPressed: () => null,
             child: Text(cars[0].name),
           ),
         ],
       ),
     ),
   );
  • הכפתור מסוג ElevatedButton מצוייד בתכונה onPressed שקוראת לפונקציה callback בתגובה להקלקה על הכפתור ותכונה child שהצבנו לתוכה Text עם שם המכונית.

כך זה נראה באימולטור:

flutter app with a button as seen in the emulator

 

עבודה עם map למיפוי רשימות על ווידג'טים

אפשר להוסיף כפתורים כמספר הפריטים ברשימה. רק שזו משימה מייגעת בפרט אם הרשימה ארוכה, וזו גם גישה לא גמישה שתקשה עלינו להכניס שינויים במידת הצורך בעתיד. דרך עדיפה היא להשתמש בפונקציה map שהיא מתודה אותה ניתן להפעיל על רשימות Dart שעוברת על כל אחד מפריטי הרשימה בלולאה ומחזירה את המידע הנדרש לצורך בניית הווידג'ט:

lib/widgets/list_cars_widget.dart

cars.map((item) {
                 return ElevatedButton(
                   onPressed: () => null,
                   child: Text(item.name),
                 );
               }).toList()),

נפעיל את המתודה map על רשימת המכוניות כדי ליצור מספר כפתורים התואם את אורך הרשימה:

return Scaffold(
     body: Padding(
       padding: const EdgeInsets.fromLTRB(30.0, 40.0, 30.0, 0.0),
       child: Column(
         children: [
           Text(
             '${selectedCar.name} ${selectedCar.price} NIS',
             textAlign: TextAlign.center,
           ),
           Column(
             crossAxisAlignment: CrossAxisAlignment.start,
             children: cars.map((item) {
               return ElevatedButton(
                 onPressed: () => null,
                 child: Text(item.name),
               );
             }).toList(),
           ),
         ],
       ),
     ),
   );
  • הוספנו ווידג'ט Column בתוכו פריטי הרשימה cars ממופים לווידג'ט כפתור מסוג ElevatedButton באמצעות המתודה map.

כך נראה מסך האימולטור:

app with multiple button widgets

 

הקצאת קובץ סקריפט נפרד לווידג'ט

הכפתור הוא ווידג'ט פשוט ורזה בשלב זה. בהמשך נוסיף לו תכונות נוספות מה שיקשה על קריאת הקוד. לכן כדאי להקצות לווידג'ט קובץ נפרד.

נפתח קובץ חדש: lib/widgets/btn_widget.dart

בתוכו נוסיף קלאס stateless ווידג'ט ששמו BtnWidget. מתחילים להקליד stls ב-IDE ובוחרים באפשרות Flutter Stateless Widget מהתפריט שנפתח מה שיוצר שלד של Stateless widget:

lib/widgets/btn_widget.dart

import 'package:flutter/material.dart';
 
class BtnWidget extends StatelessWidget {
 
 const BtnWidget();
 
 @override
 Widget build(BuildContext context) {
     return Container();
 }
}
  • בחרתי בווידג'ט stateless (להבדיל מ-stateful) היות ומרגע היווסדו המידע בווידג'ט יישאר קבוע.

נוסיף לווידג'ט כפתור מסוג ElevatedButton:

lib/widgets/btn_widget.dart

import 'package:flutter/material.dart';
 
class BtnWidget extends StatelessWidget {
 const BtnWidget();
 
 @override
 Widget build(BuildContext context) {
   return ElevatedButton(
     onPressed: () => null,
     child: Text('Text button'),
   );
 }
}

כדי שהווידג'ט יוצג בממשק למשתמש נייבא אותו לקובץ list_cars_widget.dart:

lib/widgets/list_cars_widget.dart

import 'package:flutter/material.dart';
import '../models/car.dart';
import './btn_widget.dart';

בנוסף, נשלב את הווידג'ט שזה עתה הוספנו במקום הכפתור הקיים:

lib/widgets/list_cars_widget.dart

Column(
             crossAxisAlignment: CrossAxisAlignment.start,
             children: cars.map((item) {
               return BtnWidget();
             }).toList(),
           ),

התוצאה באימולטור:

the app once the widget button was separated into a different file

 

העברת מידע בין ווידג'ט הכפתור לווידג'ט ההורה אליו הוא משתייך

כדי שווידג'ט הכפתור יציג את שם המכונית אנחנו צריכים להעביר את פרטי המכונית לווידג'ט. לשם כך, נעביר את פרטי האובייקט מסוג Car לווידג'ט הכפתור, ואח"כ נקלוט את המידע בתוך ווידג'ט הכפתור.

נעביר את המידע לווידג'ט הכפתור על ידי מיפוי באמצעות המתודה map:

lib/widgets/btn_widget.dart

cars.map((item) {
               return BtnWidget(item);
             }).toList(),

בצד של ווידג'ט הכפתור נוסיף קוד שיקלוט את האובייקט מסוג Car על ידי כך שנערוך 4 שינויים בקוד הווידג'ט:

lib/widgets/btn_widget.dart

import 'package:flutter/material.dart';
// 1. Add Car class as dependency
import '../models/car.dart';
 
class BtnWidget extends StatelessWidget {
 // 2. Add a final car variable.
 final Car car;
 // 3. Fill the car variable when constructing the widget.
 BtnWidget(this.car);
 
 @override
 Widget build(BuildContext context) {
   // 4. Make the car's name the text on the button 
   return ElevatedButton(
     onPressed: () => null,
     child: Text(car.name),
   );
 }
}
  1. נוסיף את הסקריפט car.dart בתור תלות.
  2. נגדיר את המשתנה car בתור final בגלל שהערך משתנה מכפתור לכפתור אבל עבור כל כפתור אינדיבידואלי הערך נשאר קבוע. בפעם הראשונה שהתוכנה רצה, הערך מוצב ואחר כך נשאר קבוע.
  3. בזמן שנוצר הווידג'ט flutter מעביר לו לתוך הקונסטרקטור את האובייקט המסוים מסוג Car אותו הוא צריך להציג.
  4. נשלב את שם האובייקט בתור הכיתוב על הכפתור.

custom widget - the data depends on the context

כדי לשפר את מראה הכפתורים נוסיף להם רוחב ונרווח סביבם. נעטוף את -ElevatedButton בווידג'ט מסוג Padding. בשביל לעטוף בווידג'ט ב-IDE נקליק עם העכבר על שם הווידג'ט (ElevatedButton) נלחץ על אייקון המנורה שיופיע משמאל ונבחר מהתפריט Wrap with Padding. התוצאה:

lib/widgets/btn_widget.dart

@override
 Widget build(BuildContext context) {
   return Padding(
     padding: const EdgeInsets.all(8.0),
     child: ElevatedButton(
       onPressed: () => null,
       child: Text(car.name),
     ),
   );
 }

padding around each button widget

בשביל שהכפתורים יתפסו את כל הרוחב נעטוף את הווידג'ט כפתור בווידג'ט נוסף, Container שנגדיר לו רוחב:

lib/widgets/btn_widget.dart

@override
 Widget build(BuildContext context) {
   return Container(
     width: double.infinity,
     child: Padding(
       padding: const EdgeInsets.all(8.0),
       child: ElevatedButton(
         onPressed: () => null,
         child: Text(car.name),
       ),
     ),
   );
 }
  • Container הוא אחד הווידג'טים העיקריים של flutter. הוא מאפשר מגוון רחב של אפשרויות עיצוב.
  • הגדרנו רוחב double.infinity כדי למלא את מלוא רוחב המסך.
  • הווידג'ט כפתור מקבל את מימדיו מהווידג'ט Container שעוטף אותו.

the button takes the full width of the screen

כדי להקנות צבע שונה לכפתור של הפריט הנבחר נעביר את המידע אודות הפריט הנבחר לווידג'ט.

lib/widgets/list_cars_widget.dart

return BtnWidget(item, selectedCarIndex);

בתוך ווידג'ט הכפתור נגדיר משתנה selectedIndex אליו נציב את המידע אודות אינדקס הרשימה הנבחר:

lib/widgets/btn_widget.dart

class BtnWidget extends StatelessWidget {
 final Car car;
 final int selectedIndex;
 
 BtnWidget(this.car, this.selectedIndex);
  //..

בתוך המתודה build נגדיר צבע לכפתור באמצעות:

lib/widgets/btn_widget.dart

@override
 Widget build(BuildContext context) {
   return Container(
     width: double.infinity,
     child: Padding(
       padding: const EdgeInsets.all(8.0),
       child: ElevatedButton(
         onPressed: () => null,
         child: Text(car.name),
         style: ButtonStyle(
           backgroundColor: MaterialStateProperty.all(Colors.blue),
         ),
       ),
     ),
   );
 }
  • התכונה של ElevatedButton בה השתמשנו לעיצוב הכפתור היא style המקבלת ערך ButtonStyle עם backgroundColor.

 

תחביר חלופי לכתיבת תנאי באמצעות ternary operator וצביעת הכפתור הפעיל בצבע שונה

אנחנו רוצים שהצבע ברירת המחדל יהיה אפור ואם הכפתור שייך לפריט הנבחר אז שיהיה כחול. לשם כך, נשתמש בתנאי:

lib/widgets/btn_widget.dart

@override
 Widget build(BuildContext context) {
   return Container(
     width: double.infinity,
     child: Padding(
       padding: const EdgeInsets.all(8.0),
       child: ElevatedButton(
         onPressed: () => null,
         child: Text(car.name),
         style: ButtonStyle(
           backgroundColor: car.id == selectedIndex
               ? MaterialStateProperty.all(Colors.blue)
               : MaterialStateProperty.all(Colors.grey),
         ),
       ),
     ),
   );
 }
  • בשביל התנאי השתמשנו ב-ternary operator שהוא תחביר קומפקטי לכתיבת תנאים. במקרה זה, התנאי אומר שאם הכפתור נבחר אז צבע הרקע צריך להיות כחול. אחרת אפור.

באימולטור, הכפתור העליון ביותר שמייצג את האלמנט הראשון ברשימה המוגדר כנבחר כברירת מחדל יקבל עכשיו צבע שונה.

different background color to the chosen item

 

העברת בחירת המשתמש מתוך ווידג'ט הכפתור לווידג'ט אליו הוא משתייך באמצעות העברה של פונקציה בקונסטרקטור

כדי לשנות את המידע שמציגה האפליקציה על פי הכפתור עליו מקליק המשתמש, אנחנו צריכים להעביר את בחירת המשתמש מתוך הווידג'ט כפתור אל הווידג'ט רשימה שנמצא מעליו. המתודה setSelected שנכתוב בתוך הווידג'ט רשימה מאפשר לשנות את הערך:

lib/widgets/list_cars_widget.dart

var selectedCarIndex = 0;
 
 void setSelected(int id) {
   setState(() {
     selectedCarIndex = id;
   });
 }
  • המתודה setSelected מקבלת בתור פרמטר את ה-id ומציבה אותו בתור ערך של selectedCarIndex, האינדקס הנבחר של הרשימה.
  • כדי לשנות את המצב בתוך הקלאס אנחנו משתמשים ב- setState, מתודה של StatefuleWidget שתפקידה לשנות את המידע בתוך הקלאס ואז להפעיל את build כדי שיבנה מחדש את התצוגה.

המשתמש לוחץ על הכפתור, והשאלה היא כיצד להעביר את המידע על בחירת המשתמש מתוך הווידג'ט כפתור אל הרשימה שבתוכה נמצאת המתודה setSelected. הפתרון שלנו יהיה להעביר את המתודה setSelected מההורה לכפתור על ידי כך שנוסיף את המתודה בתור פרמטר שלישי אותו מקבל BtnWidget:

lib/widgets/list_cars_widget.dart

return BtnWidget(item, selectedCarIndex, setSelected);
  • אנחנו מעבירים פונקציה בתור פרמטר לפונקציה אחרת.

נגדיר בתוך הווידג'ט כפתור את הפונקציה שמתקבלת דרך הקונסטרקטור:

lib/widgets/btn_widget.dart

class BtnWidget extends StatelessWidget {
 final Car car;
 final int selectedIndex;
 final Function setSelected;
 
 BtnWidget(this.car, this.selectedIndex, this.setSelected);
 // …
  • סוג המשתנה הוא Function.

בתוך המתודה build נגדיר שלחיצה על הכפתור תעביר למתודה setSelected את ה-id:

lib/widgets/btn_widget.dart

ElevatedButton(
         onPressed: () => setSelected(car.id),
         child: Text(car.name),
         style: ButtonStyle(
           //…         
          ),
       ),

עכשיו המשתמש יכול לשנות את המידע המוצג על ידי לחיצה על הכפתורים באפליקציה.

the user can change the chosen item by pressing the buttons

 

פורמט המספר ושימוש בחבילה intl של Dart

נשפר את מראה מחיר המכונית בתצוגה על ידי הוספת פסיקים למספר. לשם כך נייבא חבילה intl שמשמשת לפירמוט ותרגום. לדוגמה, הצגת תאריכים בפורמטים שונים (אירופאי לעומת אמריקאי). במקרה זה, נשתמש בחבילה כדי לפרמט את תצוגת המחיר.

החבילה שייכת ל-dart. ניתן למצוא את כל החבילות באתר https://pub.dev.

לצורך התקנת החבילה נפתח את הטרמינל של ה-IDE ונריץ את הפקודה להתקנה כפי שהיא מופיעה בדף החבילה intl :

$ dart pub add intl

דף החבילה כולל מידע חשוב אודות האפליקציה. כולל כיצד להתקין, מה מידת הפופולריות, תיעוד מקרי שימוש נפוצים וכיצד ניתן להשתמש בחבילה. החבילה intl היא פופולרית ביותר בגלל שהיא מאפשרת, בין השאר, לתרגם תכנים, ליישר את הטקסט (ימין לשמאל והפוך) ולפרמט תאריכים. אנחנו נשתמש בה כדי לפרמט מספרים.

אחרי ההתקנה החבילה תופיע כתלות בקובץ pubspec.yaml. בתוך הקובץ מרוכזות הגדרות האפליקציה. כולל, החבילות בהם האפליקציה תלויה לפעולתה.

נייבא את החבילה לתוך הסקריפט הרלוונטי:

lib/widgets/list_cars_widget.dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/car.dart';
import './btn_widget.dart';
  • קודם החבילות הכלליות, אח"כ החבילות שהותקנו, והכי למטה החבילות שלנו.

נגדיר פורמטר, ונעביר לו את מבנה המספר לו אנו מצפים:

var formatter = NumberFormat('###,###,###');

נשתמש בזה כדי לפרמט את המספר בתצוגה:

${formatter.format(selectedCar.price)

התוצאה:

formatted price with the help of the Dart intl package

 

כיצד לאפשר גלילה במקרה של רשימות ארוכות הגולשות מחוץ למסך

עדיין יש בעיה. אם נוסיף פריטים לרשימה ונשנה את האוריינטציה של המכשיר לאופקית נקבל בעיה שאי אפשר לראות את האלמנטים שבתחתית הרשימה כי אי אפשר לגלול אליהם:

black-yellow mark to indicate the content overflow

  • סימון שחור-צהוב אומר לנו שתוכן חורג מחוץ למסך ואי אפשר לגלול אליו.

כדי לפתור את הבעיה נשתמש בשילוב של שלושה אלמנטים מקוננים: Expanded בתוכו SizedBox ובתוכו ListView שיחליפו את הווידג'ט Column.

lib/widgets/list_cars_widget.dart

Expanded(
             child: SizedBox(
               height: 200.0,
               child: ListView.builder(
                 itemCount: cars.length,
                 shrinkWrap: true,
                 itemBuilder: (BuildContext context, int index) {
                   return BtnWidget(
                       cars[index], selectedCarIndex, setSelected);
                 },
               ),
             ),
           ),
  • Expanded הוא ווידג'ט שממלא את מלוא השטח של ההורה שעוטף אותו.
  • SizedBox - באמצעותו ניתן לרשימה גובה. הבעיה ש-ListView לא יודע כמה גובה לקחת. לכן, צריך להגביל אותו על ידי ווידג'ט עוטף שיש לו תכונה של גובה.
  • ListView הוא ווידג'ט חשוב של flutter שמאפשר להפוך רשימת פריטים לווידג'טים שניתן לרנדר למסך. שימוש ב-builder הופך את רשימת הילדים לניתנת לגלילה. בנוסף, flutter נמנע מרינדור הווידג'טים החורגים מחוץ לאלמנט העוטף מה שמקנה יתרון מבחינת צריכת משאבים של תצוגות ארוכות.

 

ווידג'ט Row למיקום אלמנטים אחד לצד השני

ראינו שניתן להשתמש בווידג'ט Column כדי למקם ווידג'טים אחד מעל לשני. באופן דומה, נשתמש בווידג'ט Row לצורך מיקום אלמנטים אחד לצד השני בשורה אחת. במקרה שלנו, נשתמש בזה כדי להפריד את החלק שמציג את פרטי המכונית הנבחרת לשני תאים: טקסט ותמונה.

lib/widgets/list_cars_widget.dart

Row(
             mainAxisAlignment: MainAxisAlignment.spaceEvenly,
             crossAxisAlignment: CrossAxisAlignment.center,
             children: [
               Expanded(
                 flex: 1,
                 child: Image(image: AssetImage('images/myCar.png')),
               ),
               Expanded(
                 flex: 2,
                 child: Text(
                   '${selectedCar.name} ${formatter.format(selectedCar.price)} NIS',
                   textAlign: TextAlign.center,
                 ),
               ),
             ],
           ),
  • בתוך ה-Row נשתמש בווידג'טים Expanded שניתן להם תכונה של flex כדי שהשורה תתפוס את כל הרוחב.
  • רוחב השורה מתחלק על פי היחס בין ערכי ה-flex של האלמנטים מסוג Expanded. במקרה זה, התא הראשון תופס שליש שורה והשני שני שלישים בגלל שהיחס הוא 1:2.

 

שילוב תמונות בתור נכסים

בשביל לשלב תמונות אנחנו יכולים להשתמש בתמונות שנמצאות בתיקיות בתוך האפליקציה. לשם כך, הוספתי תיקייה images בתוך שורש האפליקצייה, מחוץ לתיקייה lib, בתוכה שמתי את התמונה.

כדי להתיר לאפליקציה להשתמש בתמונות בתיקייה נגדיר אותה בקובץ ההגדרות pubspec.yaml בתור סוג של asset:

setting the assets in the pubspec.yaml file

את התמונה נציג באמצעות ווידג'ט מסוג Image:

lib/widgets/list_cars_widget.dart

Image(image: AssetImage('images/myCar.png'))

אותו נשלב בתוך תא ה-Expanded הראשון של ה-Row:

lib/widgets/list_cars_widget.dart

Row(
             mainAxisAlignment: MainAxisAlignment.spaceEvenly,
             crossAxisAlignment: CrossAxisAlignment.center,
             children: [
               Expanded(
                 flex: 1,
                 child: Image(image: AssetImage('images/myCar.png')),
               ),
               Expanded(
                 flex: 2,
                 child: Text(
                   '${selectedCar.name} ${formatter.format(selectedCar.price)} NIS',
                   textAlign: TextAlign.center,
                 ),
               ),
             ],
           ),

 

להורדת תיקייה מכווצת עם קבצי הפרויקט אותם פיתחנו במדריך

מדריכים נוספים שעשויים לעניין אותך

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

המדריכים באתר עוסקים בנושאי תכנות ופיתוח אישי. הקוד שמוצג משמש להדגמה ולצרכי לימוד. התוכן והקוד המוצגים באתר נבדקו בקפידה ונמצאו תקינים. אבל ייתכן ששימוש במערכות שונות, דוגמת דפדפן או מערכת הפעלה שונה ולאור השינויים הטכנולוגיים התכופים בעולם שבו אנו חיים יגרום לתוצאות שונות מהמצופה. בכל מקרה, אין בעל האתר נושא באחריות לכל שיבוש או שימוש לא אחראי בתכנים הלימודיים באתר.

למרות האמור לעיל, ומתוך רצון טוב, אם נתקלת בקשיים ביישום הקוד באתר מפאת מה שנראה לך כשגיאה או כחוסר עקביות נא להשאיר תגובה עם פירוט הבעיה באזור התגובות בתחתית המדריכים. זה יכול לעזור למשתמשים אחרים שנתקלו באותה בעיה ואם אני רואה שהבעיה עקרונית אני עשוי לערוך התאמה במדריך או להסיר אותו כדי להימנע מהטעיית הציבור.

שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.

המשתמש באתר צריך להיות מודע לכך שאם וכאשר הוא מפתח קוד בשביל פרויקט הוא חייב לשים לב ולהשתמש בסביבת הפיתוח המתאימה ביותר, הבטוחה ביותר, היעילה ביותר וכמובן שהוא צריך לבדוק את הקוד בהיבטים של יעילות ואבטחה. מי אמר שלהיות מפתח זו עבודה קלה ?

השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.

הוסף תגובה חדשה

 

 

ענה על השאלה הפשוטה הבאה כתנאי להוספת תגובה:

איך אומרים בעברית אינטרנט?