Как создать кастомную View для сообщения в чате с треугольником-хвостиком
Что проверяют
Понимаете ли вы, как View измеряется и рисуется, и умеете ли выбрать самый простой инструмент под задачу. Следом спрашивают, как разместить внутри текст с переносами, как отзеркалить хвостик для входящих сообщений и как не тормозить в длинном RecyclerView.
Как отвечать
Сначала назовите варианты от простого к сложному: 9-patch картинка, MaterialShapeDrawable с ShapeAppearanceModel и своим EdgeTreatment, собственный Drawable и только потом наследник View. Пузырь это фон, поэтому чаще всего правильный ответ: свой Drawable или ViewGroup, внутри которого обычный TextView. Наследник голого View не умеет измерять и переносить текст.
Если пишете свой класс, покажите знание деталей:
- атрибуты:
declare-styleableвattrs.xml, чтение черезobtainStyledAttributesс обязательнымrecycle()или черезwithStyledAttributesиз core-ktx; - никаких аллокаций в
onDraw:PaintсANTI_ALIAS_FLAGиPathсоздаются один раз полями, иначе объекты рождаются на каждом кадре и провоцируют сборку мусора. Lint подсвечивает это какDrawAllocation; - геометрия в
onSizeChanged: там перестраиваетсяPathскруглённого прямоугольника (addRoundRect) с хвостиком изlineTo, вonDrawостаётся толькоdrawPath; - отступы: паддинг со стороны хвостика больше, чтобы текст не заезжал на треугольник;
- направление: для входящих и исходящих хвостик зеркалится, с учётом RTL через
layoutDirection; ViewGroup: по умолчанию не вызываетonDraw, нуженsetWillNotDraw(false)или отрисовка вdispatchDraw.
Если проект на Compose, всё сводится к своему Shape: GenericShape или реализация createOutline с Outline.Generic(path), плюс Modifier.background(color, shape).
Где спотыкаются
Рисуют пузырь в View и не переопределяют onMeasure. С wrap_content такой View занимает всё доступное место.
Создают Paint и Path прямо в onDraw. В ленте из сотен сообщений это заметные подвисания при прокрутке.
Хардкодят размеры в пикселях, а не переводят из dp. На разных плотностях экрана хвостик то крошечный, то огромный.