13

I imagine a new kind of screen, and I would like to do it with Flutter since it's very powerful for rendering fast and smoothly.

I want to achieve a kind of infinite screen with kind of square or zone where you can move into. Actually exactly like a map (in fact not infinite but very large) but where I can:

  • Drag and translate
  • Zoom in and out
  • Click and press on the different component of the screen (square or whatever)

I imagine use GestureDetector on my widget "map" combine with transform on each component insde and refresh the screen after each move, or redrawing each component with draw but I'm not sure it's the best way to follow with this.

Thanks for helping if you have any idea !!

1
  • Using OverflowBox is probably the way to go. Probably playing with the offset.padding of whatever is in that box, and use some sort of GridView to move around and rendering new tiles as you go. This is roughly following the "map" analogy. Commented Aug 8, 2018 at 3:27

3 Answers 3

4

The InteractiveViewer widget

One solution could be using the InteractiveViewer widget with its constrained property set to false as it will out of the box support:

  • Drag and translate
  • Zooming in and out - Simply set minScale and maxScale
  • Clicking and pressing widgets like normal

InteractiveViewer as featured on Flutter Widget of the Week: https://www.youtube.com/watch?v=zrn7V3bMJvg

Infinite size

Regarding the question's infinite size part, a maximum size for the child widget must be specified, however this can be very large, so large that it is actually hard to re-find widgets in the center of the screen.

Alignment of the child content

By default the child content will start from the top left, and panning will show content outside the screen. However, by providing a TransformationController the default position can be changed by providing a Matrix4 object in the constructor, f.ex. the content can be center aligned if desired this way.

Example code

The code contains an example widget that uses the InteractiveViewer to show an extremely large widget, the example centers the content.

class InteractiveViewerExample extends StatefulWidget {
  const InteractiveViewerExample({
    Key? key,
    required this.viewerSize,
    required this.screenHeight,
    required this.screenWidth,
  }) : super(key: key);

  final double viewerSize;
  final double screenHeight;
  final double screenWidth;

  @override
  State<InteractiveViewerExample> createState() =>
      _InteractiveViewerExampleState();
}

class _InteractiveViewerExampleState extends State<InteractiveViewerExample> {
  late TransformationController controller;

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: InteractiveViewer.builder(
          boundaryMargin: const EdgeInsets.all(40.0),
          minScale: 0.001,
          maxScale: 50,
          transformationController: controller,
          builder: (BuildContext context, vector.Quad quad) {
            return Center(
              child: SizedBox(
                width: widget.viewerSize,
                height: widget.viewerSize,
                child: const InteractiveViewerContent(),
              ),
            );
          },
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();

    // Initiate the transformation controller with a centered position.
    // If you want the InteractiveViewer TopLeft aligned remove the
    // TransformationController code, as the default controller in
    // InteractiveViewer does that.
    controller = TransformationController(
      Matrix4.translation(
        vector.Vector3(
          (-widget.viewerSize + widget.screenWidth) / 2,
          (-widget.viewerSize + widget.screenHeight) / 2,
          0,
        ),
      ),
    );
  }
}

// Example content; some centered and top left aligned widgets,
// and a gradient background.
class InteractiveViewerContent extends StatelessWidget {
  const InteractiveViewerContent({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    TextStyle? style = Theme.of(context).textTheme.headline6;

    return Container(
      padding: const EdgeInsets.all(32.0),
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: <Color>[Colors.orange, Colors.red, Colors.yellowAccent],
        ),
      ),
      child: Stack(
        alignment: Alignment.center,
        children: [
          Align(
              alignment: Alignment.topLeft,
              child: SelectableText("Top Left", style: style),
          ),
          SelectableText("Center", style: style),
        ],
      ),
    );
  }
}

Usage

import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' as vector;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: InteractiveViewerScreen(),
    );
  }
}

class InteractiveViewerScreen extends StatelessWidget {
  const InteractiveViewerScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: InteractiveViewerExample(
          viewerSize: 50000,
          screenHeight: MediaQuery.of(context).size.height,
          screenWidth: MediaQuery.of(context).size.width,
        ),
      ),
    );
  }
}

What does the code do

InteractiveViewer in action

Sign up to request clarification or add additional context in comments.

Comments

3

I've implemented part of things you asked for except for the "infinite map". The code is quite beefy so I've described this stuff in an article on Medium. It allows:

  • move map by dragging
  • place new objects on the map
  • zoom in/out
  • click objects

GitHub repo.

Comments

1

Interesting proposal. I don't have the implementation, after all, it's up to you but I have some pointers.

Translation, I imagine, can easily be handled by 2 nested ListViews. One, that scrolls X and one that scrolls in Y direction. ScrollController can be queries for all kinds of info.

Zoom is also fairly easy at first blick: you can wrap the entire screen in a Transform.scale() widget.

You could wrap each tappable widget in a GuestureDetector, query for their RenderBox to get their position on screen in local or global coordinates, get their size.

Note: in games, there is a concept called clipping distance. Figuring out how to implement that in Flutter is going to be a fun challenge. It allows you not to render those Widgets that are too small, you zoomed out a lot eg. Let me know how it goes! Curious.


Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.