Реалізація спільного малювання в реальному часі у мобільному додатку

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 batch за 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 тижнів.