Разбираемся с первым Flutter-проектом

На прошлом занятии мы создали первый Flutter-проект. Он очень простой, содержит всего одну кнопку, при нажатии на которую цифра увеличивается. Ниже представлена структура папок этого проекта.

Структура папок проекта

Вы увидите множество папок и файлов: android, web, windows, lib, test и другие. Где хранится исходный код для написания? В папке android? Windows? На самом деле нет, исходный код находится в папке lib. Откройте lib, там вы найдёте файл main.dart.

Папка lib в дальнейшем будет хранить весь ваш исходный код. Вы будете писать код на языке Dart, не на Java или C++. Flutter использует язык Dart. Тогда почему в корне проекта есть папки android, web, windows и другие?

Всё легко понять: возможность Flutter компилироваться в приложения для разных платформ реализована именно за счёт этих папок.

Flutter — это фреймворк по принципу один набор кода Dart, запуск на множестве платформ. Папка lib содержит общий кроссплатформенный бизнес-код, который вы пишете; а папки android / windows / web / macos / linux — это нативные хост-проекты каждой платформы, они отвечают за встраивание движка Flutter в соответствующую операционную систему для запуска.

1. Папка android

Это нативный проект платформы Android

Он предоставляет контейнер для запуска Android-приложения и содержит движок рендеринга Flutter. Все низкоуровневые параметры, связанные с Android, настраиваются здесь: разрешения приложения, иконки, имя пакета и прочее. То есть здесь можно настроить специфичные для Android элементы и вызывать нативные API системы (камера, Bluetooth, уведомления, геолокация и т.д.)

2. Папка windows

Это нативный проект для настольной версии Windows

Это нативный проект C++ Win32, он создаёт окна Windows и инициализирует графический движок Skia. Skia — графическая библиотека Google, с её помощью Flutter отрисовывает окна и все элементы интерфейса десктопных приложений.

В папке windows хранятся низкоуровневые настройки для настольного ПО Windows: размер окна, заголовок окна, иконки, системный трей (значок в правом нижнем углу экрана Windows).

В итоге вы можете собрать проект в форматы exe, msix и опубликовать его в Microsoft Store.

3. Папка web

Проект статических веб-страниц (HTML/JS/CSS), содержит точку входа в веб index.html. Ваш исходный код Dart в итоге компилируется в веб-ресурсы.

Здесь настраиваются заголовок страницы, иконки, статические ресурсы и маршруты. При компиляции выводятся файлы HTML/CSS/JS, которые можно разместить на сервере и открыть в браузере.

Остальные папки

Мы не используем их сейчас, потому что при создании проекта не отметили пункты ios, macos, linux и другие, поэтому этих папок здесь нет. Но принцип их работы полностью одинаковый.

ios: Нативный проект Xcode для Apple iOS, используется для сборки ipa и публикации на iPhone

macos: Нативный проект Cocoa для настольной macOS, клиент для компьютеров Mac

linux: Настольный проект Linux на основе GTK

В правом верхнем углу Android Studio вы выбираете разные платформы, чтобы скомпилировать разные версии приложения. Также можно напрямую использовать командную строку; по сути, при выборе платформы и нажатии зелёного треугольника в Android Studio выполняются команды Flutter.

# Запустить Android, читает конфигурации из папки android
flutter run -d android

# Запустить десктоп Windows, читает C++ проект из папки windows
flutter run -d windows

# Запустить веб-версию, читает html из папки web
flutter run -d chromeCode language: PHP (php)

Откройте файл runner / main.cpp внутри папки windows

#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>

#include "flutter_window.h"
#include "utils.h"

int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
                      _In_ wchar_t *command_line, _In_ int show_command) {
  // Attach to console when present (e.g., 'flutter run') or create a
  // new console when running with a debugger.
  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
    CreateAndAttachConsole();
  }

  // Initialize COM, so that it is available for use in the library and/or
  // plugins.
  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

  flutter::DartProject project(L"data");

  std::vector<std::string> command_line_arguments =
      GetCommandLineArguments();

  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));

  FlutterWindow window(project);
  Win32Window::Point origin(10, 10);
  Win32Window::Size size(1280, 720);
  if (!window.Create(L"myproject", origin, size)) {
    return EXIT_FAILURE;
  }
  window.SetQuitOnClose(true);

  ::MSG msg;
  while (::GetMessage(&msg, nullptr, 0, 0)) {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
  }

  ::CoUninitialize();
  return EXIT_SUCCESS;
}
Code language: PHP (php)

Если вы компилируете для Windows Desktop, то этот main.cpp является настоящей точкой входа программы: сначала выполняется этот файл C++, затем управление переходит в ваш lib/main.dart

Аналогично, если открыть папку android, вы увидите структуру, почти полностью совпадающую с проектами Android, разрабатываемыми на Kotlin или Java.

Там также есть папка src, файл AndroidManifest.xml, MainActivity и другие элементы, знакомые всем, кто разрабатывал Android-приложения. По сути, при разработке Android на Flutter запуск начинается именно из этой папки android.

Точно так же, открыв папку web, вы найдёте точку входа для веб index.html

Файлы Dart

Файл main.dart внутри папки lib — это место, где вы будете вести основную разработку. В папке lib можно создавать подпапки и добавлять другие Dart-файлы.

Откройте main.dart

// Подключение библиотеки компонентов Material
import 'package:flutter/material.dart';

// Точка входа в программу
void main() {
  runApp(const MyApp());
}

// Корневой компонент приложения (без состояния)
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      // Глобальные настройки темы
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      // Главная страница
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

// Главная страница (компонент с состоянием)
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  // Параметр заголовка страницы
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

// Класс управления состоянием главной страницы
class _MyHomePageState extends State<MyHomePage> {
  // Переменная счётчика
  int _counter = 0;

  // Метод увеличения счётчика
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // Базовый шаблон макета страницы
    return Scaffold(
      // Верхняя панель навигации
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      // Основное содержимое страницы
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter', // Отображение числовой переменной
              style: Theme.of(context).textTheme.headlineMedium, // Стиль текста
            ),
          ],
        ),
      ),
      // Плавающая кнопка в правом нижнем углу: onPressed — действие по клику, tooltip — подсказка, child — иконка
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}Code language: JavaScript (javascript)

1. Блок входа

import 'package:flutter/material.dart';
void main() {
  runApp(const MyApp());
}
Code language: JavaScript (javascript)
  • Подключаем официальный набор UI-компонентов Material (кнопки, страницы, текст и т.д.), у Apple есть собственный UI-фреймворк
  • main — единственная точка входа программы Dart; runApp() запускает приложение и монтирует корневой компонент MyApp

2. MyApp — глобальный корневой компонент

Это StatelessWidget (компонент без состояния), мы разберём это позже, сейчас достаточно просто запомнить.

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // Корневой компонент вашего приложения
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
Code language: JavaScript (javascript)
  • Глобальные настройки: название приложения, общая тема, главная страница
  • MaterialApp: обёртка приложения в стиле Material, едино управляет маршрутами, темами, заголовками
  • home задаёт страницу, открывающуюся по умолчанию — MyHomePage

3. MyHomePage — главная страница

Страница, отображающаяся по умолчанию при запуске окна или приложения, это StatefulWidget (компонент с состоянием), мы подробно разберём далее.

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}Code language: JavaScript (javascript)
  • Содержит изменяемые данные (число счётчика), поэтому используется компонент с состоянием
  • Заголовок title передаётся снаружи, внутри класса состояния читается через widget.title
  • Обязательно сопутствует класс _MyHomePageState, предназначенный для хранения переменных страницы и логики взаимодействия

4. _MyHomePageState

Класс логики состояния, отвечает за обработку логики страницы, отрисовку и обновление интерфейса

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(

        backgroundColor: Theme.of(context).colorScheme.inversePrimary,

        title: Text(widget.title),
      ),
      body: Center(

        child: Column(

          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}Code language: JavaScript (javascript)
  1. int _counter = 0: изменяемая переменная страницы — значение счётчика
  2. _incrementCounter(): событие нажатия кнопки
    • setState() уведомляет Flutter об изменении данных, требуется перерисовать интерфейс страницы
    • Увеличение счётчика на единицу
  3. build(): выполняется при каждом обновлении страницы, возвращает готовый макет страницы

5. Scaffold — шаблон страницы

Шаблон макета страницы, принадлежит классу _MyHomePageState

  • appBar: верхняя панель заголовка
  • body: основное содержимое страницы
    • Center выравнивает элементы по центру, Column располагает текст вертикально
  • floatingActionButton: круглая кнопка с плюсом в правом нижнем углу, привязана к методу увеличения по клику

Общий порядок запуска

  1. Запуск программы → выполнение main → загрузка MyApp
  2. MyApp отрисовывает MaterialApp → загружает главную страницу MyHomePage
  3. На странице отображается панель заголовка, центрированный текст и кнопка с плюсом
  4. Нажатие кнопки → запуск _incrementCounter + вызов setState перерисовывает страницу → число обновляется

Разбираемся с первым Flutter-проектом

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *