Поскольку фреймворк для разработки мобильных приложений Flutter становится все более популярным, многие компании предпочитают использовать его в своих проектах.

Многие разработчики ценят его способность разрабатывать приложения с идеальным до пикселя пользовательским интерфейсом, используя простую структуру виджетов. Я думаю, что Flutter — это будущее разработки мобильных приложений из-за его простоты в разработке пользовательских интерфейсов и его способности использовать логику функций с помощью языка программирования Dart.

В этом руководстве основное внимание уделяется обучению вас основам фреймворка Flutter путем создания простого приложения «Калькулятор чаевых». Мы рассмотрим стандартные шаблоны кодирования, включая классы виджетов с отслеживанием состояния и без состояния, а также некоторые виджеты, которые вы будете чаще всего использовать во время разработки приложения Flutter.

Идея состоит в том, чтобы начать с создания стартового проекта Flutter. Затем мы перейдем к реализации общего пользовательского интерфейса и основных функций.

Как настроить свой проект Flutter

Чтобы создать новый проект Flutter, в вашей системе должен быть установлен Flutter SDK. Для простого и быстрого процесса установки вы можете следовать официальной документации flutter.

Помните, что для этого также требуются Android Studio и Android SDK, если вы разрабатываете приложение для платформы Android.

После того, как все успешно настроено, следуя документации, вы можете продолжить и запустить следующую команду Flutter в терминале:

flutter create tipCalculator

Эта команда автоматически загрузит и настроит ваш стартовый проект Flutter. Теперь вы можете открыть проект в IDE Visual Studio Code.

Если у вас подключен симулятор устройства или реальный смартфон, вы можете просто запустить следующую команду, чтобы запустить приложение:

flutter run

Кроме того, вы можете нажать «F5» на клавиатуре, что вызовет опцию меню в VSCode. В этом меню вы можете выбрать устройство, на котором хотите запустить приложение.

Обратите внимание, что для успешного выполнения этой команды вы должны находиться внутри файла с расширением .dart.

Скомпилируйте и запустите его, используя указанную выше команду или F5, чтобы получить следующий стартовый шаблон в вашем эмуляторе / фактическом устройстве:

flutter

Теперь у вас должно быть запущено приложение Flutter.

Давайте подробнее рассмотрим, что происходит в основном файле проекта main.dart.

В файле main.dart у нас есть два объекта класса. Один распространяется на виджеты с отслеживанием состояния, а другой — на виджеты без состояния. Так что это значит?

  • Stateful widget: класс, в котором хранятся состояния приложения. Состояния могут изменяться и запускать рендеринг виджетов в этом классе виджетов с отслеживанием состояния. Это способствует динамическому изменению состояния.
  • Stateless widget: этот класс не содержит состояния. Он представляет вид виджета, который не меняется. Это не способствует динамическому изменению состояния.

В файле main.dart также находится функция main (), которая вызывает класс MyApp внутри метода runApp, чтобы инициировать запуск приложения Flutter на устройстве.

Как создать пользовательский интерфейс калькулятора чаевых

Чтобы начать реализацию нашего пользовательского интерфейса, нам нужно очистить все, что присутствует внутри класса MyHomePageState по умолчанию.

После очистки мы собираемся вернуть простой виджет Scaffold из функции сборки.

Виджет Scaffold предоставляет свойства для добавления appBar, а также тела. А пока мы собираемся добавить простую панель приложений. Вы можете увидеть общую реализацию во фрагменте кода ниже:

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title:Text('Tip Calculator', style: TextStyle(color: Colors.black87),),
      ),
      body: Container()
    );
  }
}

Создайте и запустите его после добавления appBar с помощью виджета AppBar со свойством title. На экране эмулятора вы получите следующий результат:

flutter

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

Шаг 1. Создаем панель приложения

Здесь мы собираемся изменить виджет AppBar, используя различные свойства, которые он предлагает. Вы можете увидеть измененный код во фрагменте ниже:

appBar: AppBar(
  title: Text('Tip Calculator', style: TextStyle(color: Colors.black87),),
  centerTitle: true,
  elevation: 0.0,
  backgroundColor: Colors.white70,
),

Соберите и запустите его, и вы получите следующий результат на экране эмулятора:

Здесь мы использовали некоторые из основных свойств виджета AppBar, такие как:

  • высота, которая позволяет нам контролировать эффект тени на панели приложения аналогично z-index,
  • centerTitle, чтобы централизовать заголовок,
  • и мы также изменили цвет фона на белый.

Шаг 2: проектирование каркаса приложения

До этого момента в свойстве body у нас был только пустой виджет Container. Теперь мы собираемся добавить некоторые свойства и дочерние виджеты к виджету Container, как указано в фрагменте кода ниже:

body: Container(
  color: Colors.white70,
  padding: const EdgeInsets.all(16.0),
  child: Center(
    child: Form(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
        ],
      ),
    ),
  ),

Соберите и запустите его, и вы получите следующий результат на экране эмулятора:

Как вы можете видеть на скриншоте выше, мы изменили цвет фона тела на белый. Кроме того, мы добавили некоторые отступы вместе с виджетом Center в качестве дочернего виджета, который будет централизовать весь пользовательский интерфейс в теле.

У виджета Center есть виджет Form (один из его дочерних элементов), для которого мы собираемся создать простую форму с текстовыми полями.

И самое главное, у нас есть виджет Column как дочерний виджет Form. Виджет Column предоставляет нам свойство массива дочерних виджетов, в которое мы можем интегрировать любое количество виджетов, которые будут отображаться на экране вертикально.

Шаг 3. Определяем константы и переменные

Перед реализацией элементов формы (включая текстовые поля) нам необходимо определить некоторые константы, чтобы обрабатывать ввод из полей ввода.

Вы можете увидеть необходимые константы и переменные во фрагменте кода ниже:

// This is the default bill amount
  static const defaultBillAmount = 0.0;

  // This is the default tip percentage
  static const defaultTipPercentage = 15;

  // This is the TextEditingController which is used to keep track of the change in bill amount
  final _billAmountController =
      TextEditingController(text: defaultBillAmount.toString());

  // This is the TextEditingController which is used to keep track of the change in tip percentage
  final _tipPercentageController =
      TextEditingController(text: defaultTipPercentage.toString());

  // This stores the latest value of bill amount calculated
  double _billAmount = defaultBillAmount;

  // This stores the latest value of tip percentage calculated
  int _tipPercentage = defaultTipPercentage;

В приведенном выше фрагменте кода вы можете видеть, что мы используем метод TextEditingController. Этот метод контроллера позволяет нам позже обрабатывать текстовые вводы в виджете TextFormField, который инициализируется значениями по умолчанию.

Шаг 4: Добавляем поля формы ввода

Теперь мы собираемся добавить два поля формы ввода с помощью виджета TextFormField.

При использовании этого виджета нам необходимо принудительно назначить свойство контроллера нашим переменным контроллера, которые мы определили ранее. Вы можете увидеть общую реализацию кодирования виджета во фрагменте кода ниже:

body: Container(
        color: Colors.white70,
        padding: const EdgeInsets.all(16.0),
        child: Center(
          child: Form(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                TextFormField(
                  key: Key("billAmount"),
                  controller: _billAmountController,
                  keyboardType: TextInputType.numberWithOptions(decimal: true),
                  decoration: InputDecoration(
                      hintText: 'Enter the Bill Amount',
                      labelText: 'Bill Amount',
                      labelStyle: TextStyle(
                        fontSize: 25,
                        letterSpacing: 1,
                        fontWeight: FontWeight.bold
                      ),
                      fillColor: Colors.white,
                      border: new OutlineInputBorder(
                        borderRadius: new BorderRadius.circular(20.0),
                      ),
                    ),
                ),
                SizedBox(height: 25,),
                TextFormField(
                  key: Key("tipPercentage"),
                  controller: _tipPercentageController,
                  keyboardType: TextInputType.number,
                  decoration: InputDecoration(
                    hintText: 'Enter the Tip Percentage',
                    labelText: 'Tip Percentage',
                    labelStyle: TextStyle(
                      fontSize: 25,
                      letterSpacing: 1,
                      fontWeight: FontWeight.bold
                    ),
                    fillColor: Colors.white,
                    border: new OutlineInputBorder(
                      borderRadius: new BorderRadius.circular(20.0),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),

Здесь мы назначили свойство keyboardType, которое позволяет нам отображать требуемый тип клавиатуры, когда пользователь нажимает на поле ввода.

У нас также есть свойства оформления, с помощью которых мы можем стилизовать наши поля ввода с помощью виджета InputDecoration.

В виджете InputDecoration у нас есть несколько свойств, которые помогают нам отображать текст заполнителя, а также метку над полем ввода. Мы также применили свойство границы, чтобы показать границу изогнутого контура.

Соберите и запустите его, и на экране эмулятора вы получите следующий результат:

Шаг 5. Добавляем прослушиватели событий и функции

Поскольку мы собираемся рассчитать сумму чаевых, как только пользователь вводит сумму счета или процент, нам нужно прислушиваться к изменениям в полях ввода текста.

Для этого нам нужно добавить слушателей событий к контроллерам с помощью метода addListener.

Теперь, как только в поле ввода происходят какие-либо изменения, мы также хотим запустить определенную функцию для обновления суммы счета и процента чаевых.

Для этого мы собираемся использовать необходимые функции с методом setState, которые помогут нам отобразить весь пользовательский интерфейс, как только произойдут некоторые изменения.

Обратите внимание, что метод setState запускает повторный запуск метода сборки.

Вы можете увидеть общую реализацию кодирования во фрагменте кода ниже:

@override
void initState() {
  super.initState();
  _billAmountController.addListener(_onBillAmountChanged);
  _tipPercentageController.addListener(_onTipAmountChanged);
}

_onBillAmountChanged() {
  setState(() {
    _billAmount = double.tryParse(_billAmountController.text) ?? 0.0;
  });
}

_onTipAmountChanged() {
  setState(() {
    _tipPercentage = int.tryParse(_tipPercentageController.text) ?? 0;
  });
}

Здесь у нас также есть метод initState. Этот метод запускается, как только мы выходим на этот экран приложения. Поэтому мы добавляем слушателей событий, как только выходим на экран.

Шаг 6: Добавляем раздел вычислений

Теперь вернемся к нашим виджетам пользовательского интерфейса. Мы собираемся добавить раздел Amounts чуть ниже полей ввода внутри виджета Column.

Здесь мы также будем использовать виджет SizedBox, который позволяет нам обеспечить некоторый необходимый интервал между виджетами. Вы можете увидеть реализацию кодирования раздела Amounts в фрагменте кода ниже:

SizedBox(height: 20,),
Container(
  margin: EdgeInsets.all(15),
  padding: EdgeInsets.all(15),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.all(
      Radius.circular(15),
    ),
    border: Border.all(color: Colors.white),
    boxShadow: [
      BoxShadow(
        color: Colors.black12,
        offset: Offset(2, 2),
        spreadRadius: 2,
        blurRadius: 1,
      ),
    ],
  ),
  child: Column(
    children: [
      Text("Tip Amount"),
      Text("Total Amount")
    ],
  ),
),

Здесь у нас есть Container с некоторыми необходимыми стилевыми украшениями. В дочернем свойстве находится еще один виджет Column с двумя виджетами Text, расположенными вертикально.

Соберите и запустите его, и на экране эмулятора вы получите следующий результат:

Шаг 7. Создаем отдельный виджет без сохранения состояния для отображения сумм.

Поскольку мы хотим показать размер чаевых и общую сумму с небольшим стилем. Он не будет содержать никаких состояний, но будет зависеть от значения, переданного из виджета Stateful.

Вы можете увидеть общую реализацию кодирования класса виджета без сохранения состояния AmountText во фрагменте кода ниже:

class AmountText extends StatelessWidget {
  final String text;

  const AmountText(
    this.text, {
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(8),
      child: Text(text.toUpperCase(),
          style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blueAccent, fontSize: 20)),
    );
  }
}

Здесь мы использовали класс конструктора, чтобы получить значение отображаемого фактического текста. Метод сборки этого класса возвращает виджет Container с простым заполнением и виджет Text в качестве дочернего виджета.

Поскольку наш виджет AmountText готов, теперь мы можем вызвать виджет в виджете с отслеживанием состояния.

Мы собираемся добавить виджет внутри виджета Column, который мы определили ранее с помощью простых текстовых виджетов. Нам просто нужно заменить виджет Text на виджет AmountText и передать требуемые текстовые значения.

Вы можете увидеть реализацию кодирования во фрагменте кода ниже:

Container(
  margin: EdgeInsets.all(15),
  padding: EdgeInsets.all(15),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.all(
      Radius.circular(15),
    ),
    border: Border.all(color: Colors.white),
    boxShadow: [
      BoxShadow(
        color: Colors.black12,
        offset: Offset(2, 2),
        spreadRadius: 2,
        blurRadius: 1,
      ),
    ],
  ),
  child: Column(
    children: [
      AmountText(
        'Tip Amount: ${_getTipAmount()}',
        key: Key('tipAmount'),
      ),
      AmountText(
        'Total Amount: ${_getTotalAmount()}',
        key: Key('totalAmount'),
      ),
    ],
  ),
),

Здесь мы передали функцию внутри виджета AmountText. Функция возвращает соответствующие значения суммы чаевых и общей суммы, как вы можете видеть во фрагменте кода ниже:

 _getTipAmount() => _billAmount * _tipPercentage / 100;

  _getTotalAmount() => _billAmount + _getTipAmount();

Наконец, нам нужно завершить работу контроллеров при выходе из представления. Для этого нам нужно использовать встроенную функцию удаления. Эта функция работает, пока мы выходим из текущего экрана.

Внутри этого метода нам нужно вызвать контроллеры с помощью методов dispose, чтобы завершить работу контроллеров ввода текста. Это заставит контроллер перестать прислушиваться к изменениям в полях ввода.

Вы можете увидеть функцию удаления во фрагменте кода ниже:

@override
void dispose() {
  // To make sure we are not leaking anything, dispose any used TextEditingController
  // when this widget is cleared from memory.
  _billAmountController.dispose();
  _tipPercentageController.dispose();
  super.dispose();
}

Постройте и запустите его, чтобы получить окончательный результат нашей реализации Калькулятора чаевых, который вы можете увидеть в демонстрации ниже:

Вы заметите, что значение обеих сумм изменяется, когда мы изменяем ввод в полях ввода.

Мы подошли к концу этого урока. Вы успешно реализовали простой калькулятор чаевых, используя фреймворк Flutter и Dart.