İBRAHİM
İBRAHİM

Reputation: 1

How do I make a widget stay within the screen borders in Flutter?

I'm designing a resizable and repositionable widget. I can keep this widget within the upper and left screen boundaries, but I have not yet managed to keep it within the right and lower screen boundaries. The widget was created with GestureDetector and defined by taking a position within the Stack structure. I'm trying update widget position in onPanUpdate method but right and bottom screen boundaries not working. How can I define the right and bottom frame boundaries and ensure that the widget stays within these boundaries? top_and_left_frame_part | bottom_and_right_frame_part

Here's my code blocks, first important issue :

,onPanUpdate: (details) {
            setState(() {
              if (!isResizing) {
                posx += details.delta.dx / 2;
                posy += details.delta.dy / 2;
                posx = posx.clamp(0, areaW);
                posy = posy.clamp(0, areaH);
                right = posx + width;
                if (right > areaW) {
                  right = areaW;
                }
              }

in this block, onPanUpdate methods working but 'if' statement and 'right = areaW' not working.

import 'dart:math';

import 'package:flutter/material.dart';

class Note2Widget extends StatefulWidget {
  const Note2Widget({super.key});

  @override
  State<Note2Widget> createState() => _Note2WidgetState();
}

class _Note2WidgetState extends State<Note2Widget> {
  double width = 100;
  double height = 100;
  double posx = 0;
  double posy = 0;
  late double areaW;
  late double areaH;
  bool isResizing = false;
  Offset startPosition = const Offset(0, 0);
  double right = 0;

  @override
  Widget build(BuildContext context) {
    areaW = MediaQuery.sizeOf(context).width;
    areaH = MediaQuery.sizeOf(context).height;
    return Positioned(
        top: posy,
        left: posx,
        width: areaW,
        height: areaH,
        child: GestureDetector(
          onPanStart: (details) {
            if (details.localPosition.dx >= width - 15 &&
                details.localPosition.dy >= height - 15) {
              setState(() {
                isResizing = true;
                startPosition = details.localPosition;
              });
            }
          },
          onPanUpdate: (details) {
            setState(() {
              if (!isResizing) {
                posx += details.delta.dx / 2;
                posy += details.delta.dy / 2;
                posx = posx.clamp(0, areaW);
                posy = posy.clamp(0, areaH);
                right = posx + width;
                if (right > areaW) {
                  right = areaW;
                }
              }
              if (isResizing) {
                double dx = details.localPosition.dx - startPosition.dx;
                double dy = details.localPosition.dy - startPosition.dy;
                width += dx;
                height += dy;
                startPosition = details.localPosition;
              }
              if (height < 50) {
                height = 50;
              }
              if (width < 50) {
                width = 50;
              }
            });
          },
          onPanEnd: (details) {
            setState(() {
              isResizing = false;
            });
          },
          child: Stack(children: [
            Positioned(
                top: posy,
                left: posx,
                width: width,
                height: height,
                child: Container(
                  decoration: BoxDecoration(
                      border: Border.all(width: 2, color: Colors.black)),
                  child: Center(
                      child: Padding(
                    padding: const EdgeInsets.all(8),
                    child: Container(
                      width: width - 10,
                      height: height - 10,
                      color: Colors.orangeAccent,
                      child: const TextField(
                        textAlign: TextAlign.center,
                        maxLines: null,
                        decoration: InputDecoration(
                          border: InputBorder.none,
                        ),
                        style: TextStyle(
                            color: Colors.black,
                            fontSize: 20,
                            decoration: TextDecoration.none),
                      ),
                    ),
                  )),
                ))
          ]),
        ));
  }
}

This is widget button on screen

class ExampleWidgetButton extends StatefulWidget {
  const ExampleWidgetButton({super.key});

  @override
  State<ExampleWidgetButton> createState() => _ExampleWidgetButtonState();
}

class _ExampleWidgetButtonState extends State<ExampleWidgetButton> {
  @override
  Widget build(BuildContext context) {
    return Positioned(
        bottom: 30,
        left: 60,
        child: CircleAvatar(
          backgroundColor: Colors.amberAccent,
          radius: 35,
          child: IconButton(
              onPressed: () {
                context
                    .read<PageDraggable>()
                    .addWidget(const Note2Widget());
              },
              color: Colors.black,
              icon: const Icon(Icons.pix)),
        ));
  }
}

This is main page in project

import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:provider/provider.dart';
import 'package:star_menu/star_menu.dart';
import 'widgets/interfaceButtons.dart';

double globalPosX = 0;
double globalPosY = 0;
double containerLeft = 0;
double containerTop = 0;
bool isResizing = false;
Offset startPosition = Offset.zero;
String tempText = "";

class PageDraggable extends StatefulWidget with ChangeNotifier {
  PageDraggable({super.key});

  static List<Widget> widgets = [];

  void addWidget(Widget widget) {
    PageDraggable.widgets.add(widget);
    notifyListeners();
    print("${widgets.toList()}");
  }

  void removeWidget(Widget widget) {
    PageDraggable.widgets.remove(widget);
    notifyListeners();
  }

  void allRemoveWidget() {
    PageDraggable.widgets.clear();
    notifyListeners();
  }

  @override
  State<PageDraggable> createState() => _PageDraggableState();
}

class _PageDraggableState extends State<PageDraggable> with ChangeNotifier {
  int count = 0;
  Color pickerColor = const Color(0xff443a49);
  Color currentColor = const Color(0xff443a49);

  void changeColor(Color color) {
    setState(() => pickerColor = color);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Stack(children: <Widget>[
          // AreaControlWidget(),
          Consumer<PageDraggable>(
            builder: (context, value, child) {
              return Positioned(
                  top: 5,
                  left: 250,
                  child: Text("| x: $globalPosX \n| y: $globalPosY "));
            },
          ),
          Consumer<PageDraggable>(
            builder: (context, value, child) {
              return Stack(
                children: [...PageDraggable.widgets],
              );
            },
          ),

          ///Delete Button
          const DeleteWidget(),

          ///
          Positioned(
              bottom: 40,
              left: 120,
              child: ElevatedButton(
                  onPressed: () {
                    showDialog(
                        builder: (context) => AlertDialog(
                              title: const Text('Pick a Color'),
                              content: SingleChildScrollView(
                                child: MaterialPicker(
                                  pickerColor: pickerColor,
                                  onColorChanged: changeColor,
                                ),
                              ),
                              actions: <Widget>[
                                ElevatedButton(
                                  child: const Text('Got it'),
                                  onPressed: () {
                                    setState(() => currentColor = pickerColor);
                                    Navigator.of(context).pop();
                                  },
                                ),
                              ],
                            ),
                        context: context);
                  },
                  child: const Text("Click"))),

          ///
          Positioned(
            bottom: 60,
            right: 30,
            child: StarMenu(
              params: const StarMenuParameters(
                shape: MenuShape.circle,
                circleShapeParams:
                    CircleShapeParams(startAngle: 90, endAngle: 180),
                animationCurve: Curves.bounceInOut,
              ),
              onStateChanged: (state) => print("state changed"),
              onItemTapped: (index, controller) {
                controller.closeMenu!();

                print("Menu item $index tapped");
              },
              items: const [
                ///Create Widget Button
                CreateWidget(),

                ///Create Text Widget Button
                CreateTextWidget(),

                ///Resizeable Widget Button
                ResizeableWidget(),

                ///StickyNote Widget Button
                StickyNoteWidgetButton(),

                ///Example Widget Button
                ExampleWidgetButton(),
              ],
              child: FloatingActionButton(
                onPressed: () {},
                child: const Icon(Icons.looks_one),
              ),
            ),
          )
        ]),
      ),
    );
  }
}

This is main.dart file

import 'package:draggable_example/ui/pageDraggable.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MultiProvider(
        providers: [
          ChangeNotifierProvider(
            create: (context) => PageDraggable(),
          ),
        ],
        child: MaterialApp(
          home: PageDraggable(),
        )));

All necessary libraries are installed and running, and flutter is running on the latest version.

-flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.13.9, on Microsoft Windows [Version 10.0.19045.3570], locale tr-TR)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[√] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[X] Chrome - develop for the web (Cannot find Chrome executable at .\Google\Chrome\Application\chrome.exe)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.6.5)
[√] Android Studio (version 2022.3)
[√] VS Code, 64-bit edition (version 1.81.1)
[√] Connected device (3 available)
[√] Network resources

Upvotes: 0

Views: 225

Answers (1)

Rap
Rap

Reputation: 7292

Your problem is that MediaQuery.sizeOf(context) gives you the size of the screen but you have your PageDraggable widget wrapped in a SafeArea which adds padding sufficient to avoid notches and rounded corners.

What you need is to take your screen size and subtract the amount cut off by the SafeArea.

This library on pub.dev may help: https://pub.dev/packages/responsive_sizer. It has an object called Adaptive that is aware of the SafeArea. To get the usable width, you'll go

double usableHeight = Adaptive.sh();
double usableWidth = Adaptive.sw();

Then just draw beginning at 0,0 as you'd normally do but now the width and height of your drawing area will fit the screen.

Upvotes: 0

Related Questions