По любви · Android × Figma · 10.09.2026

Видео, верификация, возраст, лента, свайп и мэтч сошлись с макетом

Десять экранов — от записи кружка до «Это по любви!» — собраны из данных нод: переменных, стилей, компонентов, дуг, матриц авто-лейаута и матриц обрезки картинок. Каждый кадр снят на устройстве в честном масштабе ×3 (1170×2532) и сведён с рендером Figma. Ниже — кадр рядом с рендером, четыре числа на кадр и разбор того, что оказалось не так. Это продолжение артефактов по кадрам S01–S10 и S11–S20.

75 / 75кадров с зелёной геометрией при пороге 0,3 %
0,279 %худшая геометрия
0,000 %лучшая геометрия
36расхождений найдено и закрыто
12вопросов к самому макету — дизайнеру

Смотровая

Кадр рядом с рендером

Тяните шторку, чтобы совместить макет и приложение. «Карта» показывает, где остаётся расхождение по геометрии: синим закрыты системный статус-бар, полоска home, клавиатура и диалоги разрешений iOS — их рисует Android, они в сравнении не участвуют (спека 6.4).

Рендер кадра из Figma Снимок экрана приложения

Числа

Четыре измерения на кадр

Полное — доля пикселей, отличающихся сильнее ΔE 6 вне масок. Без текста — то же с исключёнными текстовыми блоками. ±1 px — пиксель считается расхождением, только если не совпал ни с одним пикселем рендера в окне ±1: сдвиг растра на долю пикселя не есть ошибка вёрстки. Геометрия — два последних вместе, то есть чистая форма, положение и материал; по ней и выносится вердикт. Шкала показывает геометрию относительно порога 0,3 %.

ЭкранКадрСостояние Полное %Без текста % ±1 px %Геометрия % Шкала до порога 0,3 %

Разбор

Что оказалось не так

Каждая строка — расхождение из реестра docs/figma/fig/ledger.md: что показала нода, что делало приложение и сколько это стоило в числах.

Кольцо записи — дуга, а не окружность

Прогресс записи кружка рисовался полным кольцом. В ноде это дуга: у эллипса 3463:9249 есть arcData с началом 0 и концом 2,29646 рад (131,58°) на одном кадре и 0,41818 рад (23,96°) на другом, а сам эллипс повёрнут на −90°. Согласие с рендером проверено по самому рендеру: дуги там ровно 0…132° и 0…24°.

дуга2162:20378 и ещё 4 кадра
0…131,58° и 0…23,96°

Фотография шире рамки — Modifier.size зажимается родителем

На кадре «7 checking photos» фотография 602,8 dp лежит в рамке 338 со сдвигом −216,76: в макете видна её правая треть. Compose зажимает Modifier.size шириной родителя, и от снимка оставалась середина — 60,8 % кадра красным. Лечится не «требуемым размером», а пересчётом: из матрицы обрезки считается общий кроп источника.

−60 п.п.кадр 4547:14500
60,8 % → 0,3 %

Пересчёт растра: фильтр выбирается замером, а не по вкусу

Исходники в 4096 px Skia уменьшает не так, как Figma. Картинка заранее приводится к точному размеру ×3, а фильтр пересчёта подобран измерением по рендеру: на уменьшение HAMMING, на увеличение BILINEAR. На кадре «Верификация 3» это сняло 16,88 % пикселей с |Δ|>30 до 1,19 %.

−94 %кадр 3620:25708
16,88 % → 1,19 %

Пробел в конце строки и невидимый разделитель

Figma при выравнивании по центру не учитывает пробел в конце строки, Compose учитывает — заголовок уезжал на 4,67 dp. А в одном тексте перенос строки задан символом U+2028: Compose рисует его отдельным глифом-«тофу» и строку не переносит — блок выходил на 35 dp шире. Оба случая закрыты одной нормализацией строки из ноды.

−35 dpкадр 7761:25204
1,50 % → 0,90 %

Цветокоррекция заливки — это шейдер Figma

У фотографии селфи в заливке стоят filters: exposure −0,05, contrast +0,09, highlights −0,37. Повторить формулу нечем, поэтому подбирается поканальная линейная поправка по самому рендеру устойчивым методом — наложенные сверху элементы из выборки выбрасываются. Остаток пишется числом, а не словом «похоже».

|Δ| 16,7 → 2,0кадр 3620:25849
7,22 % → 0,57 %

Одно число — два источника: счётчик лайков

В шапке ленты число лайков лежит текстом, который в дереве стоит до navbar-custom и закрыт его белой заливкой. Видимое число даёт барабан counter-reel — тот его .num-box, что стоит на одной высоте с самим барабаном. Читать надо барабан, а текст в кадре не виден вовсе.

«7» → «12»кадр 4680:23282
видимое число из барабана

Точки пейджера — кольцо и шеврон, а не кружки

Индикатор фотографий рисовался кружками. В ките у активной точки контур «O» 4926:17524 10×8, у остальных — шеврон «Union» 4926:17526 8×8 белым с альфой 0,5. Правка общая для всей ленты: после неё пересняты все 26 замеров экрана карточки, и геометрия у каждого стала не хуже прежней.

26 кадров4680:23282 и вся лента
0,197 % → 0,155 %

Слой шире экрана Compose молча ужимает

Оверлей лайка — квадрат 635×635 dp в кадре 390. С Modifier.requiredSize он рисовался 635-м только на бумаге: на снимке выходил 506 dp и вставал не туда. Проверено отдельным кадром — рамка 200×200 укладывается точно, большая нет. Всё, что выходит за кадр, кладётся drawImage в заданный прямоугольник.

−49 п.п.кадр 7047:31149
49,4 % → 0,05 %

Вложенные слои сдвигают содержимое на половину разницы размеров

Задняя карточка стопки рисовалась двумя слоями: прозрачность на родителе 339×581, масштаб на ребёнке 366×627. Ребёнок больше родителя — и содержимое уехало ровно на (627 − 581) / 2 = 22,9 dp вверх. Один слой на элемент: масштаб, прозрачность и точка отсчёта вместе.

−27 п.п.кадр 6046:33444
34,4 % → 7,2 %

Повёрнутый ребёнок в авто-лейауте занимает повёрнутый габарит

Карточка 366×627 под углом 10,42° занимает в колонке 682,87 — по своему повёрнутому габариту, а не по своей высоте. Поэтому описание под ней стоит на 604,87, а не на 549. Без этого всё описание уезжало на 55,87 dp.

−2,1 п.п.кадр 6502:21576
2,97 % → 0,86 %

Фон мэтча — эффект, а не форма

Фон экрана «Это по любви!» — три эллипса с размытием слоя 243,3. Compose повторяет такое размытие другим ядром: подгонка по сигме давала |Δ| около 20 единиц канала на всём кадре. Фон положен растром ×3 из рендера — тем же классом, что оверлеи свайпа, где размытие тоже не выражается вектором.

−57 п.п.кадр 6139:30556
57,3 % → 0,28 %

Тёмная шапка карточки, которой нет на ленте

У верхней карточки кадров свайпа поверх фотографии лежит gradient up — 366×80 тёмного с альфой 0,5 → 0,25 → 0,06 → 0. Этого слоя нет ни на одном кадре ленты при той же карточке, и мы его не рисовали: верх карточки выходил светлее рендера в 1,7 раза. Слой нашёлся не глазами: разбор красных пикселей по полосам показал, что три четверти расхождения кадра лежат в верхних 60 dp карточки, а подгонка цвета там дала множитель 1,0 — значит дело не в цвете фотографии, а в отсутствующем слое.

−0,74 п.п.кадр 6046:33047
0,85 % → 0,11 %

Режим наложения стоит у слоя, а не у заливки

У нижнего градиента карточки blendMode заливки — NORMAL, а у самого узла — MULTIPLY. Читали заливку. Проверено там, где ответ известен точно: фикстура фотографии совпадает с экраном пиксель в пиксель, и почти чёрный низ карточки (13…24 единицы) в рендере уходит в 3, а обычным наложением получалось 24. Правка общая для всей ленты.

−0,20 п.п.кадр 6046:33444
0,37 % → 0,17 %

Ширина внутри повёрнутой карточки — не ширина блока

absoluteBoundingBox ребёнка повёрнутой карточки — это габарит уже повёрнутой рамки. Имя шириной 137 читалось как 139,08, возраст 29 как 32,86 — и значок проверки, стоящий в ряду после них, уезжал на 5,9 dp. Ровно столько и намерил центр тяжести терракоты значка на рендере против снимка: 5,87. Сторона решается из габарита — и только для узлов внутри самой повёрнутой карточки: колонка описания под ней лежит ровно.

−0,21 п.п.кадр 6014:24550
0,32 % → 0,10 %

Сплошной тон вместо размытия — только над ровным фоном

Фонового размытия чипов эмулятор не рисует, поэтому тон чипа снимается медианой с рендера. Там, где под чипом фон резко меняется — «Йога» лежит наполовину на тёмной шторе, наполовину на светлой руке, — медиана мажет половину чипа. Мерой служит среднее отклонение внутри самого чипа на рендере: выше 11 единиц берётся заливка ноды над фотографией, ниже — снятый тон. Порог выбран прогоном обеих веток по всем кадрам, а не на глаз.

−0,11 п.п.кадр 13353:32000
0,20 % → 0,09 %

Мера

Как мы проверяем текст

Форма закрывается числом «геометрия». Текст закрывается отдельно: габарит нарисованных глифов из ноды и плотность чернил в блоке. В этой партии выяснилось, что вторая мера годится не везде.

Над фотографией «чернила» мерили саму фотографию

Плотность чернил считалась как «255 − яркость» по прямоугольнику блока. Для тёмного текста на светлой бумаге это верно, а у белого текста поверх фото сумма считает фото. Контроль: те же рамки, сдвинутые с текста на соседний участок фотографии, дали ту же недостачу — −3,80 % против −3,78 %. Теперь замер печатает обе меры — по яркости и по покрытию цветом заливки, — сам сравнивает подложку вокруг блоков и говорит, по какой мере ставить вердикт.

−4,1 → +0,3 %кадр 5152:19003
мера сменилась, вёрстка нет

Измеритель проверяется на эталоне

Строки, где методика не сошлась с самим рендером, помечаются и в вердикт не идут: окно поиска чернил захватывает соседний текст того же цвета, и «сдвиг» получается у пары «56» и «%», стоящих в четырёх dp друг от друга. Это правило спеки К-17 — сначала проверить прибор, потом чинить вёрстку.

3 мерыяркость · покрытие · подложка
вердикт называет свою

Вопросы

Что стоит посмотреть в макете

Здесь не ошибки вёрстки, а места, где макет спорит сам с собой или с собственным рендером. Мы делаем как в рендере и передаём пару дизайнеру.

ЧтоГдеКак сейчас
Заголовок панелиI3041:13063;4300:16385 лежит в контейнере 375, а рендер центрирует по кадру 390 — сдвиг +7,33 dp
Крестик на экране мэтчаI6139:30944;2858:23480 в ноде x=337, в рендере 352 — тот же случай, что с заголовком
Счётчик «О себе»3258:25396 показывает «400 / 2000» при тексте в 96 знаков
Число лайков4680:23432 и 5411:10557 текст под шапкой и барабан рядом — два источника одного числа
Ширина «match-rate»11508:24391 против 5568:34637 87 при «56 %» и 85 при «72 %» — строка чипов от этого едет
Градиент внутри повёрнутой карточки6119:26783 контр-повёрнут на −3° и остаётся горизонтальным, остальные слои повёрнуты
Фотография на экране мэтчаimageRef ffae4478… картинки нет в контейнере файла — Figma подгружает её извне
Кадр «2.1» верификации3615:21079 в реестре экрана не значится, хотя это его состояние под диалогом
Кадр V5 «4»16986:55192 числится за одним экраном, по содержимому — другой (дерево совпало поимённо)
Кадр V5 «1»16970:46651 значится сразу за двумя экранами
Тёмная шапка карточки6046:33121 и ещё четыре есть на кадрах свайпа и нет ни на одном кадре ленты при той же карточке
Шапка кадра «5 like»5411:8370 без заливки вовсе — сквозь неё видно сердце лайка; на прочих кадрах белая

Границы

Что не закрыто

Форма зелёная — это половина работы. Вот честный остаток.

ЧтоПочемуЧто нужно
Плотность чернил на четырёх кадрах у 6014:24550, 6502:21576, 7047:32037 и 7047:32446 мера чернил даёт −3,9…+5,8 % при пределе 2 %: форма у них закрыта, расходится сам текст разобрать метрики шрифта на кадрах анимации — кегли там дробные (17,84 и 22,69)
Поведение экрановтрасса запросов против карт Flutter не снята файловый лог запросов в debug-сборке и инструмент разбора
Лента и мэтч в приложенииновые экраны собраны и снимаются фикстурой, но в навигацию не включенызаменить прежние FeedScreen и MatchOverlay и переснять кадры
Сам жест свайпакадры — это ключи анимации, а не её воспроизведение стек карточек, перетаскивание, вылет сердца и возврат анкеты
Плотность чернил на семнадцати старых кадрах по своей мере они выходят за 2 % (−2,08…−3,72 %) разобрать текст этих экранов отдельно; форма у них закрыта