Реализация совместного рисования в реальном времени в мобильном приложении

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Реализация совместного рисования в реальном времени в мобильном приложении
Сложный
от 1 недели до 3 месяцев
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Реализация совместного рисования в реальном времени в мобильном приложении

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

Разделение local и remote stroke

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

// Flutter: локальный штрих
class DrawingBloc extends Bloc<DrawingEvent, DrawingState> {
  void onPointerDown(PointerDownEvent e) {
    currentStroke = Stroke(id: uuid(), points: [e.localPosition]);
    emit(state.copyWith(activeStroke: currentStroke));
    _syncService.beginStroke(currentStroke.id, color, brushSize);
  }

  void onPointerMove(PointerMoveEvent e) {
    currentStroke.points.add(e.localPosition);
    emit(state.copyWith(activeStroke: currentStroke));
    _syncService.appendPoints(currentStroke.id, [e.localPosition]);
  }

  void onPointerUp(PointerUpEvent e) {
    _syncService.finalizeStroke(currentStroke.id);
  }
}

Синхронизация идёт параллельно — локальный рендер не ждёт сети.

Транспорт: батчинг точек

60fps на мобиле = 60 pointerMove событий в секунду. При RTT 100ms батч за 100ms = ~10 точек. Отправляем batch каждые 50–80ms — баланс между задержкой и трафиком.

Формат сообщения (бинарный, а не JSON — экономия в 3–5x по размеру):

[strokeId: 16 bytes UUID][pointCount: uint8][x1:f32][y1:f32][p1:f16][x2:f32]...

Float16 для давления (pressure) — 0.0–1.0 с точностью 0.001 достаточно. Float32 для координат (субпиксельная точность нужна для масштабирования). Итого ~10 байт на точку против ~30 байт в JSON.

На WebSocket: бинарные frames (ArrayBuffer в JS, Uint8List в Dart, ByteBuffer в Kotlin).

Алгоритм сглаживания на стороне получателя

Удалённые точки приходят батчами с задержкой и дискретно. Простая отрисовка линий между точками — ступенчато. Нужно сглаживание:

Catmull-Rom Spline — проходит через все контрольные точки. Для каждой пары соседних точек генерирует промежуточные. Подходит для рисования, не требует offline-вычислений.

Perfect Freehand (библиотека Steve Ruiz) — симулирует форму кисти с учётом давления и скорости, генерирует SVG-path из точек. Работает в Dart, JS, Swift. Результат — органичная кривая, а не просто линия с round cap.

Для удалённого stroke: применяем Perfect Freehand ко всему накопленному массиву точек при каждом обновлении. Path перерисовывается полностью. Это дороже по CPU, чем инкрементальное добавление, но визуально правильнее (сглаживание учитывает весь контекст пути).

Слои и порядок объектов

Рисование без слоёв — примитивно. Базовая модель: каждый stroke имеет z-index (timestamp создания). При конкурентном рисовании в одной области — кто нарисовал последним, тот сверху.

Слои (layers) — опциональная фича. Каждый слой — отдельный Y.Array объектов. Пользователь выбирает активный слой. Видимость/блокировка слоя — поле в Y.Map слоя.

При рендеринге: Canvas рисует слои снизу вверх. Каждый слой — отдельный offscreen canvas (iOS: UIGraphicsImageRenderer, Android: Bitmap с Canvas). Слои кешируются и перерисовываются только при изменении.

Ластик: специальный инструмент

Ластик не рисует белым — он удаляет пиксели. Два варианта:

  1. Object-level eraser — удаляет весь stroke при пересечении. Просто в реализации, соответствует модели объектов.
  2. Pixel-level eraser — разрезает stroke на части. Требует геометрических вычислений (clip polygon by path).

Для collaborative: object-level eraser проще синхронизировать (delete(strokeId) — атомарная операция). Pixel-level — нужно разрезать stroke и создать новые объекты, что сложнее в CRDT-контексте.

Apple Pencil и Android Stylus

Apple Pencil через UITouch.type == .pencil:

  • force — давление 0.0–1.0
  • altitudeAngle — угол наклона (0 = горизонтально, π/2 = вертикально)
  • azimuthAngle(in:) — направление наклона
  • predictedTouches(for:) — предсказанные будущие точки

Flutter: PointerEvent.pressure, PointerEvent.tilt, PointerEvent.orientation — работают для стилуса на обеих платформах.

Синхронизировать pressure/tilt на удалённые клиенты — стоит. Результат виден: кисть партнёра отображается так же, как он рисовал, с той же динамикой.

Оценка

Совместное рисование с базовыми инструментами (кисть, ластик, цвет) на Flutter — 8–14 недель. С поддержкой стилуса, слоями, pixel-level eraser и масштабируемым холстом — 20–28 недель.