flutter web zoom and pan on mouse hover - flutter

iam working on flutter web project and i need to make widget contain image that zoom and pan on mouse hover
i tried using mouse region + InteractiveViewer but i can't make it work the way i wanted
iam trying to mimic this: https://sb-topdeal2.mybigcommerce.com/sample-laundry-detergent/ ( try to hover on the image )
MouseRegion(
onExit: (event) {
transformationController.value = Matrix4.identity();
},
onHover: (h){
print(h.position.dy);
transformationController.value = Matrix4.identity()
..translate(h.position.dx , h.position.dy)
..scale(1.5);
},
child: Container(
height: Responsive.is1200(context) ? 360 : 260,
child: InteractiveViewer(
scaleEnabled: false,
panEnabled: true,
minScale: 0.1, // min scale
maxScale: 1.0, // max scale
transformationController: transformationController,
child: Image.asset(
image,
)),
),
)

You can use the ImageZoomOnMove package:
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
final imageUrl = "https://images.unsplash.com/photo-1619360142632-031d811d1678?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=872&q=80";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Image Zoom on Move'),
),
body: Center(
child: ImageZoomOnMove(
width: 500,
height: 500,
image: Image.network(
imageUrl,
fit: BoxFit.cover,
),
),
),
);
}
}

Related

Show half of rotating image in Flutter

I am creating an animation where a circle is rotating (left image).
My goals is only having half of the animation visible (blue rectangle in right image). In web development I would have created a div with a hidden overflow. I can't get this to work in Flutter. I've looked into ClipRect without luck.
This is the Flutter code I am using to rotate the image:
class ImageRotate extends StatefulWidget {
const ImageRotate({Key? key}) : super(key: key);
#override
_ImageRotateState createState() => _ImageRotateState();
}
class _ImageRotateState extends State<ImageRotate>
with SingleTickerProviderStateMixin {
late AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 30),
);
animationController.repeat();
}
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
// color: Colors.white,
child: AnimatedBuilder(
animation: animationController,
child: SizedBox(
child: Image.asset('assets/svg/circle.png'),
),
builder: (BuildContext context, Widget? _widget) {
return Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
);
}
}
You could try using a Stack that clips its contents using its clipBehavior: Clip.hardEdge option, then you shift it half way its height. You wrap the Stack in a SizedBox to constrain its height to half the height of the circle, and apply a height to the ImageRotate also, as such:
Center(
child: SizedBox(
height: 200,
width: 400,
child: Stack(
clipBehavior: Clip.hardEdge,
children: [
Positioned(
top: 0,
child: ImageRotate()
)
]
)
),
)
To your ImageRotate:
SizedBox(
child: Image.asset('assets/svg/circle.png', width: 400, height: 400, fit: BoxFit.contain)
),
Check out this Gist and run it through DartPad.dev to check it out. Your output should look like this:
Here's an idea,
Put your circle and a rectangular opaque box(possibly container) in a Stack Widget in this order.
Give the container half of your rectangular box's size and color that can hide the circle.
Wrap the container in Positioned Widget and place it aligning to the bottom half of the blue rectangle.
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
height: 400,
width: 400,
alignment: Alignment.center,
child: Stack(children: [
AnimatedBuilder(
animation: animationController,
child: ClipOval(
child: Image.asset(
'assets/images/download.png',
width: 400,
height: 400,
fit: BoxFit.cover,
),
),
builder: (BuildContext context, Widget? _widget) {
return Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 400,
height: 200,
color: Colors.white,
),
)
]),
),
),
);
}
In this code:
I used ClipOval for circular image cause my image was square.
Parent container has height and width, as well as child positioned container(half of the parent height).
And SafeArea for avoiding device margins.
Is this what you're looking for?

How to make the application responsive using Stack and Positoned - Flutter

How to use Stack and Positioned to add a shape in the SafeArea, I tried to change the color of the AppBar and connect to the shape and add mediaQuery, but still not on every screen it will be properly connected. So how to get a svg photo on the entire surface of SafeArea, and to make it responsive without using appbar, is it necessary to get the effect like in the picture below?(the code gives the effect as in the picture, but it is not responsive and consists of two parts, and I would like one part and get responsive)
Any help very much appreciated.
class Shape extends StatelessWidget {
static Route route() {
return MaterialPageRoute<void>(builder: (_) => Shape());
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
elevation: 0,
actions: [],
),
body: _profilePage(context),
);
}
Widget _profilePage(BuildContext context) {
return SafeArea(
child: Align(
child: Center(
child: Stack(
children: <Widget>[
Positioned(
width: MediaQuery.of(context).size.width * 1,
height: MediaQuery.of(context).size.height,
bottom: MediaQuery.of(context).size.width * 0.6,
child: _curved(context),
),
],
),
),
),
);
}
Widget _curved(BuildContext context) {
return SvgPicture.asset(
'assets/images/shape_purple.svg',
color: Colors.blue,
allowDrawingOutsideViewBox: true,
);
}
Use FitteBox Widget instead
FittedBox(
child: Image.asset('assets/images/background.png'),
fit: BoxFit.fill,
// decoration: BoxDecoration(
// color: Colors.white),
),

Could not complete vertical scroll of Listview inside InteractiveViewer

I have a requirement where I need to scroll through a list of images and also zoom and pan them. Very similar to a pdf document viewer. So I Used a ListView to show the pages and added the ListView as child to InteractiveViewer.
After zooming in I could not scroll to the top or bottom end of the ListView.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: InteractiveViewer(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 10,
itemBuilder: (context, _index) {
print(_index);
return Container(
color: Colors.grey,
height: MediaQuery.of(context).size.width * 1.1,
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(2),
child: Container(
padding: EdgeInsets.all(2),
color: Colors.white,
child: Center(
child: Text('Page index: $_index'),
),
),
);
},
),
scaleEnabled: true,
panEnabled: true,
),
);
}
I guess it might be due to the InteractiveViewer handling the scroll gesture of ListView.
Is there a way to avoid vertical gesture to be handled by InteractiveViewer?
I don't think there is a way to make the two element have their scroll behavior working together, as InteractiveViewer allows you to move after zooming in your image.
Would it fulfill your requirement to set the image to fullscreen when taping on the image to zoom ?
That way you keep the scroll handled by the ScrollView and separate the InteractiveViewer to another view.
Something like that, you wrap all of your images with the ImageDetails widget and remove your InteractiveViewer from the Scaffold:
class ImageDetails extends StatelessWidget {
final String url;
const ImageDetails({Key key, this.url}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Hero(
tag: 'tag$url',
child: NetworkImage(
imageUrl: url,
fit: BoxFit.cover,
),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return FullScreenImage(
url: url,
);
}));
});
}
}
class FullScreenImage extends StatelessWidget {
final String url;
const FullScreenImage({Key key, this.url})
: super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
child: InteractiveViewer(
maxScale: 2.0,
minScale: 1.0,
child: Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Hero(
tag: 'tag$url',
child: SizedBox(
width: MediaQuery.of(context).size.width,
child:
NetworkImage(imageUrl: url, fit: BoxFit.fitWidth),
),
),
),
),
),
onTap: () {
Navigator.pop(context);
},
);
}
}

Flutter Web sliver: customscrollview

I'm making a flutter web.
I want to resize a horizontal image in a CustomScrollView.
Ex: On a product detail page, I want to place the top image size 600x400 width in the center of the layout.
Regardless of the image size, it is a problem that it expands to 900x or more horizontally.
layout:
code:
class ServiceProductDetailPage extends StatelessWidget {
final String productId;
final ServiceProductModel product;
ServiceProductDetailPage({required this.product, required this.productId});
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 50,
title: Text('Title'),
stretch: true,
backgroundColor: Colors.black,
),
SliverToBoxAdapter(child: _Body(context, _product)),
],
),
);
}
}
Widget _Body(BuildContext context, ServiceProductModel product) {
return Card(
child: ConstrainedBox(
constraints: BoxConstraints.tight(Size(900,400)
),
child: LoadingImage(url: product.image),
),
);
}
class LoadingImage extends StatelessWidget {
const LoadingImage({
Key? key,
required this.url,
}) : super(key: key);
final String url;
#override
Widget build(BuildContext context) {
return Image.network(
url,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
errorBuilder: (BuildContext c, Object err, StackTrace? stack) {
return const Center(child: Text('error'));},
frameBuilder: (BuildContext c, Widget image, int? frame, bool sync){
if (!sync && frame == null) {
return const Center(child: CircularProgressIndicator());
}
return image;
},
);
}
}
What you are looking for is an UnconstrainedBox. As per the documentation, the UnconstrainedBox makes itself take the full width of it's parent, but it allows it's child to be of any width the child decides.
So, you can use it like this.
Widget _Body(BuildContext context, ServiceProductModel product) {
return Card(
child: UnconstrainedBox(
child: Container(
color: Colors.deepOrange,
width: 900,
height: 400,
child: LoadingImage(url: product.image),
),
),
);
}
Next, you given some properties to you Image.network that is making it expand.
So remove the following
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
And just add this
fit: BoxFit.contain,
I have given your 900 width Container a color to see that it stays at 900 despite the window being larger than 900.

Propagate click behind a widget

I have a Stack with two widgets inside.
I'm trying to detect the click on the bottom widget of the Stack, which is behind the top one.
I am using HitTestBehavior.translucent, but it only work if the GestureDetector doesn't have any child.
This is a simplified version of what i need in my app. I have a Stack which contains many tiny Cards on the screen, and there's one canvas on top of them all. Despite the canvas, I need to be able to tap on the Cards to change its content. I thought using a translucent behavior would solve my problem but it does not.
EDIT : Also, the first GestureDetector will always be in a Positioned widget.
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 800,
width: 400,
child: Stack(
children: [
/// 2. ... and trigger the onTap function of this widget
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
print('TAP BOTTOM');
},
child: Container(
height: 500,
width: 400,
color: Colors.deepOrange,
),
),
/// 1. I'm Trying to clic here...
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: null,
child: Container(
height: 300,
width: 400,
color: Colors.deepPurple,
),
),
],
),
),
// ),
),
);
}
}
I have a sample code with which you can achieve this:
​class TestPage extends StatelessWidget {
  #override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          height: 800,
          width: 400,
          child: Stack(
            children: [
              /// 2. ... and trigger the onTap function of this widget (WIDGET_2)
              GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTap: () {
                  print('TAP BOTTOM');
                },
                child: Container(
                  height: 500,
                  width: 400,
                  color: Colors.deepOrange,
                ),
              ),
              /// Able to tap bottom
              IgnorePointer(
                ignoring: true,
                child: Container(
                  height: 300,
                  width: 400,
                  color: Colors.deepPurple,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Also sorry for posting late here.
The answer was given by VrajGohil on this issue :
https://github.com/flutter/flutter/issues/77596
Here is his solution :
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 800,
width: 400,
child: Stack(
children: [
/// 2. ... and trigger the onTap function of this widget (WIDGET_2)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
print('TAP BOTTOM');
},
child: Container(
height: 500,
width: 400,
color: Colors.deepOrange,
),
),
/// Able to tap bottom
IgnorePointer(
ignoring: true,
child: Container(
height: 300,
width: 400,
color: Colors.deepPurple,
),
),
],
),
),
),
);
}
}