Введение в Dart для Java-программистов

Введение в Dart для Java-программистов

1. Вступление

Dart это язык программирования, используемый во Flutter, мобильном SDK от Google. Сегодня вы освоите язык Dart, при этом особый упор будет сделан на вещах, неочевидных для Java-разработчика.

Вы сможете писать функции в Dart уже через 1 минуту, скрипты через 5 минут, а приложения через 10 минут!

Что вы узнаете

  • Как создавать конструкторы
  • Способы передачи параметров
  • Когда и как создавать геттеры (getters) и сеттеры (setters)
  • Приватность (privacy) в Dart
  • Как создавать фабрики (factories)
  • Функциональное программирование в Dart
  • Другие основные концепции Dart

Что вам понадобится

Для этого урока вам нужен только браузер!

Все примеры вы будете писать и запускать на выполнение в DartPad, интерактивной веб-утилите, позволяющей использовать всю функциональность языка Dart и основных его библиотек. Но вы можете использовать IDE, например, WebStorm, IntelliJ с плагином Dart, или Visual Studio Code с расширением Dart Code.

2. Создаём класс

Начнём с создания простого класса Dart с функционалом, подобным Bicycle class из учебника Java. Класс Bicycle содержит несколько приватных переменных с геттерами и сеттерами. Метод main() создаёт экземпляр Bicycle и выводит его в консоль.

Введение в Dart для Java-программистов

Открываем DartPad

В этом уроке для каждого набора упражнений создаётся новый экземпляр DartPad. Ссылка ниже откроет экземпляр с примером по-умолчанию. Вы можете использовать одну и ту же вкладку с DartPad на весь урок, но помните, что при нажатии кнопки Reset DartPad вернёт всё к началу, затерев ваш код.

Открыть DartPad

Класс Bicycle

Создадим класс Bicycle (выше метода main()), содержащий три переменных. Удалите из main() всё содержимое, как в этом примере:

class Bicycle {
  int cadence;
  int speed;
  int gear;
}

void main() {
}

Наблюдения

  • Главный метод Dart называется main() или (если вам нужен доступ к аргументам командной строки) main(List<String> args).
  • Метод main() находится на самом верхнем уровне. В Dart вы можете размещать код вне классов. Переменные, функции, геттеры и сеттеры — все они могут быть вне классов.
  • В оригинальном примере из Java были приватные переменные, 
    заданные тегом private , которого нет в Dart. О приватности будет написано детально в разделе «Добавим переменную только для чтения».
  • Ни main(), ни Bicycle не указаны как public, потому что все идентификаторы публичны по-умолчанию. В Dart просто нет ключей public, private или protected.
  • Dart по стандарту использует отступ в 2 символа, вместо 4. Но вам не следует об этом беспокоится благодаря утилите dartfmt. Как сказано в соглашении (Effective Dart), «Официальное правило по количеству пробелов в Dart — такое, какое делает dartfmt

Конструктор Bicycle

Добавьте следующий конструктор в класс Bicycle:

Bicycle(this.cadence, this.speed, this.gear);

Наблюдения

  • У этого конструктора нет тела, это допустимо в Dart.
  • Если вы забудете поставить точку с запятой (;) в конце такого конструктора, DartPad выведет ошибку: «A function body must be provided.» (тело функции отсутствует).
  • Слово this в параметрах конструктора — это удобный способ присвоения значений переменным экземпляра.
  • Код выше полностью эквивалентен следующему:
Bicycle(int cadence, int speed, int gear) {
  this.cadence = cadence;
  this.speed = speed;
  this.gear = gear;
}

Форматируем код

Отформатировать код Dart можно нажатием на Format сверху в DartPad. Форматирование бывает полезно при вставке стороннего кода.

Нажмите Format.

Инициализируем и выведем на печать экземпляр bicycle

Добавьте следующий код в функцию main():

void main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

Удалите необязательный ключ new :

var bike = Bicycle(2, 0, 1);

Наблюдения

  • Ключевое слово new стало опциональным в Dart 2.
  • Если вы уверены в том, что значение переменной не изменится, можете использовать final вместо var.

Запускаем пример

Нажмите кнопку Run сверху окна DartPad для запуска на выполнение. Если Run недоступна, читайте раздел Проблемы ниже.

Вы должны увидеть следующий вывод:

Instance of 'Bicycle'

Наблюдения

  • Если нет ошибок, значит анализатор типов правильно определил, что var bike = ... это экземпляр Bicycle.

Улучшаем вывод

Вывод «Instance of ‘Bicycle'» корректен, но малоинформативен. Все классы Dart имеют метод toString(), который вы можете переопределить для получения лучшего вывода.

Добавьте следующий метод toString() в любое место класса Bicycle:

@override
String toString() => 'Bicycle: $speed mph';

Наблюдения

  • Аннотация @override сообщает анализатору, что вы хотите переопределить какой-то метод. Анализатор проверит, что вы корректно реализовали переопределение.
  • Dart поддерживает одиночные или двойные кавычки для определения строк.
  • Используйте строковую интерполяцию для указания значения выражения внутри строкового литерала: ${expression}. Если выражение это идентификатор, вы можете обойтись без скобок: $variableName.
  • Однострочные методы используют стрелочную (=>) нотацию.

Запускаем пример

Нажмите Run.

Теперь вы должны увидеть следующее:

Bicycle: 0 mph

Проблемы?
Проверьте свой код

Добавим переменную только для чтения

В оригинальном примере на Java переменная speed — только для чтения — она приватная и у неё есть только метод геттер. Сделаем то же самое на Dart.

Откройте bicycle.dart в DartPad (или продолжайте с вашим кодом)

Чтобы сделать идентификатор в Dart приватным, напишите его имя  начинающимся с символа подчёркивания (_). Преобразуем speed в переменную «только для чтения», изменив её имя, затем добавим геттер.

В конструкторе Bicycle удалите параметр speed:

Bicycle(this.cadence, this.gear);

В main() удалите второй параметр (speed) из вызова конструктора:

var bike = Bicycle(2, 1);

Замените все speed на _speed. (в 2 местах)

Подсказка: Если вы используете JetBrains IDE, вы легко сможете переименовать все вхождения переменной, кликнув правой кнопкой на её имени, затем выбрать Refactor > Rename… из появившегося меню.

Инициализируем _speed равной 0:

int _speed = 0;

Добавим следующий геттер в класс Bicycle:

int get speed => _speed;

Наблюдения

  • Неинициализированные переменные (и даже числа) имеют значение null.
  • Компилятор Dart делает приватными все переменные, начинающиеся с подчёркивания.
  • По-умолчанию, Dart предлагает неявные геттеры и сеттеры для всех публичных экземпляров переменных. Вам не потребуется создавать свои геттеры/сеттеры, пока не будет необходимости сделать это для переменных «только для чтения» или «только для записи», рассчитать или сверить значение переменной и т.п.
  • Начните с простого поля, типа bike.cadence, затем отрефакторите его с применением геттеров и сеттеров. API при этом останется прежним. Другими словами, путь от поля до геттера/сеттера очень прост в Dart.

Добавьте следующие методы в класс Bicycle:

void applyBrake(int decrement) {
  _speed -= decrement;
}

void speedUp(int increment) {
  _speed += increment;
}

Окончательный пример на Dart выглядит схоже с оригинальным на Java, но более компактный — всего 23 строки вместо 40:

class Bicycle {
  int cadence;
  int _speed = 0;
  int get speed => _speed;
  int gear;

  Bicycle(this.cadence, this.gear);

  void applyBrake(int decrement) {
    _speed -= decrement;
  }

  void speedUp(int increment) {
    _speed += increment;
  }

  @override
  String toString() => 'Bicycle: $_speed mph';
}

void main() {
  var bike = Bicycle(2, 1);
  print(bike);
}

Проблемы?
Проверьте свой код

3. Используем опциональные параметры (вместо перегрузки)

Следующий урок посвящён классу Rectangle.

Код Java часто содержит перегружаемые конструкторы, такие, в которых конструкторы имеют одинаковое имя, но отличаются количеством или типом параметров. Dart не поддерживает такие конструкторы, но предлагает разные подходы к этой задаче.

Откройте пример Rectangle в DartPad

Добавляем конструктор Rectangle

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

Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});

В этом конструкторе используются опциональные именные параметры.

Наблюдения

  • this.origin, this.width и this.height используются для присвоения значений переменным экземпляра внутри конструктора.
  • this.origin, this.width и this.height — опциональные именные параметры. Именные параметры заключены в фигурные скобки ({}).
  • Синтакс this.origin = const Point(0, 0) указывает значение по-умолчанию, равное Point(0,0) для переменной origin. Это значение должно быть константой времени компиляции (compile-time constant). В этом конструкторе значения по-умолчанию указаны для всех трёх переменных.

Улучшаем вывод

Добавим следующую функцию toString() в класс Rectangle:

@override
String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';

Используем конструктор

Чтобы проверить создание класса только с нужными нам параметрами, заменим main() следующим кодом:

main() {
  print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
  print(Rectangle(origin: const Point(10, 10)));
  print(Rectangle(width: 200));
  print(Rectangle());
}

Наблюдения

  • Конструктор класса Rectangle на Dart это всего одна строка, в отличие от 16 строк кода в примере на Java.

Запускаем пример

Вы должны увидеть следующее:

Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0

Проблемы?
Проверьте свой код

4. Создаём фабрику

Фабрики (Factories), популярный шаблон проектирования в Java, имеют некоторые преимущества перед прямым созданием объекта, такие как сокрытие деталей создания, возможность возвращения подтипа фабричного типа, а также возможность возвращения существующего экземпляра вместо нового.

Рассмотрим два способа реализации фабрики:

  • 1: Создадим функцию высшего порядка
  • 2: Создадим конструктор фабрики

Для примера используем класс Shape, создающий фигуры и выводящий информацию об их площадки:

import 'dart:math';

abstract class Shape {
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => pi * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}

main() {
  final circle = Circle(2);
  final square = Square(2);
  print(circle.area);
  print(square.area);
}

Открыть пример в DartPad

В консоли вы должны увидеть рассчитанную площадь круга и квадрата:

12.566370614359172
4

Наблюдения

  • Dart поддерживает абстрактные классы.
  • В одном файле можно создать множество кллассов.
  • dart.math — одна из основных библиотек в Dart. Также там есть dart:core, dart:async, dart:convert и dart:collection.
  • В Dart 1.x константы основной библиотеки были в верхнем регистре (PI); в Dart 2 они в нижнем регистре (pi).
  • В этом коде два геттера, рассчитывающих значения:
    num get area => pi * pow(radius, 2); // Круг
    num get area => pow(side, 2); // Квадрат

Создаём функцию высшего порядка

Создадим фабрику как функцию высшего порядка, добавив следующую функцию на верхний уровень (вне классов):

Shape shapeFactory(String type) {
  if (type == 'circle') return Circle(2);
  if (type == 'square') return Square(2);
  throw 'Can\'t create $type.';
}

Вызовем фабричную функцию, заменив первые две строки метода main():

  final circle = shapeFactory('circle');
  final square = shapeFactory('square');

Запускаем пример

Вывод должен быть таким же, как раньше.

Наблюдения

  • Если функцию вызвать с параметрами, отличающимися от 'circle' или 'square', она выбросит исключение.
  • Dart SDK имеет классы для большинства типовых исключений, также вы можете расширить класс Exception для создания своих исключений.
  • Когда встречается исключение, DartPad выводит Uncaught. Для более детальной информации об ошибке, используйте блоки try-catch с выводом исключения. Также дополнительно можно посмотреть в этом примере DartPad.
  • Для использования одиночных кавычек внутри строки, экранируйте их обратной косой чертой ('Can\'t create $type.') или создавайте строку в двойных кавычках  ("Can't create $type.").

Проблемы?
Проверьте свой код

Фабричный конструктор

Ключевое слово factory создаёт фабричный конструктор в Dart.

Добавим его в абстрактный класс Shape:

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return Circle(2);
    if (type == 'square') return Square(2);
    throw 'Can\'t create $type.';
  }
  num get area;
}

Замените первые две строки в main() на:

  final circle = Shape('circle');
  final square = Shape('square');

Удалите функцию shapeFactory(), добавленную ранее.

Наблюдения

  • Код в фабричном конструкторе идентичен коду в функции shapeFactory().

Проблемы?
Проверьте свой код

5. Реализуем интерфейс

В языке Dart не нужно ключевое слово interface потому что каждый класс создаёт интерфейс.

Откройте пример Shapes в DartPad (или продолжайте в своей копии)

Добавим класс CircleMock, расширяющий класс Circle:

class CircleMock implements Circle {}

Вы должны увидеть ошибку «Missing concrete implementations» (отсутствует реализация). Уберём эту ошибку, создав переменные area и radius:

class CircleMock implements Circle {
  num area;
  num radius;
}

Наблюдения

  • Несмотря на то, что класс CircleMock не описывает никакого поведения, он корректен в Dart — анализатор не выводит ошибки.

Проблемы?
Проверьте свой код

6. Функциональное программирование в Dart

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

  • Передавать функции как аргументы.
  • Присваивать функции переменным.
  • Деконструировать функции, принимающие множество аргументов, в последовательность функций, каждая из которых принимает единственный аргумент.
  • Создавать безымянные функции, которые можно использовать как значение констант (их также называют лямбдами).

Dart поддерживает всё это. В Dart даже функции это объекты (имеющие тип Function) и это значит, что функции могут быть присвоены переменным или переданы как аргументы, другим функциям. Также вы можете вызвать класс Dart как функцию, как в этом примере.

Следующий пример описан в императивном (не функциональном) стиле:

String scream(int length) => "A${'a' * length}h!";

main() {
  final values = [1, 2, 3, 5, 10, 50];
  for (var length in values) {
    print(scream(length));
  }
}

Откройте пример в DartPad

Его вывод должен быть таким:

Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!

Наблюдения

  • При использовании интерполяции строк, строка ${'a' * length} читается как «символ 'a' повторить length раз.»

Конвертируем императивный код в функциональный

Удалите императивный цикл for() {...} в main() и замените его таким однострочным выражением:

  values.map(scream).forEach(print);

Запустите пример

Функциональный подход также выведет эти же строки.

Проблемы?
Проверьте ваш код

Больше итераторов

Списки и итераторы из dart:collection поддерживают методы fold, where, join, skip и другие. В Dart также есть Map-ы и Set-ы.

Замените строку values.map() в main() следующей:

  values.skip(1).take(3).map(scream).forEach(print);

Запустите пример

Вывод должен быть примерно такой:

Aaah!
Aaaah!
Aaaaaah!

Наблюдения

  • skip(1)пропускает первое значение, 1, в списке values .
  • take(3)берёт следующие 3 значения — 2, 3 и 5 — в списке values.
  • Остальные значения списка проигнорированы.

Проблемы?
Проверьте свой код

7. Поздравляем!

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

Дальнейшие шаги

За 20 минут невозможно показать все отличия между Java и Dart. К примеру, мы не рассмотрели следующие темы:

  • async/await, что позволит писать асинхронный код так, как синхронный. Откройте пример в DartPad с анимацией расчёта первых пяти чисел π.
  • Каскадный метод, где всё — builder!
  • Null-безопасные операторы

Источник

Подписывайтесь на новости Flutter! https://t.me/flutterdaily

Flutter: реализация BottomAppBar с FAB

Flutter: BottomAppBar с FAB

Сегодня мы узнаем, как добавить FloatingActionButton (FAB) к BottomAppBar в Flutter. В итоге должно получиться что-то такое:

Flutter: реализация BottomAppBar с FAB

Наша задача — сделать BottomAppBar, который будет работать так же, как BottomNavigationBar. В нём будет несколько вкладок и только одна из них активная.

Если вы только начинаете разрабатывать на Flutter, обратите внимание на статью Создание первого приложения Flutter.

Вы можете удивиться, почему нельзя использовать сразу BottomNavigationBar? Несмотря на то, что технически можно вставить FloatingActionButton в BottomNavigationBar, на практике это решение полно недостатков.

Добавляем отцентрированный FAB

После создания нового проекта Flutter, в нём уже есть FloatingActionButton для увеличения счётчика.

Мы можем добавить BottomAppBar в наш Scaffold.bottomNavigationBar вот так:

return Scaffold(
  appBar: AppBar(
    title: Text(widget.title),
  ),
  floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
  floatingActionButton: FloatingActionButton(
    onPressed: () { },
    tooltip: 'Increment',
    child: Icon(Icons.add),
    elevation: 2.0,
  ),
  bottomNavigationBar: BottomAppBar(
    child: Row(
      mainAxisSize: MainAxisSize.max,
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[],
    ),
    notchedShape: CircularNotchedRectangle(),
    color: Colors.blueGrey,
  ),
);

Обратите внимание, как мы установили положение Scaffold.floatingActionButtonLocation в FloatingActionButtonLocation.centerDocked для того, чтобы вставить FAB в центр BottomAppBar.

Также мы добавили notchedShape: CircularNotchedRectangle() для создания выемки в BottomAppBar для FAB.

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

Flutter: реализация BottomAppBar с FAB

Добавляем вкладки

Чтобы добавить вкладки с отдельными страницами в наше приложение, мы можем создать FABBottomAppBar:

  • он вмещает 2 или 4 вкладки (из-за симметрии).
  • подсвечивает и отслеживает выбранную вкладку.
  • делает callback при выборе вкладки, чтобы приложение могло обновить нужную страницу.

Сделаем набросок класса FABBottomAppBar:

class FABBottomAppBarItem {
  FABBottomAppBarItem({this.iconData, this.text});
  IconData iconData;
  String text;
}

class FABBottomAppBar extends StatefulWidget {
  final List<FABBottomAppBarItem> items;
  final ValueChanged<int> onTabSelected;
  
  @override
  State<StatefulWidget> createState() => FABBottomAppBarState();
}

class FABBottomAppBarState extends State<FABBottomAppBar> {
  int _selectedIndex = 0;

  _updateIndex(int index) {
    widget.onTabSelected(index);
    setState(() {
      _selectedIndex = index;
    });
  }
  
  // TODO: build method here
}

Вначале мы создали класс FABBottomAppBarItem, содержащий переменные IconData и String. Так мы описали вкладку.

Затем мы создали виджет FABBottomAppBar. Это необходимо для создания списка вкладок и задания callback (onTabSelected).

FABBottomAppBar это StatefulWidget, так как он должен отслеживать выбранную вкладку и обновлять её.

В классе FABBottomAppBarState мы задали _selectedIndex, обновляемый при вызове _updateIndex.

Создадим метод build:

@override
Widget build(BuildContext context) {
  List<Widget> items = List.generate(widget.items.length, (int index) {
    return _buildTabItem(
      item: widget.items[index],
      index: index,
      onPressed: _updateIndex,
    );
  });

  return BottomAppBar(
    child: Row(
      mainAxisSize: MainAxisSize.max,
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: items,
    ),
  );
}

Здесь:

  • Строки с 3 по 9: здесь мы используем генератор списка для создания вкладок, передавая нужный элемент, индекс и ширину.
  • Строки с 11 по 17: создаём BottomAppBar, содержащий Row с заданными элементами. Мы используем MainAxisSize.max и MainAxisAlignment.spaceAround для корректного размещения элементов в ряду в полную ширину.

Реализуем метод _buildTabItem:

Widget _buildTabItem({
  FABBottomAppBarItem item,
  int index,
  ValueChanged<int> onPressed,
}) {
  Color color = _selectedIndex == index ? widget.selectedColor : widget.color;
  return Expanded(
    child: SizedBox(
      height: widget.height,
      child: Material(
        type: MaterialType.transparency,
        child: InkWell(
          onTap: () => onPressed(index),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Icon(item.iconData, color: color, size: widget.iconSize),
              Text(
                item.text,
                style: TextStyle(color: color),
              )
            ],
          ),
        ),
      ),
    ),
  );
}

В строке 6 мы проверяет соответствие выбранного индекса индексу текущей вкладки, затем выбираем цвет вкладки в соответствии с этим.

В строках с 13 по 26 мы создаём InkWell внутри виджета Material. Таким образом мы получаем распознавание жеста onTap и специальный эффект при нажатии.

Потомком InkWell является Column, содержащий Icon и Text, оба настроенные на приём данных из FABBottomAppBarItem.

И всё это завёрнуто внутрь виджета Expanded. Таким образом каждый элемент имеет одинаковую ширину внутри родительского Row.

Протестируем FABBottomAppBar, указав его как bottomNavigationBar в Scaffold, прописав четыре элемента:

bottomNavigationBar: FABBottomAppBar(
  onTabSelected: _selectedTab,
  items: [
    FABBottomAppBarItem(iconData: Icons.menu, text: 'This'),
    FABBottomAppBarItem(iconData: Icons.layers, text: 'Is'),
    FABBottomAppBarItem(iconData: Icons.dashboard, text: 'Bottom'),
    FABBottomAppBarItem(iconData: Icons.info, text: 'Bar'),
  ],
),

Результат:

Flutter: реализация BottomAppBar с FAB

И всё работает, вкладки переключаются, при выборе происходит вызов callback.

Подводя итоги

Итак, прогресс определённо есть, компонент работает как задумано. Осталось немного доработать FABBottomAppBar.

В примере выше мы захардкодили такие вещи, как:

  • ПараметрыBottomAppBarheight (высота), backgroundColor (цвет фона) и notchedShape (форма выемки)
  • Размер иконки
  • Цвет активной/неактивной вкладок

Также иконки на вкладках посередине находятся слишком близко к FAB. Нужно добавить небольшой отступ для них.

Финальная версия FABBottomAppBar с указанными доработками:

import 'package:flutter/material.dart';

class FABBottomAppBarItem {
  FABBottomAppBarItem({this.iconData, this.text});
  IconData iconData;
  String text;
}

class FABBottomAppBar extends StatefulWidget {
  FABBottomAppBar({
    this.items,
    this.centerItemText,
    this.height: 60.0,
    this.iconSize: 24.0,
    this.backgroundColor,
    this.color,
    this.selectedColor,
    this.notchedShape,
    this.onTabSelected,
  }) {
    assert(this.items.length == 2 || this.items.length == 4);
  }
  final List<FABBottomAppBarItem> items;
  final String centerItemText;
  final double height;
  final double iconSize;
  final Color backgroundColor;
  final Color color;
  final Color selectedColor;
  final NotchedShape notchedShape;
  final ValueChanged<int> onTabSelected;

  @override
  State<StatefulWidget> createState() => FABBottomAppBarState();
}

class FABBottomAppBarState extends State<FABBottomAppBar> {
  int _selectedIndex = 0;

  _updateIndex(int index) {
    widget.onTabSelected(index);
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> items = List.generate(widget.items.length, (int index) {
      return _buildTabItem(
        item: widget.items[index],
        index: index,
        onPressed: _updateIndex,
      );
    });
    items.insert(items.length >> 1, _buildMiddleTabItem());

    return BottomAppBar(
      shape: widget.notchedShape,
      child: Row(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: items,
      ),
      color: widget.backgroundColor,
    );
  }

  Widget _buildMiddleTabItem() {
    return Expanded(
      child: SizedBox(
        height: widget.height,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            SizedBox(height: widget.iconSize),
            Text(
              widget.centerItemText ?? '',
              style: TextStyle(color: widget.color),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildTabItem({
    FABBottomAppBarItem item,
    int index,
    ValueChanged<int> onPressed,
  }) {
    Color color = _selectedIndex == index ? widget.selectedColor : widget.color;
    return Expanded(
      child: SizedBox(
        height: widget.height,
        child: Material(
          type: MaterialType.transparency,
          child: InkWell(
            onTap: () => onPressed(index),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Icon(item.iconData, color: color, size: widget.iconSize),
                Text(
                  item.text,
                  style: TextStyle(color: color),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Обратите внимание, как мы задаём centerItemText, который будет размещён прямо под FAB. Если он пустой или null, мы отобразим пустой Text.

Вот что получится в итоге:

Flutter: реализация BottomAppBar с FAB

Что же с BottomNavigationBar?

Я пробовал расположить FAB внутри BottomNavigationBar, но у меня были такие проблемы:

  • Невозможно добавить шаблонный или пустой текст под FAB, кроме как создать BottomNavigationBarItem. Но это нежелательно, так как BottomNavigationBarItem это вкладка и может быть сама по себе задействована.
  • BottomNavigationBar не поддерживает notchedShape.

В целом BottomAppBar требует написания большего количества кода, но в результате получается лучше из-за использования более удобного в настройке Row.

Исходный код

Полный исходный код приложения вы можете скачать на GitHub

Хорошей разработки!

По материалам поста «Flutter: BottomAppBar Navigation with FAB» by Andrea Bizzotto.

Подписывайтесь на новости Flutter! https://t.me/flutterdaily

Командная строка linux

Командная строка linux
Сегодня мы поговорим о командной строке. Командная строка линукс — это один из важнейших интерфейсов операционной системы, он встроен в ядро и доступен даже в том случае, когда графическая подсистема не загружается (или она не установлена).Выглядеть она может по-разному, в зависимости от графического окружения и способа запуска. Например, так:
Командная строка linux
Запущена командой Ctrl+Alt+F2
Или так:
Командная строка linux
Оболочка Konsole, часть окружения KDE
Что мы здесь видим? Вот эта строка: igor@fokusov:~$ называется строка-приглашение, где:
  • igor — имя пользователя
  • fokusov — имя компьютера
  • : — разделитель;
  • ~ — обозначение текущей папки (здесь ~ означает домашнюю директорию /home/<имя пользователя>);
  • $ или # — символ приглашения ввода. При этом $ означает, что вы зашли обычным пользователем, а # — вы зашли под администратором (суперпользователь или root).

Как вызвать командную строку в linux

Открыть командную строку линукс можно несколькими способами:
  • Без графической подсистемы:
    Вызвать одну из виртуальных консолей можно нажав следующие комбинации клавиш: 

Ctrl+Alt+F2 — вторая виртуальная консоль
Ctrl+Alt+F3 — третья виртуальная консоль
Ctrl+Alt+F4 — четвертая виртуальная консоль
Ctrl+Alt+F5 — пятая виртуальная консоль
Ctrl+Alt+F6 — шестая виртуальная консоль

Ctrl+Alt+F1 — возврат в основной графический режим (или
Ctrl+Alt+F7 на некоторых дистрибутивах)

  • В графическом окружении её можно найти в меню приложений, например, так: 
    Главное меню → Приложения → Система → Терминал
Командная строка linux
Или сразу в первой вкладке главного меню

Команды командной строки linux

Команды — это названия приложений, которые можно выполнить, набрав имя этого приложения с клавиатуры и нажав Enter.Типичная команда строится так:
название_программы -ключ значение 
где
название_программы  — это название исполняемого файла из каталогов, записанных в переменной $PATH (/bin, /sbin, /usr/bin, /usr/sbin, /usr/local/bin, /usr/local/sbin и др.) или полный путь к исполняемому файлу (например, /opt/deadbeef) 
ключ — пишется после названия исполняемого файла, например -h (стандартный ключ для запуска справки). У каждой программы свой набор ключей, их лучше узнать в справке к программе. Ключи используются по-разному, но в основном для указания используемых настроек приложения
значение — всё, что угодно — адрес, цифры, текст, спецсимволы (*, ~, \, &, « », _ ), переменные ($HOME, $USER, $PATH) и т.п.
Команд очень много и перечислять все не имеет смысла, однако вот список основных системных команд линукс:cd — Смена директории (папки). Например, выполнив команду cd ~  я попаду в свой домашний каталог. А команда cd ../..  перейдёт на директорию, на два уровня выше текущей.pwd — покажет текущую директориюmkdir dir1 — создаст каталог «dir1» в текущей папкеrm file.txt — удалит файл «file.txt»cp file1 file2 — скопирует file1 в file2mv dir1 dir2 — переименует или переместит файл или папкуРассмотрим некоторые команды подробнее.

Как создать файл в командной строке linux

Для создания пустого файла есть команда touch:touch file.txt — создаст пустой файл с указанным именем в текущей папкеДля создания файла заданного размера можно воспользоваться  командой dd:dd of=file bs=1 count=0 seek=10M — создаст файл с именем «file» размером 10 Мб.

Копирование файлов в linux через командную строку

Выше была описана команда cp, но она имеет несколько важных опций, которые мы и опишем здесь.cp file1 file2 — копирует содержимое файла file1 в file2. Если file2 не существует, он будет создан; в противном случае, file2 полностью перезапишется содержимым file1.cp -i file1 file2 — с ключом «-i» (интерактивно) если file2 существует, команда спросит у пользователя, нужно ли перезаписать этот файл.cp file1 dir1 — копирует содержимое файла file1 (в файл с именем file1) в папку dir1.cp -R dir1 dir2  — копирует содержимое папки dir1. Если папка dir2 не существует, она будет создана. Иначе папка dir1 будет скопирована в папку dir2.

Как узнать версию линукса из командной строки

Здесь на помощь приходит команда uname. У неё есть несколько ключей, наиболее полная информация выводится с ключом -a. А ключ -r покажет версию ядра. 
Командная строка linux
Также есть очень крутая утилита inxi. Установить её в Ubuntu и Mint можно командой sudo apt install inxi.Ключей у inxi очень много, но информацию по системе можно узнать ключами -S и -C:
Командная строка linux

Выключение (перезагрузка) линукс из командной строки

Для перезагрузки и выключения есть команда shutdown.

shutdown -h now — выключит систему
shutdown -h 12:00 & — планирование выключения системы на указанное время (здесь 12:00)
shutdown -c — отменит запланированное выключение системы
shutdown -r now — перегрузка системы
logout — выход из сеанса

Как поднять сеть в linux из командной строки

Если у вас есть проблемы с автоматическим подключением к сети wifi, возможно, вам поможет следующее решение:Убедимся, что наш адаптер работает. Выполните команду iwconfig:
ubuntu@ubuntu:~$ iwconfig
lo        no wireless extensions.

eth0      no wireless extensions.

wmaster0  no wireless extensions.

wlan0     IEEE 802.11bg  ESSID:""  
          Mode:Managed  Frequency:2.412 GHz  Access Point: Not-Associated   
          Tx-Power=27 dBm   
          Retry min limit:7   RTS thr:off   Fragment thr=2352 B   
          Power Management:off
          Link Quality:0  Signal level:0  Noise level:0
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:0  Invalid misc:0   Missed beacon:0
Устройство работает. Теперь проверим доступные беспроводные сети командой iwlist:
ubuntu@ubuntu:~$ iwlist wlan0 scan

wlan0     Scan completed :
          Cell 01 - Address: 00:00:00:00:00:00
                    ESSID:"ubuntuessid"
                    Mode:Master
                    Channel:8
                    Frequency:2.447 GHz (Channel 8)
                    Quality=7/100  Signal level:-99 dBm  Noise level=-104 dBm
                    Encryption key:on
                    Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 18 Mb/s
                              24 Mb/s; 36 Mb/s; 54 Mb/s; 6 Mb/s; 9 Mb/s
                              12 Mb/s; 48 Mb/s
                    Extra:tsf=0000001d57e98bf6
                    Extra: Last beacon: 388ms ago
Если адаптер не включен, тогда вы увидите следующее:
wlan0    Failed to read scan data : Network is down
Просто введите команду:
sudo ip link set dev wlan0 up

Редактирование файла в linux через командную строку

Для редактирования файлов в командной строке есть немало текстовых редакторов. Однако, за простоту и удобство, я люблю редактор nano:
Командная строка linux
Открыть файл «file1» в nano можно командой nano file1. Все команды в редакторе перечислены внизу окна и знак ^ означает клавишу Ctrl.Например, после редактирования сохранить файл нужно клавишами Ctrl+o, а закрыть редактор — Ctrl+x.

Как закомментировать строку в linux

Что значит закомментировать строку в linux? Обычно пользователю необходимо убрать из сценария выполнение некоторых строк, при этом удалять эти строки не нужно.В таком случае нужно открыть файл редактором, например, nano:
nano myscript.sh
И в начале строк, которые не нужны, добавить символ #
# эта строка не выполнится
# эта тоже
time # команда time выполнится
После этого сохраните (Ctrl+o) и закройте (Ctrl+x) файл.

Как сделать автоматический ответ Y / N в консоли Linux 

Иногда бывает необходимость небольшой автоматизации какого-то процесса, который требует от пользователя постоянно отвечать yes или no. Для этого есть команда yes, например:yes | sudo apt updateДля автоввода отрицательных ответов поможет такой способ:yes no | <команда>

Команда echo

Рассмотрим полезную утилиту echo и её использование.
Командная строка linux
Как мы видим, команда echo убирает лишние пробелы, при этом нормально отрабатывает комментарии. Для вывода нужного количества пробелов, заключайте строку в кавычки.По-умолчанию, echo заканчивает вывод символом переноса строки. Чтобы запретить это, используйте ключ -n. Чтобы разрешить использование начинающихся с символа «\» escape-последовательностей, используйте ключ -e.Вот некоторые escape-последовательности:

\f — Перевод страницы
\n — Новая строка
\r — Возврат каретки
\t — Табуляция

Замена строки в файле с sed

Sed позволяет находить нужные строки в файле и заменять их. Основной сценарий запуска sed:
sed -i 's/.*ЧТО_ИЩЕМ.*/ЧЕМ_ЗАМЕНЯЕМ/' ИМЯ_ФАЙЛА
где ключом -i мы просим перезаписать исходный файл
Adblock detector