JohnTaa
JohnTaa

Reputation: 2824

How to Align many container widgets over an image precisely to behave exactly as a part of the image when resizing

I have a book-like application that display images of a book pages inside PageView widget, and I want to make some phrases on that Image clickabe at a specific positions related to the image (left,right,width,height),I tried to use Stack widget with Positioned containers But I cannot align the phrases coordinates precisely on the image. the problem is: how to make those positioned containers have absolute position on the image. What I have done to acheive that :

Here is the Application code :

    import 'package:flutter/material.dart';
  class ImageSliderApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        // scrollBehavior: AppScrollBehavior(),
        home: ImageSliderScreen(),
      );
    }
  }
  class ImageSliderScreen extends StatefulWidget {
    @override
    _ImageSliderScreenState createState() => _ImageSliderScreenState();
  }
  class _ImageSliderScreenState extends State<ImageSliderScreen> {
    PageController _pageController = PageController();

    int _currentPage = 0;
    List<String> _images = [];
    bool isEditable = false;
    final List<GlobalObjectKey> keyList =
        List.generate(170, (index) => GlobalObjectKey(index));

  /* lights : sample data of the phrases [page, x , y] 
  width and hight generated dynamically.
  */
    List<List<dynamic>> lights = [
      [4, 96, 104],
      [4, 238, 144],
      [4, 128, 222],
      [4, 284, 342],
      [4, 153, 379],
      [4, 343, 497],
      [4, 303, 576],
      [4, 58, 616]
    ];

  /*
  loadAssets : to fill images list with images names
  */
    void loadAsset() async {
      for (int i = 3; i < 164; i++) {
        String ff = "assets/images/$i.png";
        _images.add(ff);
      }
    }
  /*
  calculatePosiion: function called to reassign the position of the highlighting rectangles
  relative to the image position when the viewport resized.
  */
    Rect calculatePosition(details) {
      double det_width =
          (details?.width != null) ? (details?.width) : 0; //(454,735)
      double det_height = (details?.height != null) ? (details?.height) : 0;
      double ret_width = det_width.round() / 454;
      double ret_height = det_height.round() / 735;
      Rect recto = new Rect.fromLTWH(45, 20, ret_width * 150, ret_height * 30);
      return recto;
    }

    @override
    void initState() {
      super.initState();
      loadAsset();
      _pageController.addListener(() {
        setState(() {
          _currentPage = _pageController.page!.round();
        });
      });
    }

    @override
    void dispose() {
      super.dispose();
      _pageController.dispose();
    }

    @override
    Widget build(BuildContext context) {
      return SafeArea(
        child: Scaffold(
          appBar: isEditable == true
              ? AppBar(
                  title: Text('Book Pages Slider'),
                  centerTitle: true,
                )
              : null,
          body: PageView.builder(
            controller: _pageController,
            scrollDirection: Axis.horizontal,
            padEnds: false,
            reverse: true,
            itemCount: _images.length,
            physics: BouncingScrollPhysics(),
            onPageChanged: (int) {},
            itemBuilder: (context, index) {
              return SingleChildScrollView(
                  child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Center(
                  child: Stack(
                    clipBehavior: Clip.none,
                    children: <Widget>[
                      Positioned(
                        child: SizedBox(
                          // height: (MediaQuery.of(context).size.height),
                          //height: 600,
                          // width: 200,
                          child: Image.asset(
                            width: (MediaQuery.of(context).size.width),
                            key: keyList[index],
                            _images[index],
                            fit: BoxFit.contain,
                          ),
                        ),
                      ),
                      Positioned(
                          top: keyList[index].globalPaintBounds?.top,
                          left: keyList[index].globalPaintBounds?.left,
                          //top: calculatePosition(yourKey.globalPaintBounds).top,
                          child: Container(
                            // width: calculatePosition(keyList[index].globalPaintBounds).width,
                            height: keyList[index].globalPaintBounds?.height,
                            width: (MediaQuery.of(context).size.width),
                            decoration:
                                BoxDecoration(color: Colors.red.withOpacity(.1)),
                            child: InkWell(
                                onTap: () {
                                  print(lights[4][5]);
                                  print(
                                      'coordinates on screen: ${keyList[index].globalPaintBounds}');
                                },
                                child: const Text('1')),
                          )),
                      /** Positioned WIdget **/
                    ],
                  ),
                ),
              ));
            },
          ),
        ),
      );
    }
  }

  extension GlobalKeyExtension on GlobalKey {
    Rect? get globalPaintBounds {
      final renderObject = currentContext?.findRenderObject();
      final translation = renderObject?.getTransformTo(null).getTranslation();
      if (translation != null && renderObject?.paintBounds != null) {
        final offset = Offset(translation.x, translation.y);
        return renderObject!.paintBounds.shift(offset);
      } else {
        return null;
      }
    }
  }

Upvotes: 0

Views: 84

Answers (1)

pskink
pskink

Reputation: 24740

simplicity is the most important, try this:

import 'package:flutter/material.dart';

final rects = [
  (const Rect.fromLTWH(31, 235, 100, 26), Colors.indigo.withOpacity(0.5), 'pantry'),
  (const Rect.fromLTWH(121, 341, 102, 70), Colors.green.withOpacity(0.5), 'bath'),
];

main() => runApp(
  MaterialApp(home: Scaffold(body: SizedBox.expand(
    child: FittedBox(
      child: Stack(
        children: [
          Image.network('https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sample_Floorplan.jpg/640px-Sample_Floorplan.jpg'),
          ...rects.map((r) => Positioned.fromRect(
              rect: r.$1,
              child: Material(
                color: r.$2,
                child: InkWell(
                  splashColor: Colors.black,
                  onTap: () => print('onTap ${r.$3}'),
                ),
              ),
            ),
          ),
        ],
      ),
    ),
  )))
);

here:

Rect.fromLTWH(31, 235, 100, 26)
Rect.fromLTWH(121, 341, 102, 70)

represent two areas on the original image: one with position 31, 235 and size 100 x 25 and the second with position 121, 341 and size 102 x 70

they correspond with "pantry" and "bath" on the floor plan

in case Image.network('https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sample_Floorplan.jpg/640px-Sample_Floorplan.jpg') is not reachable here is a copy:

enter image description here

Upvotes: 1

Related Questions