MDC-102 Flutter: struktura i układ materiału

1. Wprowadzenie

logo_components_color_2x_web_96dp.png

Komponenty Material (MDC) pomagają deweloperom wdrażać Material Design. MDC zostało stworzone przez zespół inżynierów i projektantów UX w Google. Zawiera dziesiątki atrakcyjnych i funkcjonalnych komponentów interfejsu, które są dostępne na platformach Android, iOS, internetowej i Flutter.material.io/develop

W kursie MDC-101 do utworzenia strony logowania użyto 2 komponentów Material: pól tekstowych i przycisków z efektem rozchodzenia się fali atramentu. Teraz rozbudujmy tę podstawę, dodając nawigację, strukturę i dane.

Co utworzysz

W tym laboratorium kodowania utworzysz ekran główny aplikacji Shrine, czyli aplikacji do handlu elektronicznego, w której sprzedawane są ubrania i artykuły gospodarstwa domowego. Będzie ona zawierać:

  • górny pasek aplikacji,
  • Lista produktów w formie siatki

Android

iOS

aplikacja e-commerce z górnym paskiem aplikacji i siatką pełną produktów;

aplikacja e-commerce z górnym paskiem aplikacji i siatką pełną produktów;

Komponenty i podsystemy Material Flutter w tym laboratorium

  • Górny pasek aplikacji
  • Siatki
  • karty;

Jak oceniasz swój poziom doświadczenia w programowaniu w Flutterze?

Początkujący Średnio zaawansowany Zaawansowany

2. Konfigurowanie środowiska programistycznego Fluttera

Aby ukończyć ten moduł, potrzebujesz 2 programów: pakietu SDK Flutteredytora.

Codelab możesz uruchomić na dowolnym z tych urządzeń:

  • fizyczne urządzenie z Android lub iOS podłączone do komputera i ustawione w trybie deweloperskim;
  • Symulator iOS (wymaga zainstalowania narzędzi Xcode).
  • Android Emulator (wymaga konfiguracji w Android Studio).
  • przeglądarka (do debugowania wymagana jest Chrome);
  • Jako aplikacja komputerowa na Windows, Linux lub macOS. Musisz tworzyć aplikację na platformie, na której zamierzasz ją wdrożyć. Jeśli chcesz opracować aplikację na komputery z systemem Windows, musisz to zrobić na komputerze z tym systemem, aby mieć dostęp do odpowiedniego łańcucha kompilacji. Istnieją wymagania dotyczące poszczególnych systemów operacyjnych, które są szczegółowo opisane na stronie docs.flutter.dev/desktop.

3. Pobierz aplikację startową do ćwiczeń z programowania

Kontynuujesz naukę z MDC-101?

Jeśli masz już za sobą MDC-101, kod powinien być gotowy do tego ćwiczenia. Przejdź do kroku: Dodaj górny pasek aplikacji.

Zaczynasz od zera?

Pobieranie aplikacji do ćwiczeń z programowania

Aplikacja startowa znajduje się w katalogu material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series.

...lub sklonuj go z GitHub

Aby skopiować ten codelab z GitHuba, uruchom te polecenia:

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 102-starter_and_101-complete

Otwórz projekt i uruchom aplikację

  1. Otwórz projekt w wybranym edytorze.
  2. Postępuj zgodnie z instrukcjami w sekcji „Uruchamianie aplikacji” w artykule Wprowadzenie: testowanie dotyczącym wybranego edytora.

Gotowe! Na urządzeniu powinna pojawić się strona logowania Shrine z samouczka MDC-101.

Android

iOS

strona logowania z polami nazwy użytkownika i hasła oraz przyciskami Anuluj i Dalej;

strona logowania z polami nazwy użytkownika i hasła oraz przyciskami Anuluj i Dalej;

Ekran logowania wygląda już dobrze, więc teraz wypełnimy aplikację produktami.

4. Dodawanie górnego paska aplikacji

Jeśli teraz klikniesz przycisk „Dalej”, zobaczysz ekran główny z napisem „Udało Ci się!”. Wspaniale. Ale teraz użytkownik nie może podjąć żadnych działań ani nie wie, gdzie w aplikacji się znajduje. Aby mu pomóc, musimy dodać nawigację.

Material Design oferuje wzorce nawigacji, które zapewniają wysoki poziom użyteczności. Jednym z najbardziej widocznych elementów jest górny pasek aplikacji.

Aby zapewnić nawigację i szybki dostęp do innych działań, dodajmy górny pasek aplikacji.

Dodawanie widżetu AppBar

home.dart dodaj AppBar do Scaffold i usuń wyróżniony element const:

return const Scaffold(
  // TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
  ),

Dodanie elementu AppBar do pola appBar: elementu Scaffold zapewnia nam idealny układ bez dodatkowych kosztów, ponieważ element AppBar znajduje się u góry strony, a treść pod nim.

Dodawanie widżetu tekstowego

home.dart dodaj tytuł do paska aplikacji:

// TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
    title: const Text('SHRINE'),
    // TODO: Add trailing buttons (102)

Zapisz projekt.

Android

iOS

pasek aplikacji z tytułem Shrine,

pasek aplikacji z tytułem Shrine,

Wiele pasków aplikacji ma przycisk obok tytułu. Dodajmy do aplikacji ikonę menu.

Dodawanie ikony IconButton na początku

W sekcji home.dart ustaw IconButton dla pola leading: AppBar. (Umieść go przed polem title:, aby zachować kolejność od lewej do prawej):

    // TODO: Add buttons and title (102)
    leading: IconButton(
      icon: const Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
      onPressed: () {
        print('Menu button');
      },
    ),

Zapisz projekt.

Android

iOS

pasek aplikacji z tytułem Shrine i ikoną menu w formie hamburgera;

pasek aplikacji z tytułem Shrine i ikoną menu w formie hamburgera;

Ikona menu (tzw. „hamburger”) pojawia się w oczekiwanym miejscu.

Możesz też dodać przyciski po prawej stronie tytułu. W Flutterze są one nazywane „działaniami”.

Dodawanie działań

Możesz dodać jeszcze 2 przyciski IconButton.

Dodaj je do instancji AppBar po tytule:

// TODO: Add trailing buttons (102)
actions: <Widget>[
  IconButton(
    icon: const Icon(
      Icons.search,
      semanticLabel: 'search',
    ),
    onPressed: () {
      print('Search button');
    },
  ),
  IconButton(
    icon: const Icon(
      Icons.tune,
      semanticLabel: 'filter',
    ),
    onPressed: () {
      print('Filter button');
    },
  ),
],

Zapisz projekt. Ekran główny powinien wyglądać tak:

Android

iOS

pasek aplikacji z tytułem Shrine i ikoną menu w formie hamburgera oraz ikonami wyszukiwania i dostosowywania na końcu;

pasek aplikacji z tytułem Shrine i ikoną menu w formie hamburgera oraz ikonami wyszukiwania i dostosowywania na końcu;

Aplikacja ma teraz przycisk wiodący, tytuł i 2 działania po prawej stronie. Pasek aplikacji wyświetla też wysokość za pomocą subtelnego cienia, który pokazuje, że znajduje się on na innej warstwie niż treść.

5. Dodawanie karty w siatce

Aplikacja ma już pewną strukturę, więc uporządkujmy treści, umieszczając je na kartach.

Dodawanie widoku GridView

Zacznijmy od dodania jednej karty pod górnym paskiem aplikacji. Sam widżet Card nie ma wystarczających informacji, aby wyświetlić się w widocznym miejscu, więc umieścimy go w widżecie GridView.

Zastąp element Center w treści elementu Scaffold elementem GridView:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  // TODO: Build a grid of cards (102)
  children: <Widget>[Card()],
),

Przeanalizujmy ten kod. Widok GridView wywołuje konstruktor count(), ponieważ liczba wyświetlanych przez niego elementów jest policzalna, a nie nieskończona. Potrzebuje jednak więcej informacji, aby określić układ.

Wartość crossAxisCount: określa liczbę elementów w poprzek. Chcemy mieć 2 kolumny.

Pole padding: zapewnia miejsce po wszystkich 4 stronach widoku GridView. Oczywiście nie widać dopełnienia po prawej stronie ani u dołu, ponieważ nie ma jeszcze elementów podrzędnych widoku GridView.

Pole childAspectRatio: określa rozmiar produktów na podstawie współczynnika proporcji (szerokość do wysokości).

Domyślnie widok GridView tworzy kafelki o jednakowym rozmiarze.

Mamy jedną kartę, ale jest ona pusta. Dodajmy widżety podrzędne do naszej karty.

Rozmieszczenie treści

Karty powinny zawierać obszary na obraz, tytuł i tekst dodatkowy.

Zaktualizuj elementy podrzędne widoku GridView:

// TODO: Build a grid of cards (102)
children: <Widget>[
  Card(
    clipBehavior: Clip.antiAlias,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        AspectRatio(
          aspectRatio: 18.0 / 11.0,
          child: Image.asset('assets/diamond.png'),
        ),
        Padding(
          padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Title'),
              const SizedBox(height: 8.0),
              Text('Secondary Text'),
            ],
          ),
        ),
      ],
    ),
  )
],

Ten kod dodaje widżet Column, który służy do pionowego rozmieszczania widżetów podrzędnych.

Znak crossAxisAlignment: field określa CrossAxisAlignment.start, co oznacza „wyrównaj tekst do krawędzi początkowej”.

Widżet AspectRatio określa kształt obrazu niezależnie od tego, jaki obraz jest dostarczany.

Wypełnienie nieco przesuwa tekst z boku.

Dwa widżety Text są ułożone pionowo, a między nimi jest 8 punktów pustego miejsca (SizedBox). Tworzymy kolejną kolumnę, aby umieścić w niej elementy w ramach dopełnienia.

Zapisz projekt.

Android

iOS

pojedynczy element z obrazem, tytułem i tekstem dodatkowym;

pojedynczy element z obrazem, tytułem i tekstem dodatkowym;

W tym podglądzie widać, że karta jest odsunięta od krawędzi, ma zaokrąglone rogi i cień (który wskazuje wysokość karty). Cały kształt w Material Design nazywa się „kontenerem”. (Nie myl tego z rzeczywistą klasą widżetu o nazwie Container).

Karty są zwykle wyświetlane w kolekcji z innymi kartami. Ułóżmy je w kolekcję w siatce.

6. Tworzenie kolekcji kart

Gdy na ekranie znajduje się kilka kart, są one grupowane w 1 lub więcej kolekcji. Karty w kolekcji są współpłaszczyznowe, co oznacza, że znajdują się na tej samej wysokości (chyba że zostaną podniesione lub przeciągnięte, ale w tym przypadku nie będziemy tego robić).

Powielanie karty w kolekcji

Obecnie karta jest tworzona w wierszu pola children: elementu GridView. To dużo zagnieżdżonego kodu, który może być trudny do odczytania. Wyodrębnijmy ją do funkcji, która może generować dowolną liczbę pustych kart i zwracać listę kart.

Utwórz nową funkcję prywatną powyżej funkcji build() (pamiętaj, że funkcje zaczynające się od podkreślenia są prywatnym interfejsem API):

// TODO: Make a collection of cards (102)
List<Card> _buildGridCards(int count) {
  List<Card> cards = List.generate(
    count,
    (int index) {
      return Card(
        clipBehavior: Clip.antiAlias,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            AspectRatio(
              aspectRatio: 18.0 / 11.0,
              child: Image.asset('assets/diamond.png'),
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: const <Widget>[
                  Text('Title'),
                  SizedBox(height: 8.0),
                  Text('Secondary Text'),
                ],
              ),
            ),
          ],
        ),
      );
    },
  );
  return cards;
}

Przypisz wygenerowane karty do pola children w widoku siatki. Pamiętaj, aby zastąpić tym nowym kodem wszystko, co znajduje się w widoku GridView:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(10) // Replace
),

Zapisz projekt.

Android

iOS

siatka elementów z obrazem, tytułem i tekstem dodatkowym;

siatka elementów z obrazem, tytułem i tekstem dodatkowym;

Karty są widoczne, ale jeszcze nic nie wyświetlają. Teraz możesz dodać dane produktów.

Dodawanie danych produktów

Aplikacja zawiera produkty ze zdjęciami, nazwami i cenami. Dodajmy go do widżetów, które już mamy na karcie.

Następnie w home.dart zaimportuj nowy pakiet i niektóre pliki, które udostępniliśmy dla modelu danych:

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

import 'model/product.dart';
import 'model/products_repository.dart';

Na koniec zmień _buildGridCards(), aby pobrać informacje o produkcie, i użyj tych danych na kartach:

// TODO: Make a collection of cards (102)

// Replace this entire method
List<Card> _buildGridCards(BuildContext context) {
  List<Product> products = ProductsRepository.loadProducts(Category.all);

  if (products.isEmpty) {
    return const <Card>[];
  }

  final ThemeData theme = Theme.of(context);
  final NumberFormat formatter = NumberFormat.simpleCurrency(
      locale: Localizations.localeOf(context).toString());

  return products.map((product) {
    return Card(
      clipBehavior: Clip.antiAlias,
      // TODO: Adjust card heights (103)
      child: Column(
        // TODO: Center items on the card (103)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18 / 11,
            child: Image.asset(
              product.assetName,
              package: product.assetPackage,
             // TODO: Adjust the box size (102)
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
               // TODO: Align labels to the bottom and center (103)
               crossAxisAlignment: CrossAxisAlignment.start,
                // TODO: Change innermost Column (103)
                children: <Widget>[
                 // TODO: Handle overflowing labels (103)
                 Text(
                    product.name,
                    style: theme.textTheme.titleLarge,
                    maxLines: 1,
                  ),
                  const SizedBox(height: 8.0),
                  Text(
                    formatter.format(product.price),
                    style: theme.textTheme.titleSmall,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }).toList();
}

UWAGA: nie zostanie jeszcze skompilowany ani uruchomiony. Mamy jeszcze jedną zmianę.

Zmień też funkcję build(), aby przekazywała BuildContext do _buildGridCards(), zanim spróbujesz skompilować kod:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(context) // Changed code
),

Uruchom ponownie aplikację.

Android

iOS

siatka produktów ze zdjęciem, tytułem produktu i ceną;

siatka produktów ze zdjęciem, tytułem produktu i ceną;

Zauważ, że nie dodajemy żadnych spacji pionowych między kartami. Domyślnie mają one 4 punkty marginesu u góry i u dołu.

Zapisz projekt.

Dane o produkcie są wyświetlane, ale wokół zdjęć jest dodatkowe miejsce. Obrazy są domyślnie rysowane z wartością BoxFit równą .scaleDown (w tym przypadku). Zmieńmy to na .fitWidth, aby nieco powiększyć obraz i usunąć dodatkowe odstępy.

Dodaj do obrazu pole fit: o wartości BoxFit.fitWidth:

  // TODO: Adjust the box size (102)
  fit: BoxFit.fitWidth,

Android

iOS

siatka produktów z przyciętym zdjęciem, tytułem produktu i ceną;

Nasze produkty wyświetlają się teraz w aplikacji bez zarzutu.

7. Gratulacje!

Nasza aplikacja ma podstawowy proces, który prowadzi użytkownika od ekranu logowania do ekranu głównego, na którym można wyświetlać produkty. W kilku wierszach kodu dodaliśmy górny pasek aplikacji (z tytułem i 3 przyciskami) oraz karty (do prezentowania treści aplikacji). Ekran główny jest teraz prosty i funkcjonalny, ma podstawową strukturę i zawiera treści, które można wykorzystać.

Dalsze kroki

Użyliśmy już 4 podstawowych komponentów z biblioteki Material Flutter: górnego paska aplikacji, karty, pola tekstowego i przycisku. Więcej informacji znajdziesz w katalogu widżetów komponentów Material.

Aplikacja jest w pełni funkcjonalna, ale nie wyraża jeszcze żadnej konkretnej marki ani punktu widzenia. W module MDC-103: Material Design Theming with Color, Shape, Elevation and Type dostosujemy styl tych komponentów, aby odzwierciedlały żywą, nowoczesną markę.

Udało mi się ukończyć to ćwiczenie w rozsądnym czasie i przy rozsądnym nakładzie pracy.

Zdecydowanie się zgadzam Zgadzam się Nie mam zdania Nie zgadzam się Zdecydowanie się nie zgadzam

Chcę nadal korzystać z komponentów Material

Zdecydowanie się zgadzam Zgadzam się Nie mam zdania Nie zgadzam się Zdecydowanie się nie zgadzam