← Назад к вопросам

Как получить положение объекта на экране?

2.0 Middle🔥 111 комментариев
#Flutter виджеты#Анимации

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как получить положение объекта на экране?

Получение позиции виджета на экране — важная задача для реализации анимаций, drag-and-drop, overlay и других интерактивных элементов.

Способ 1: GlobalKey и RenderBox

Самый распространённый и надёжный способ:

import 'package:flutter/material.dart';

class PositionExample extends StatefulWidget {
  @override
  State<PositionExample> createState() => _PositionExampleState();
}

class _PositionExampleState extends State<PositionExample> {
  final GlobalKey<State> _widgetKey = GlobalKey();
  
  Offset? _widgetPosition;
  Size? _widgetSize;
  
  void _getPosition() {
    final RenderBox renderBox =
        _widgetKey.currentContext!.findRenderObject() as RenderBox;
    final offset = renderBox.localToGlobal(Offset.zero);
    final size = renderBox.size;
    
    setState(() {
      _widgetPosition = offset;
      _widgetSize = size;
    });
    
    print('Position: ${offset.dx}, ${offset.dy}');
    print('Size: ${size.width}x${size.height}');
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Get Position')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              key: _widgetKey,
              width: 200,
              height: 100,
              color: Colors.blue,
              child: Center(child: Text('Touch me')),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _getPosition,
              child: Text('Get Position'),
            ),
            if (_widgetPosition != null) ...
              [
                SizedBox(height: 20),
                Text('X: ${_widgetPosition!.dx.toStringAsFixed(2)}'),
                Text('Y: ${_widgetPosition!.dy.toStringAsFixed(2)}'),
                Text('Width: ${_widgetSize!.width.toStringAsFixed(2)}'),
                Text('Height: ${_widgetSize!.height.toStringAsFixed(2)}'),
              ]
          ],
        ),
      ),
    );
  }
}

Способ 2: GestureDetector для touch позиции

Получение позиции клика/touch:

class TouchPositionExample extends StatefulWidget {
  @override
  State<TouchPositionExample> createState() => _TouchPositionExampleState();
}

class _TouchPositionExampleState extends State<TouchPositionExample> {
  Offset? _touchPosition;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Touch Position')),
      body: GestureDetector(
        onTapDown: (TapDownDetails details) {
          // Позиция относительно экрана
          final globalPosition = details.globalPosition;
          // Позиция относительно виджета
          final localPosition = details.localPosition;
          
          setState(() {
            _touchPosition = globalPosition;
          });
          
          print('Global: ${globalPosition.dx}, ${globalPosition.dy}');
          print('Local: ${localPosition.dx}, ${localPosition.dy}');
        },
        child: Container(
          color: Colors.grey[300],
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Tap anywhere'),
                if (_touchPosition != null) ...
                  [
                    SizedBox(height: 20),
                    Text('X: ${_touchPosition!.dx.toStringAsFixed(0)}'),
                    Text('Y: ${_touchPosition!.dy.toStringAsFixed(0)}'),
                  ]
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Способ 3: Mouse position на web/desktop

class MousePositionExample extends StatefulWidget {
  @override
  State<MousePositionExample> createState() => _MousePositionExampleState();
}

class _MousePositionExampleState extends State<MousePositionExample> {
  Offset? _mousePosition;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: MouseRegion(
        onHover: (PointerHoverEvent event) {
          setState(() {
            _mousePosition = event.position;  // Глобальная позиция
          });
        },
        child: Container(
          color: Colors.grey[300],
          child: Center(
            child: _mousePosition == null
                ? Text('Move mouse')
                : Text(
                    'Mouse: ${_mousePosition!.dx.toStringAsFixed(0)}, '
                    '${_mousePosition!.dy.toStringAsFixed(0)}'
                  ),
          ),
        ),
      ),
    );
  }
}

Способ 4: Получение позиции элемента в списке

class ListItemPositionExample extends StatefulWidget {
  @override
  State<ListItemPositionExample> createState() =>
      _ListItemPositionExampleState();
}

class _ListItemPositionExampleState extends State<ListItemPositionExample> {
  final itemKeys = List.generate(10, (_) => GlobalKey());
  
  void _getListItemPosition(int index) {
    final RenderBox renderBox =
        itemKeys[index].currentContext!.findRenderObject() as RenderBox;
    final offset = renderBox.localToGlobal(Offset.zero);
    
    print('Item $index position: ${offset.dx}, ${offset.dy}');
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('List Item Position')),
      body: ListView.builder(
        itemCount: 10,
        itemBuilder: (context, index) {
          return GestureDetector(
            onTap: () => _getListItemPosition(index),
            child: Container(
              key: itemKeys[index],
              padding: EdgeInsets.all(16),
              color: Colors.blue[100 * ((index % 4) + 1)],
              child: Text('Item $index - Tap to get position'),
            ),
          );
        },
      ),
    );
  }
}

Способ 5: Получение относительной позиции (localToGlobal)

class RelativePositionExample extends StatefulWidget {
  @override
  State<RelativePositionExample> createState() =>
      _RelativePositionExampleState();
}

class _RelativePositionExampleState extends State<RelativePositionExample> {
  final GlobalKey _containerKey = GlobalKey();
  final GlobalKey _childKey = GlobalKey();
  
  void _calculateRelativePosition() {
    final RenderBox containerRender =
        _containerKey.currentContext!.findRenderObject() as RenderBox;
    final RenderBox childRender =
        _childKey.currentContext!.findRenderObject() as RenderBox;
    
    // Глобальная позиция контейнера
    final containerGlobalPosition =
        containerRender.localToGlobal(Offset.zero);
    
    // Глобальная позиция дочернего элемента
    final childGlobalPosition = childRender.localToGlobal(Offset.zero);
    
    // Позиция дочернего элемента относительно контейнера
    final relativePosition = childGlobalPosition - containerGlobalPosition;
    
    print('Child position relative to container: '
        '${relativePosition.dx}, ${relativePosition.dy}');
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          key: _containerKey,
          width: 300,
          height: 300,
          color: Colors.grey[300],
          padding: EdgeInsets.all(20),
          child: Stack(
            children: [
              Positioned(
                left: 50,
                top: 100,
                child: Container(
                  key: _childKey,
                  width: 100,
                  height: 100,
                  color: Colors.red,
                ),
              ),
              Positioned(
                bottom: 10,
                left: 10,
                child: ElevatedButton(
                  onPressed: _calculateRelativePosition,
                  child: Text('Calculate Position'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Способ 6: Для анимаций (Animate from top-left corner)

class AnimatedPositionExample extends StatefulWidget {
  @override
  State<AnimatedPositionExample> createState() =>
      _AnimatedPositionExampleState();
}

class _AnimatedPositionExampleState extends State<AnimatedPositionExample> {
  final GlobalKey _widgetKey = GlobalKey();
  Offset? _startPosition;
  late AnimationController _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _capturePosition();
    });
  }
  
  void _capturePosition() {
    final RenderBox renderBox =
        _widgetKey.currentContext!.findRenderObject() as RenderBox;
    setState(() {
      _startPosition = renderBox.localToGlobal(Offset.zero);
    });
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              key: _widgetKey,
              width: 100,
              height: 100,
              color: Colors.blue,
              child: Center(child: Text('Animate')),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => _controller.forward(from: 0.0),
              child: Text('Start Animation'),
            ),
            if (_startPosition != null)
              Text('Start: ${_startPosition!.dx}, ${_startPosition!.dy}'),
          ],
        ),
      ),
    );
  }
}

Практическая таблица методов

Сценарий                        | Метод
--------------------------------|-------------------------------------------
Позиция виджета на экране      | RenderBox.localToGlobal(Offset.zero)
Позиция клика/касания          | TapDownDetails.globalPosition
Позиция в координатах виджета  | TapDownDetails.localPosition
Позиция мыши (web/desktop)    | PointerHoverEvent.position
Размер виджета                 | RenderBox.size
Высота элемента в списке       | renderBox.localToGlobal() for each item
Относительная позиция          | childPosition - parentPosition

Лучшие практики

// ✅ Используйте Post-frame callback для инициализации
WidgetsBinding.instance.addPostFrameCallback((_) {
  _getPosition();  // После первого кадра
});

// ✅ Кэшируйте позиции если часто используются
late Offset _cachedPosition;

// ❌ Не вызывайте во время сборки
@override
Widget build(BuildContext context) {
  // _getPosition();  // ❌ Ошибка!
}

// ✅ Для сложных случаев создавайте helper функции
RenderBox? _getRenderBox(GlobalKey key) {
  try {
    return key.currentContext?.findRenderObject() as RenderBox?;
  } catch (e) {
    return null;
  }
}

Получение позиции виджета — мощный инструмент для создания интерактивных интерфейсов.