Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing Widgets at specific pixel locations for different screen sizes

I'm trying to build a simple Flutter application that displays a full-screen background image and enables the user to drag certain widgets (i.e. a basic circle) from pre-defined start positions (given in pixels) to pre-defined target positions (also given in pixels). The following screenshot from the TouchSurgery app shows a very similar setup to what I'm trying to achieve (green circle = start position, white circle = target position):

enter image description here

My biggest concern at this point are different screen sizes. Let's assume we have an iPhone SE (second generation) with a resolution of 750 x 1334. I can create the following background image with the desired resolution and randomly determine the desired start position to be at coordinates (430, 949) (for simplicity we can disregard the target position):

enter image description here

With the following widget, I can render a circular Container on top of the starting point:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var dpr = MediaQuery.of(context).devicePixelRatio;
    return Scaffold(
      body: Stack(
        children: <Widget>[
          Container(
            decoration: BoxDecoration(
              image: DecorationImage(
                image: AssetImage("assets/iPhoneSE.png"),
                fit: BoxFit.fill,
              ),
            ),
          ),
          Positioned(
            left: 430 / dpr,
            top: 949 / dpr,
            child: Container(
              width: 77.0 / dpr,
              height: 77.0 / dpr,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.red,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

The resulting image looks like this:

enter image description here

Things start to get tricky when I add an AppBar or a BottomNavigationBar to my application. Both Widgets have a default height of 56 pixels. Given a devicePixelRatio of 2 on the iPhone SE, I need to crop the size of my background image to 750 x 1110 for the overlay to still be accurate (1334 - 2 * 56 (AppBar) - 2 * 56 (BottomNavigationBar)).

Things get even more complicated for other devices such as the iPhone XR, where also the size of the safe area has to be considered. And for Android, there's even more different screen resolutions available.

My question now is the following: instead of creating differently sized background images for 20-30+ different screen sizes - is there a more efficient way in Flutter to draw widgets such as a circular Container at very specific screen locations that works independently of the actual screen size?

like image 266
Schnigges Avatar asked Mar 02 '23 05:03

Schnigges


1 Answers

You need to get the size of the image container BEFORE positioning your Positioned Widget.

Because as you said, the screen size could change, independently of the image size (e.g. The screen is taller but has a bigger SafeArea, or has an appBar and BottomAppBar. The image could be the same size even if the screen size increased...)

Since your Positioned widget and your image Container are in the same build method, you have to use a LayoutBuilder widget to track the size of your image Container before moving on to building your Positioned widget.

Here's how:
(I've included 2 fully working examples so that you can see that the red circle keeps the same relative position to the background image, even when the image size changes. Your corrected code is the first example).

Example 1


/*
  I used these calculated ratios based on your code. 
  Feel free to use any other way to get these ratios. 
  The method will be the same.

  - The image takes all the available width and height 

  - The positioned element is postioned : 
      58.9% from the left of the image container  
      72% from the top of the image container

  - The inner container's:
      width is 7.129629629% of the Image Container's width,
      height is 4.292084726% of the Image Container's height, 
*/

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) { //This is key 
      return Scaffold(
        body: Stack(
          children: <Widget>[
            Container(
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: AssetImage("assets/iPhoneSE.png"),
                  fit: BoxFit.fill,
                ),
              ),
            ),
            Positioned(
              left: 0.589 * constraints.maxWidth,
              top: 0.72 * constraints.maxHeight,
              child: Container(
                width: 0.07129629629 * constraints.maxWidth,
                height: 04292084726 * constraints.maxHeight,
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: Colors.red,
                ),
              ),
            ),
          ],
        ),
      );
    });
  }
}

Example 1 image:

enter image description here

Example 2 (with an AppBar and BottomAppBar)

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Title of app"),
      ),
      body: LayoutBuilder(builder: (context, constraints) {
        return Column(
          children: <Widget>[
            Flexible(
              fit: FlexFit.loose,
              child: Stack(
                children: <Widget>[
                  Container(
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        image: AssetImage("assets/iPhoneSE.png"),
                        fit: BoxFit.fill,
                      ),
                    ),
                  ),
                  Positioned(
                    left: 0.589 * constraints.maxWidth,
                    top: 0.72 * constraints.maxHeight,
                    child: Container(
                      width: 0.07129629629 * constraints.maxWidth,
                      height: 0.04292084726 * constraints.maxHeight,
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: Colors.red,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        );
      }),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
              icon: Icon(
                Icons.home,
              ),
              title: Text("Home")),
          BottomNavigationBarItem(
              icon: Icon(Icons.account_circle), title: Text("Profile")),
        ],
      ),
    );
  }
}

Example 2 image:
enter image description here

like image 170
lenz Avatar answered Mar 10 '23 15:03

lenz