Flutter Pulse
614 subscribers
385 photos
843 links
На канале будут новости про flutter с сайтов, информация об обновлении пакетов, а также авторский контент.
Download Telegram
Отслеживание видимости клавиатуры
Без каких-либо плагинов 😉

В разработке Flutter-приложений часто возникает необходимость отслеживать состояние клавиатуры (видима она или нет). Это может быть полезно, например, когда нужно скрыть или показать определенные элементы интерфейса в зависимости от того, открыта клавиатура или нет.

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

Решение:
Для отслеживания состояния клавиатуры мы можем использовать виджет KeyboardVisibility, который не требует установки дополнительных плагинов.



import 'package:flutter/material.dart';

// состояние клавиатуры
enum KeyboardVisibilityState { visible, hidden }

// определение функции слушателя
typedef OnKeyboardStateChanged = void Function(KeyboardVisibilityState state);

// stateful виджет для отслеживания изменения видимости клавиатуры
class KeyboardVisibility extends StatefulWidget {
final Widget child;
final OnKeyboardStateChanged onKeyboardStateChanged;

const KeyboardVisibility({
super.key,
required this.child,
required this.onKeyboardStateChanged,
});

@override
State<KeyboardVisibility> createState() => _KeyboardVisibilityState();
}

class _KeyboardVisibilityState extends State<KeyboardVisibility> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
void didChangeMetrics() {
super.didChangeMetrics();
checkState();
}

void checkState() {
final value = WidgetsBinding.instance.platformDispatcher.views.first.viewInsets.bottom;
switch (value != 0.0) {
case true:
widget.onKeyboardStateChanged(KeyboardVisibilityState.visible);
case false:
widget.onKeyboardStateChanged(KeyboardVisibilityState.hidden);
}
}

@override
Widget build(BuildContext context) {
return widget.child;
}
}



Использование:
Виджет KeyboardVisibility можно использовать следующим образом:



KeyboardVisibility(
onKeyboardStateChanged: (state) => _showOrHideBottomAction(state),
child: Form(...),
)



Оцените новую рубрику и напишите свое мнение в комментариях! 👍

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #UIUX #CodingTips #AppDev #DevTips
1👍1
Создание изображения из виджета

Привет, Flutter-разработчики! 👋 Сегодня мы рассмотрим интересную и полезную тему: создание изображения из любого виджета в вашем приложении. 📸

Шаг 1: Добавьте RepaintBoundary поверх виджета, который хотите экспортировать

Чтобы создать изображение из виджета, сначала нужно обернуть его в RepaintBoundary. Для этого потребуется GlobalKey, который поможет найти нужный виджет в дереве рендеринга.



final GlobalKey _repaintBoundaryKey = GlobalKey();

RepaintBoundary(
key: _repaintBoundaryKey,
child: CustomPaint(
painter: MyPainter(),
...
),
)



Этот ключ позволит нам найти объект рендеринга и вызвать метод toImage. 📚 Для более глубокого понимания можно изучить, что такое Widget tree, Element tree и RenderObject tree.

Шаг 2: Вызовите метод для экспорта изображения в галерею телефона

Теперь напишем функцию, которая будет экспортировать изображение:



import 'package:image_gallery_saver/image_gallery_saver.dart';

Future<void> exportImage() async {
try {
RenderRepaintBoundary boundary = _repaintBoundaryKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
ui.Image image = await boundary.toImage(pixelRatio: 3.0);
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData!.buffer.asUint8List();

final directory = await getDownloadsDirectory();
final imgName = 'myapp-${DateTime.now().microsecondsSinceEpoch}';
final result = await ImageGallerySaver.saveImage(pngBytes, quality: 100, name: imgName);

if (result['isSuccess']) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Изображение экспортировано')),
);
}
} catch (e) {
// Обработайте ошибку (покажите сообщение пользователю, сохраните отчет о сбое)
}
}



Мы используем пакет image_gallery_saver, чтобы сохранить изображение в галерею телефона. 📁📸

Оцените новую рубрику и напишите в комментариях, что хотите видеть в следующих постах! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #CodingTips #UIUX #FlutterTips
🔥1
Сохранение позиции прокрутки страницы

Привет, разработчики Flutter! 👋 Сегодня мы поделимся полезным советом о том, как сохранить позицию прокрутки страницы при навигации между разными экранами. 📱💻

Вы когда-нибудь сталкивались с ситуацией, когда пользователь прокручивал список или контент на странице, а затем возвращался обратно, и страница снова оказывалась в самом верху? 😒 Это может быть неудобно для пользователей, особенно если они хотят вернуться к тому месту, где они остановились.

Решение: использование PageStorage и PageStorageBucket

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

Вот пример кода:


final PageStorageBucket _bucket = PageStorageBucket(); // Создание хранилища для страниц

final pages = <Widget>[ // Список страниц
Page1(key: const PageStorageKey('page1'), ...), // Первая страница с уникальным ключом
Page2(key: const PageStorageKey('page2'), ...), // Вторая страница с уникальным ключом
];

@override
Widget build(BuildContext context) {
return Scaffold( // Основной виджет страницы
body: PageStorage( // Обертка для сохранения состояния прокрутки
child: pages[currentTab], // Текущая отображаемая страница
bucket: _bucket, // Передача хранилища
),
);
}



Как это работает?

1. Мы создаем экземпляр PageStorageBucket, который будет хранить состояние наших страниц.
2. Каждой странице присваиваем уникальный ключ с помощью PageStorageKey. Это позволяет PageStorage идентифицировать каждую страницу и сохранять её состояние.
3. Обернём наши страницы в виджет PageStorage, передав туда текущего ребёнка (текущую страницу) и наше хранилище (_bucket).

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

Оцените новую рубрику и напишите в комментариях, какие темы вы хотели бы видеть в наших следующих постах! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #UIUX #DevTips
👍4
Правило 8 для интервалов в дизайне

Генерация визуальной гармонии на подсознательном уровне

Вы когда-нибудь задумывались, что делает дизайн визуально приятным? 🤔 Одним из секретов является соблюдение правила 8 при проектировании интервалов между элементами! 📐

Что такое правило 8?

Теория довольно проста: все элементы в вашем дизайне кратны 8 по ширине и высоте, как и расстояния между ними. 📏 Это создает ощущение гармонии и порядка, делая интерфейс более интуитивным и комфортным для пользователя. 😌

Давайте рассмотрим пример реализации этого правила во Flutter:


class AppSpacer extends StatelessWidget {
final double? width;
final double? height;

const AppSpacer._({Key? key, this.width, this.height}) : super(key: key);

factory AppSpacer.p32() => const AppSpacer._(height: 32, width: 32);
factory AppSpacer.p24() => const AppSpacer._(height: 24, width: 24);
factory AppSpacer.p16() => const AppSpacer._(height: 16, width: 16);
factory AppSpacer.p8() => const AppSpacer._(height: 8, width: 8);

@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height,
);
}
}



Этот код определяет виджет AppSpacer, который можно использовать для создания интервалов, кратных 8. 📝 Просто используйте один из фабричных конструкторов, таких как AppSpacer.p8(), AppSpacer.p16() и т.д., чтобы добавить нужный интервал в вашем интерфейсе. 👍

Оцените новую рубрику лайком 👍, если считаете её полезной! 💬 Поделитесь своими мыслями в комментариях! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #SpacingDesign #DesignTips #FlutterTips #UIUX #MobileDev #AppDev
👍4
Воспроизведение видео на весь экран
Покажите полноэкранное видео с правильным соотношением сторон 🤩

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

1. Установите пакет video_player 📦
2. Встройте видеоплеер внутрь виджета VideoContainer 📺

Обеспечьте, чтобы содержимое занимало весь экран 📱

return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
),
extendBodyBehindAppBar: true,
body: Stack(
children: [
Positioned.fill(
child: GestureDetector(
onTap: () => videoListener?.pauseOrResume(),
child: VideoContainer.fromController(_controller!),
),
),
],
),
);


Код виджета VideoContainer:

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

class VideoContainer extends StatelessWidget {
final Widget child;
final double ratio;
final Size contentsSize;

const VideoContainer({
super.key,
required this.child,
required this.ratio,
required this.contentsSize,
});

factory VideoContainer.fromController(VideoPlayerController controller) =>
VideoContainer(
ratio: controller.value.aspectRatio,
contentsSize: controller.value.size,
child: VideoPlayer(controller),
);

@override
Widget build(BuildContext context) {
return FittedBox(
fit: BoxFit.cover,
child: AspectRatio(
aspectRatio: ratio,
child: child,
),
);
}
}


Оцените новую рубрику FlutterPulseTips 👍💬
Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #mobiledev #appdev #codingtips #uiux #videostreaming #fullscreenvideo
👍1
Запуск анимации при изменении свойства
Привет, подписчики! 👋 Сегодня мы рассмотрим интересный вопрос: как запустить анимацию каждый раз, когда меняется определенное свойство? 🤔

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

Для этого мы можем использовать метод didUpdateWidget в StatefulWidget. Этот метод вызывается каждый раз, когда виджет обновляется. 🔄

Пример кода:


@override
void didUpdateWidget(covariant UploadedAvatarAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
final (wasUploading, isUploading) = (oldWidget.isUploading, widget.isUploading);
switch ((wasUploading, isUploading)) {
case (false, true):
_controller.forward(from: 0);
_initScaleAnim(0, pt);
case (true, false):
_controller.reverse(from: 1);
default:
}
}



В этом примере мы проверяем, изменилось ли свойство isUploading, и запускаем анимацию соответствующим образом. 🔮

Как это работает?
1. Мы используем метод didUpdateWidget, чтобы отслеживать изменения виджета.
2. Мы сравниваем старое и новое значения свойства isUploading.
3. В зависимости от изменения, мы запускаем анимацию вперед или назад.

Оцените новую рубрику и напишите в комментариях, что вы думаете! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips 👍
#flutter #dart #flutterpulse #FlutterPulseTips #mobiledev #appdev #animation #ui #ux #codingtips #programming #softwaredevelopment
👍4
Добавьте кастомные переходы страниц с GoRouter

Переходы между страницами важны для пользовательского опыта

Добавьте этот небольшой помощник, чтобы определить кастомный переход страницы:


Page<dynamic> Function(BuildContext, GoRouterState) buildPageTransition(
Widget child,
) => (BuildContext context, GoRouterState state) {
return CustomTransitionPage(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
FadeThroughTransition( // можно создать свой собственный переход или использовать из пакета animations на pub.dev
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
),
);
};


Теперь оберните свою страницу в переход, используя pageBuilder для вашего маршрута:


GoRoute(
path: '/signin',
builder: (context, state) => const SigninPage(),
pageBuilder: buildPageTransition(const SigninPage()),
),


Вы также можете настроить стандартный переход страницы прямо в вашей теме:


pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: const ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: const CupertinoPageTransitionsBuilder(),
},
),


Оцените нашу новую рубрику! 👍💡
Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #UIUX #FlutterTips
👍5
Принудительный портретный режим
Когда вы в последний раз использовали приложение в ландшафтном режиме? 🤔

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


SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp, // Портретная ориентация вверх
DeviceOrientation.portraitDown, // Портретная ориентация вниз
]);


Теперь приложение не сможет перейти в ландшафтный режим 📱💻

Результат:
Приложение останется в портретном режиме 👍

Оцените нашу новую рубрику FlutterPulseTips! 👍 Ваши отзывы помогут нам улучшать контент. 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #ProgrammingTips #UIUX #DevTips
👍3
Загрузка байтов изображения и отображение на холсте
Не так сложно сделать с помощью Flutter 😉

Вы когда-нибудь задумывались, как можно загрузить изображение и отобразить его на холсте в Flutter? 🤔 Давайте разберемся в этом вместе! 💡

Шаг 1: Загрузка изображения
Сначала нам нужно загрузить изображение из наших ресурсов. Для этого мы используем метод load класса rootBundle:


import 'dart:ui' as ui;
import 'package:flutter/services.dart';

// Загружаем изображение из ресурсов
final ByteData data = await rootBundle.load(path);
// Преобразуем в Uint8List
final Uint8List bytes = data.buffer.asUint8List();
// Преобразуем в ui.Image
final ui.Image image = await decodeImageFromList(bytes);



Шаг 2: Отображение на холсте
Теперь, когда у нас есть изображение в формате ui.Image, мы можем отобразить его на холсте. Для этого создадим собственный класс CustomPainter:


// Создаем новый CustomPainter
class MyPainter extends CustomPainter {
final ui.Image image;

MyPainter(this.image);

@override
void paint(Canvas canvas, Size size) {
// Рисуем изображение на холсте
canvas.drawImageRect(image, srcRect, dstRect, Paint());
}
}



Вот и все! Теперь вы знаете, как загрузить байты изображения и отобразить их на холсте во Flutter. Просто и эффективно, не правда ли? 😊

Оцените нашу новую рубрику и оставьте свои комментарии! 👇

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #mobiledev #appdev #codingtips #FlutterTips
👍3🔥1
Хранение настроек пользователя

Используйте плагин shared preferences только для некритичных данных!

Почему не стоит хранить всё в SharedPreferences:
• Shared preferences предназначены для хранения простых данных в формате ключ-значение.
• Они не подходят для хранения сложных структур данных.
• Они не оптимизированы для высокопроизводительных операций чтения/записи.
• Не храните конфиденциальные данные, так как нет гарантии, что запись будет сохранена на диске (иногда всё может быть потеряно).


// getInstance возвращает Future
// Хорошей практикой является инициализация в начале работы приложения,
// а затем использование уже инициализированного экземпляра
final SharedPreferences prefs = await SharedPreferences.getInstance();


Если вы используете Provider или Riverpod, рассмотрите возможность сделать следующее:
вызовите метод init перед отображением чего-либо и используйте SharedPreferencesBuilder
для доступа к sharedPreferences.


import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';

final sharedPreferencesProvider = Provider<SharedPreferencesBuilder>((ref) => SharedPreferencesBuilder(),
);

class SharedPreferencesBuilder implements OnStartService {
SharedPreferences? _sharedPreferences;

@override
Future<void> init() async {
if (_sharedPreferences != null) {
return;
}
_sharedPreferences = await SharedPreferences.getInstance();
}

SharedPreferences get prefs {
if (_sharedPreferences == null) {
throw Exception('SharedPreferences not loaded');
}
return _sharedPreferences!;
}
}


Prefer SharedPreferencesAsync or SharedPreferencesWithCache,
так как SharedPreferences будет объявлен устаревшим в будущих обновлениях.

Оцените новую рубрику и напишите своё мнение! 🤔💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #ProgrammingTips #DevTips
👍3