Распознавание виджетов на экране приложения Flutter

от автора

Hola, Amigos! На связи Саша Чаплыгин, Flutter-dev агентства продуктовой разработки Amiga и соавтор телеграм-канала Flutter. Много. Сегодня мы вновь займемся практикой! Расскажу об интересной теме — определение положения объекта на экране. Это может быть полезно, когда мы хотим понять, виден тот или иной виджет на экране в данный момент или нет.

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

Как же это сделать?

Для начала создадим кастомный виджет уведомления. Для определения объекта на экране используем пакет visibility_detector. При применении данного пакета  обязательно используйте UniqueKey, так как данный пакет включает в себя RenderObject.

import 'package:flutter/material.dart'; import 'package:hmelbakery/core/style/colors.dart'; import 'package:visibility_detector/visibility_detector.dart';   class NotificationChip extends StatefulWidget {  NotificationChip({    required this.index,    required this.visibleIndex,    super.key,    this.title = '',    this.text = '',    this.subtitle = '',    this.status = true,  });    final String title;  final int index;  final ValueChanged<int> visibleIndex;  final String text;  final String subtitle;  final bool status;    @override  State<NotificationChip> createState() => _NotificationChipState(); }   class _NotificationChipState extends State<NotificationChip> {  final UniqueKey key = UniqueKey();  late bool _visible;    @override  void initState() {    _visible = widget.status;    super.initState();  }    @override  Widget build(BuildContext context) {    return VisibilityDetector(      onVisibilityChanged: !_visible          ? (visibilityInfo) {              // Здесь определяется на сколько виден объект в процентах              var visiblePercentage = visibilityInfo.visibleFraction * 100;              if (visiblePercentage == 100.0) {                setState(() {                  _visible = true;                });                widget.visibleIndex.call(widget.index); // прочитали              }            }          : null,      key: key,      child: Container(        decoration: BoxDecoration(color: AppColors.grey, borderRadius: BorderRadius.circular(16)),        child: Padding(          padding: const EdgeInsets.all(16),          child: Column(            mainAxisAlignment: MainAxisAlignment.start,            crossAxisAlignment: CrossAxisAlignment.start,            children: [              Row(                mainAxisAlignment: MainAxisAlignment.spaceBetween,                children: [                  Text(                    widget.title,                    style: Theme.of(context).textTheme.titleLarge,                  ),                  if (!_visible)                    Container(                      width: 16,                      height: 16,                      decoration: const BoxDecoration(                        color: AppColors.yellow,                        shape: BoxShape.circle,                      ),                    ),                ],              ),              const SizedBox(height: 8),              Text(widget.text, style: Theme.of(context).textTheme.bodyMedium),              const SizedBox(height: 16),              Text(                widget.subtitle,                style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: AppColors.black400),              ),            ],          ),        ),      ),    );  } }

Отображаем список уведомлений на экране.

ListView.builder(    padding: const EdgeInsets.symmetric(horizontal: 16),    itemBuilder: (BuildContext context, int index) {      if (index == state.pageState.data.length - 1 && !state.pageState.loadNewPage) {        context.read<ProfileNotificationsBloc>().add(ProfileNotificationsFetchDataEvent());      }      return Column(        children: [          NotificationChip(            title: state.pageState.data[index].title,            text: state.pageState.data[index].text,            subtitle: DateConverter.formattingDateWTime(state.pageState.data[index].datetime),            status: !(state.pageState.data[index].statusCode == 'unread'),            index: index,            visibleIndex: (int value) {              context                  .read<ProfileNotificationsBloc>()                  .add(ProfileNotificationsMarkReadEvent(index: value)); // передаем в блок индекс прочитаного             },          ),          const SizedBox(height: 8),          if (index == state.pageState.data.length - 1 && state.pageState.loadNewPage) ...[            const Center(child: CircularProgressIndicator(color: AppColors.black)),            const SizedBox(height: 20),          ],        ],      );    },    itemCount: state.pageState.data.length,  ),

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

markRead(ProfileNotificationsMarkReadEvent event, emit) async {  if (!state.pageState.readIndexes.contains(event.index)) {    emit(ProfileNotificationsUp(state.pageState.copyWith(      readIndexes: [...state.pageState.readIndexes, event.index],      data: state.pageState.data..[event.index] = state.pageState.data[event.index].copyWith(statusCode: 'read'),    )));  } }

Далее нужно решить задачу, как правильно отправить на сервер информацию? Не будем же мы каждый прочитанный индекс отправлять, верно?

Timer? _timer; List<int> _tempList = [];   _timerFunc() { // старт функции в блоке  _timer = Timer.periodic(    const Duration(seconds: 1),    (timer) {      if (_tempList.length < state.pageState.readIndexes.length) {        _tempList = state.pageState.readIndexes;        notificationsRepository.markRead(          request: MarkReadNotificationRequest(            notifications: state.pageState.data                .sublist(_tempList.first, _tempList.length > 1 ? _tempList.last : _tempList.first + 1)                .map((e) => e.id)                .toList(),          ),        );      }    },  ); }

Создаем таймер, который каждую секунду проверяет изменился ли список прочитанных уведомлений, и тогда отправляем на сервер. Конечно же мы не будем заставлять пользователя ждать ответа и блокировать экран загрузкой, поэтому не используем async/await. 

Всё готово! Надеюсь, вам будет полезно. Делитесь в нашем чате мобильных разработчиков о своем опыте применения пакета visibility_detector. 

А также всегда ждем вас в нашем телеграм-канале Flutter. Много, который мы ведем командой мобильных разработчиков. Рассказываем про свой личный опыт и делимся советами от софт-скиллов до технических знаний. Присоединяйтесь!


ссылка на оригинал статьи https://habr.com/ru/articles/804729/


Комментарии

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

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