Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Hit Test в Flutter
Hit Test — это механизм определения, какой виджет или элемент интерфейса должен получить событие касания (tap), когда пользователь прикасается к экрану. Это критически важная часть архитектуры событий во Flutter.
Как работает Hit Test
Когда пользователь касается экрана в точке координат (x, y), Flutter запускает процесс hit testing:
- Начало с корня — процесс начинается с корневого виджета
- Обход дерева — Flutter рекурсивно проходит по дереву виджетов снизу вверх (bottom-up)
- Проверка границ — для каждого элемента проверяется, попадает ли точка касания в его границы
- Формирование цепи — создаётся цепь (hit path) элементов, которые были попаданы
- Обработка событий — события распространяются по цепи от самого глубокого виджета вверх
Пример: простой Hit Test
class MyButton extends StatelessWidget {
final VoidCallback onTap;
final String label;
const MyButton({required this.onTap, required this.label});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 100,
height: 50,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(label),
),
),
);
}
}
Когда пользователь нажимает на кнопку, GestureDetector выполняет hit test и определяет, что касание произошло в пределах его границ, после чего вызывает onTap.
Переопределение Hit Test
Иногда нужно кастомизировать поведение hit test. Это делается через метод hitTest в класс RenderObject:
class CustomButton extends LeafRenderObjectWidget {
final VoidCallback onTap;
final double size;
const CustomButton({
required this.onTap,
required this.size,
});
@override
RenderObject createRenderObject(BuildContext context) {
return RenderCustomButton(onTap: onTap, size: size);
}
}
class RenderCustomButton extends RenderBox {
final VoidCallback onTap;
final double size;
RenderCustomButton({
required this.onTap,
required this.size,
});
@override
void performLayout() {
size = Size(size, size);
}
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
// Кастомная логика: например, определяем круг вместо прямоугольника
final distance = (position - size.center(Offset.zero)).distance;
if (distance <= size.width / 2) {
result.add(BoxHitTestEntry(this, position));
return true;
}
return false;
}
@override
void paint(PaintingContext context, Offset offset) {
context.canvas.drawCircle(
offset + size.center(Offset.zero),
size.width / 2,
Paint()..color = Colors.blue,
);
}
}
Практические применения
1. Увеличение области касания
GestureDetector(
onTap: () {},
child: Padding(
padding: EdgeInsets.all(20), // Расширяем область для касания
child: Icon(Icons.close),
),
)
2. Прозрачные области, которые не реагируют на касание
IgnorePointer(
ignoring: true,
child: Container(
color: Colors.transparent,
child: Text("Это не кликабельно"),
),
)
3. Абсорбирование событий касания
AbsorbPointer(
absorbing: isDisabled,
child: Button(onTap: action),
)
Важные моменты
- Hit test — это отдельно от render. Размер для hit test может отличаться от визуального размера
- Порядок имеет значение. Виджеты, расположенные сверху, имеют приоритет при hit test
- Производительность. Глубокие деревья виджетов замедляют hit test
- Opacity не влияет на hit test. Полностью прозрачный виджет всё ещё может получать события (используй IgnorePointer)
Освоение hit test критически важно для создания правильной обработки пользовательского ввода во Flutter.