Reputation: 2824
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
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:
Upvotes: 1