Flutter. Много
2.93K subscribers
462 photos
24 videos
285 links
Заказать мобильную разработку: https://amiga.ru//?utm_source=tg
Заказать рекламу в канале @amiga_agency_bot

Новости Flutter-разработки, дайджесты мероприятий, личный опыт.
Download Telegram
Hola, Amigos! На связи Павел Гершевич, Mobile Team Lead агентства продуктовой разработки Amiga. Сегодня мы снова поговорим про иконки приложения, но в этот раз про динамические, которые вы можете менять по воздуху или дать возможность пользователю выбрать самому. Про это мы нашли и перевели для вас подробную инструкцию.

Что вы сможете узнать из нее:

• Как добавить сразу несколько вариантов иконок в ваше приложение для iOS и Android;
• Как одновременно предоставить выбор пользователю для изменения настроек приложения и иногда самим их перезаписывать по воздуху;

🔗 Ссылка на переведенную статью

Делитесь в комментариях, пробовали ли вы менять иконку приложения таким способом?
🔥9🥰1
Hola Amigos! Собрали в одну подборку все полезные посты июня, которые вы могли пропустить. Выбирайте, что интересно, и переходите по ссылкам для полезного выходного чтива:

🟡Snippet для анимированного виджета
🟡Форматируем код при сохранении
🟡Кастомные иконки для iOS
🟡Оптимизируем Assets. Часть 1. Часть 2. Часть 3.
🔴Динамические иконки приложения на Flutter

Всем хорошего кода!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9
Forwarded from Amiga
💸 Дешёвый старт, дорогой финал: как выбор технологии мстит команде спустя год

Планируете мобильное приложение и пока не определились со стеком? Прежде чем выбирать по цене и тому, с чем проще запускаться – стоит понимать, во что это выльется через год.

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

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

🔗 https://amiga.ru/blog/flutter-vs-react-native-vs-kmp-vybor-steka-2026
🔥4👍2
Hola, Amigos! Продолжаем разговаривать про настройку наших IDE. В этот раз разберемся, как мы можем работать с импортами в наших .dart файлах и как их настроить, чтобы они сортировались и удалялись, если не используются, автоматически.

Для Android Studio перейдем в настройки IDE (Settings на Windows и Linux, Preferences на macOS), там в “Languages & Frameworks” и выберем Flutter. У нас откроется окно настроек плагина, где у нас уже включен чекбокс “Format code on save”. Под ним есть еще один - “Organize imports on save”, именно он нам и нужен - включаем его.

Для VS Code все немного проще. В settings.json нужно всего лишь включить:


“editor.codeActionsOnSave”: {
“source.organizeImports”: true
}


Теперь при сохранении файла, наши импорты будут упорядочены, но желательно еще настроить правила линтера для этого в нашем analysis_options.yaml:


linter:
rules:
- directives_ordering


или


linter:
rules:
directives_ordering: true


Таким образом, наши импорты будут выстраиваться так:

• Импорты из Dart
• Импорты из библиотек, самого фреймворка или статические импорты из проекта
• Локальные импорты по проекту, без использования package:

Например:


import ‘dart:async’;

import ‘package:flutter/material.dart’;
import ‘package:flutter_bloc/flutter_bloc.dart’;
import ‘package:my_app/common/widgets.dart’;

import ‘widgets/app_bar.dart’;
import ‘../../navigation.dart’;


Делитесь в комментариях, включена ли у вас эта настройка и как вы организуете импорты на ваших проектах?
🔥8👍4
Hola, Amigos! Сегодня мы хотим рассказать про один из редко используемых, но очень полезных виджетов - OverflowBar.

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

Сначала кажется верным использовать Row или Wrap. Но тут мы встречаемся с проблемой - при использовании первого, мы не можем перенести дочерние виджеты, а со вторым не можем растянуть их. Третий вариант - использовать LayoutBuilder и вычислять размеры самим, но это сложно. Вот тут то нам и приходит на помощь виджет OverflowBar.

И вместо


Row(
mainAxisAlignment: MainAxisAlignment.end,
spacing: 8,
children: [
TextButton(onPressed: () {}, child: Text("Cancel")),
TextButton(onPressed: () {}, child: Text("Save")),
ElevatedButton(onPressed: () {}, child: Text("Delete")),
],
)


Мы можем сделать так:


OverflowBar(
alignment: MainAxisAlignment.end,
spacing: 8,
overflowSpacing: 8,
children: [
TextButton(onPressed: () {}, child: Text("Cancel")),
TextButton(onPressed: () {}, child: Text("Save")),
ElevatedButton(onPressed: () {}, child: Text("Delete")),
],
)


Как мы видим, этот виджет больше похож на Wrap, но он работает как смесь Wrap и Row.

Делитесь в комментариях, использовали ли данный виджет на своих проектах?
👍19🔥6❤3
Hola, Amigos! Продолжаем рассказывать про полезные виджеты. Сегодня на очереди SliverMainAxisGroup, который позволит нам увеличить читаемость кода.

Во время разработки экрана со сложной версткой и использовании на нем CustomScrollView, может случиться ситуация, когда количество используемых сливеров становится слишком большим. Из-за этого кода становится настолько много, что его читаемость сильно падает. И тут встает вопрос о выносе некоторых сливеров в отдельный виджет.

Ранее для этого писали собственные решения или использовали виджет MultiSliver из библиотеки sliver_tools. Но время идет, фреймворк обновляется, а старые решения работают все хуже. Например, sliver_tools уже пару лет не обновляется и ее использование может отражаться на производительности приложений.

Сейчас для выноса нескольких сливеров в отдельный виджет есть SliverMainAxisGroup, который работает из коробки. Давайте на него посмотрим:


SliverMainAxisGroup(
slivers: [...]
);


Делитесь в комментариях, используете ли данный виджет на своих проектах или все еще делаете по старинке?
🔥9❤4
Hola, Amigos! На связи Павел Гершевич, Mobile Team Lead в Amiga. В этой небольшой серии мы разберем, как кастомизировать виджеты Slider и RangeSlider, чтобы они лучше соответствовали вашему дизайну.

Делается это как и внутри самого виджета, так и через тему. Начнём с простого кейса: внешний вид слайдера нас устраивает, но нужно изменить только цветовую схему.


Slider(
… // Основные настройки слайдера
activeColor: Colors.red,
inactiveColor: Colors.blue,
overlayColor: WidgetStatePropertyAll(Colors.green),
);


Эти три параметра позволяют быстро адаптировать внешний вид Slider:
• activeColor отвечает за цвет активной части полосы — от начального значения до текущей позиции, а также за цвет индикатора выбранного значения;
• inactiveColor задаёт цвет неактивной части полосы;
• overlayColor позволяет перекрасить цвет выделения, которое показывается при перемещении индикатора.

Те же настройки доступны и для RangeSlider.

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


SliderThemeData(
activeTrackColor: Colors.red,
thumbColor: Colors.red,
inactiveTrackColor: Colors.blue,
overlayColor: Colors.green,
)


Как мы видим, свойство activeColor - разделилось на два: activeTrackColor отвечает за полосу, а thumbColor за индикатор выбранного значения. Кроме этого, тема позволяет кастомизировать и цвета для отключенных виджетов при помощи таких же свойств, только с префиксом disabled.

В следующей части поговорим о более глубокой кастомизации — разберём, как создать собственный индикатор выбранного значения для Slider и RangeSlider.

А пока делитесь в комментариях: приходилось ли вам менять стандартный вид этих виджетов? Какие настройки использовали?
🔥4❤1
Hola, Amigos! На связи Павел Гершевич, Mobile Team Lead в Amiga. В прошлой части мы рассмотрели базовую кастомизацию виджетов Slider и RangeSlider. Сегодня мы углубимся в детали и изменим внешний вид индикатора у слайдера.

Для этого нам нужно будет создать отдельный класс, который наследуется от SliderComponentShape для Slider или RangeSliderThumbShape для RangeSlider:


class AppThumbShape extends SliderComponentShape
class AppRangeThumbShape extends RangeSliderThumbShape


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


@override
Size getPrefferedSize(bool isEnabled, bool isDiscrete) => Size.fromRadius(8);


Далее наступает момент отрисовать на канвасе. Делается это точно также как и при использовании CustomPaint, только свойства у методов немного другие. Давайте на них посмотрим.

Для SliderComponentShape это:
• PaintingContext, который нам и выдает канвас для отрисовки;
• Offset center - координаты центра канваса;
• activationAnimation и enableAnimation - анимации начала взаимодействия и включения / выключения слайдера;
• sliderTheme - тема для получения цветов и всего необходимого, что мы уже настроили ранее;

Остальные параметры могут быть не настолько полезны, кроме тех, что используются при отрисовке текста. Например, выбранного значения.

Для RangeSliderThumbShape добавляются:
• bool isOnTop - показывает, находится ли этот индикатор над другим;
• Thumb thumb - какой это именно индикатор - в начале или в конце интервала.

Давайте нарисуем наш индикатор для обычного Slider:


@override
void paint(
PaintingContext context,
Offset center, {
required Animation<double> activationAnimation,
required Animation<double> enableAnimation,
required bool isDiscrete,
required TextPainter labelPainter,
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required TextDirection textDirection,
required double value,
required double textScaleFactor,
required Size sizeWithOverflow,
) {
final canvas = context.canvas;
final redPaint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
final whitePaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;

canvas
..drawCircle(center, 8, redPaint)
..drawCircle(center, 6, whitePaint)
..drawCircle(center, 3, redPaint);
}


Теперь нужно добавить наш готовый индикатор в тему:


SliderThemeData(
…,
thumbShape: AppThumbShape(),
rangeThumbShape: AppRangeThumbShape(),
)


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

Пишите в комментариях, если уже пробовали кастомизировать слайдеры своими руками! 👇
❤3
Hola, Amigos! На связи Павел Гершевич, Mobile Team Lead в Amiga. В прошлых частях мы разобрали базовую кастомизацию виджетов Slider и RangeSlider, а также замену индикатора на собственный. Сегодня мы займемся изменением полосы значений (Track) под наш дизайн.

Для этого нам так же, как и с индикатором, нужно создать отдельный класс, наследуемый от SliderTrackShape для Slider и RangeSliderTrackShape для RangeSlider.

Данные классы отличаются от тех, которые мы применяли в прошлой части, так как вместо метода на получение размера, тут есть метод на получение объекта Rect. Для примера выберем самое простое - задать только размеры:


@override
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
required SliderThemeData sliderTheme,
bool isEnabled,
bool isDiscrete
}) {
return Rect.fromTLWH(0, 0, parentBox.constraints.width, 8.0);
}


После чего переопределяем метод для отрисовки. В нашем примере полоса всегда будет одинаковой, поэтому мы рисуем только один RRect:


@override
void void paint(
PaintingContext context,
Offset offset, {
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required Animation<double> enableAnimation,
required Offset thumbCenter,
Offset? secondaryOffset,
bool? isEnabled,
bool? isDiscrete,
required TextDirection textDirection
}) {
final trackRect = Rect.fromTLWH(0, 0, parentBox.constraints.width, 8.0);
final trackRRect = RRect.fromRectAndRadius(trackRect, Radius.circular(4);

final redPaint = Paint()
..color = Colors.red
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
final whitePaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;

canvas
..drawRRect(trackRRect, whitePaint)
..drawRRect(trackRRect, redPaint);
}


Если вы хотите показывать часть полосы до индикатора одним стилем, а после - другим, то на помощь придет свойство thumbCenter. Оно означает координаты центра индикатора:


final trackRectBefore = Rect.fromPoints(
Offset.zero,
Offset(thumbCenter.dx, 8.0),
);
final trackRectAfter = Rect.fromPoints(
Offset(thumbCenter.dx, 0),
Offset(parentBox.constraints.width, 8.0),
);


Для RangeSliderTrackShape будут доступны два таких свойства - startThumbCenter и endThumbCenter. С их помощью можно разделить трек на три зоны: неактивную область до начального индикатора, активную область между индикаторами и неактивную область после конечного индикатора.

Для того, чтобы применить наш стиль нужно добавить классы к теме:


SliderThemeData(
…,
trackShape: AppSliderTrackShape(),
rangeTrackShape: AppRangeSliderTrackShape(),
)


Небольшая рекомендация от нас - если применяете собственную отрисовку трека, то лучше всего Slider будет оборачивать в SizedBox для лучшего позиционирования на экране.

Рассказывайте в комментариях, кастомизацию каких виджетов хотели бы увидеть? 🙂
Please open Telegram to view this post
VIEW IN TELEGRAM
1👌2🔥1
Hola, Amigos! Сегодня мы вводим новую рубрику - обзоры правил для статического анализатора #lintRules.

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

Поэтому ловите первую подборку. А также рассказывайте в комментариях, про какие правила рассказать в следующий раз?
👍17
Hola, Amigos! Сегодня посмотрим на один пакет, который может быть полезен в разработке - paged_vertical_calendar.

Он выводит календарь непрерывным вертикальным списком вместо одного месяца переключением. Это полезно в таких случаях:
⭕️ Нужно показать сразу несколько месяцев;
⭕️ Есть выбор интервала дат, которые могут быть в разных месяцах;
⭕️ Нужен другой UX, чем стандартный.

Давайте разберем его базовое использование:


PagedVerticalCalendar(
minDate: DateTime.now().subtract(Duration(days 365)),
maxDate: DateTime.now().add(Duration(days 365)),
initialDate: DateTime.now().add(Duration(days 3)),
invisibleMonthsThreshold: 1,
startWeekWithSunday: true,
onMonthLoaded: (year, month) {
// on month widget load
},
onDayPressed: (value) {
// on day widget pressed
},
onPaginationCompleted: (direction) {
// on pagination completion
},
);


Пройдемся по его параметрам:
• minDate и maxDate помогают нам сделать календарь не бесконечным;
• initialDate - дата начального месяца, который будет показан пользователю посередине списка;
• invisibleMonthThreshold - количество месяцев, которые пользователь не видит, но они просчитываются для дальнейшего отображения;
• startWeekWithSunday - полезное свойство, если приложение работает в странах, где неделя начинается с воскресенья, а не с понедельника;
• onMonthLoaded, onPaginationCompleted - обратные вызовы, которые помогают подгружать данные, если это нужно;
• onDayPressed - обработка нажатия на какой-либо день.

Так как это все-таки список, то ему доступны еще параметры, которые есть у других похожих виджетов - controller, physics, addAutomaticKeepAlives и reverse.

Но самое интересное и важное, что такой календарь можно максимально кастомизировать под себя следующими параметрами:
• dayAspectRatio - помогает задать соотношение сторон для виджета дня;
• weekdaysToHide - скрывает дни недели, если нужно показать только определенные. Например, только рабочие дни;
• listPadding - позволяет задать отступы у списка;
• monthBuilder - позволяет кастомизировать шапку месяца под себя;
• dayBuilder - позволяет кастомизировать отображение дня на календаре.

Рассказывайте, решали ли уже подобные задачи и пользовались ли этим пакетом в своих проектах?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤1🔥1
Дайджест июля

Hola, Amigos! Собрали в одну подборку все полезные посты июля, которые вы могли пропустить. Выбирайте, что интересно, и переходите по ссылкам для полезного выходного чтива:

🔴Организация импортов в проекте;
🔴Обзоры виджетов OverflowBar и SliverMainAxisGroup;
🔴Кастомизация виджета Slider - Часть 1. Часть 2. Часть 3;
🔴Правила статического анализа;
🔴Обзор пакета paged_vertical_calendar.

Всем хорошего кода!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3