Flutter MatrixGestureDetector - Minimum and maximum scale - flutter

My question essentially duplicates the previously asked question: Flutter: How to set a max/min scale using matrix_gesture_detector
I have a MatrixGestureDetector:
MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
setState(() {
transform = m;
});
},
child: Transform(
transform: transform,
child: Image.asset("assets/maps/map.gif"),
),
),
The picture can be zoomed in and out indefinitely, but I want to limit the minimum and maximum scale.
The solution suggested in the link above is not correct for me. The scale is really limited, but when it reaches its limits, gestures start to be handled incorrectly. The picture starts moving very quickly when you try to zoom in or out when the limits are reached.
Is there any solution to this problem? Maybe there are other packages to solve my problem? PhotoViev did not satisfy me for the reason described here: Flutter PhotoView - Rotation about the point between the fingers

Flutter announced a new version(1.20) and it came with a new widget called InteractiveViewer.
You can use the InteractiveViewer to achieve what you want by setting a minScale and maxScale.
This release introduces a new widget, the InteractiveViewer. The InteractiveViewer is designed for building common kinds of interactivity into your app, like pan, zoom, and drag ā€™nā€™ drop
Read more about the release here: Flutter 1.20 Release notes
I added a demo:
NOTE: You can only use this widget if you have upgraded to the latest Flutter version (1.20)
class ZoomImage extends StatelessWidget {
final transformationController = TransformationController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: InteractiveViewer(
transformationController: transformationController,
onInteractionEnd: (details) {
setState(() {
// unzoom when interaction has ended
transformationController.toScene(Offset.zero);
});
},
// set the boundary margin for the image
boundaryMargin: EdgeInsets.all(20.0),
// set min scale here
minScale: 0.1,
// set maximum scall here
maxScale: 1.6,
child: ClipRRect(
borderRadius: BorderRadius.circular(18.0),
child: Image.asset('assets/maps/map.gif'),
),
),
);
}
}

Related

Flutter GridView sometimes shows padding despite setting it to EdgeInsets.zero

I'm making a very simple Grid, basically 9 squares, and I set the padding to EdgeInsets.zero (or EdgeInsets.all(0)) and sometimes, depending on the window's size, it shows a padding, or not.
I'm testing it on chrome and linux (fedora plasma, and fedora i3wm) and the error is consistent.
The images bellow share the same code, I just resized the window slightly
Image with paddings/gaps
Image without paddings/gaps
Here's the code
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(home: myApp));
}
// display 9 squares
Widget myApp = GridView.count(
crossAxisCount: 3,
padding: EdgeInsets.zero,
children: [
Square(),
Square(),
Square(),
Square(),
Square(),
Square(),
Square(),
Square(),
Square(),
],
);
// simple red colored square
class Square extends StatelessWidget {
Color color = Colors.red;
Square({super.key});
#override
Widget build(BuildContext context){
return Container(
color: color,
padding: EdgeInsets.zero,
);
}
}
flutter is cross-platform framework, which help to build apps with single codebase. But, the challenge is how to make the app adaptive for each platform.
You might read this documentation for full explanation: https://docs.flutter.dev/development/ui/layout/building-adaptive-apps
there many considerations, and you said that its show padding depending window size, which is one of the documentaion mentioned here: https://docs.flutter.dev/development/ui/layout/building-adaptive-apps#building-adaptive-layouts
im not explore yet, but thats my hypothesis. hope it guide you to solve the issue

flutter camera - what is the difference between CameraPreview(controller) and controller.buildPreiview()

I am new to flutter and I am trying to use camera with flutter.
I want to understand the difference between CameraPreview(controller) and controller.buildPreiview() because it behaves differently for some reason.
This is the code for the showing the preview:
#override
Widget build(BuildContext context) {
return _isCameraInitialized
? Material(
child: Stack(
children: [
GestureDetector(
...
child: _cameraController!.buildPreview()
// child: CameraPreview(_cameraController!)
),
....
]
),
)
: Container();
The result for using _cameraController!.buildPreview():
This is the desired result - make the camera preview appear as full screen.
But the result for using CameraPreview(_cameraController!) is:
This leaves the right of the screen white and does not take the full width of the screen for some reason. I also tried to wrap it with AspectRatio but it didn't work.
I was wondering why those methods behave differently and if it is better to use one of them over the other?
use this solution it will remove the right vertical banner :
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child:CameraPreview(_cameraController),

Best way to allow a bit of overscroll in a CustomScrollView

The UI I'm making usually starts with the bottom sliver scrolled all the way in, so that its top is at the top of the view. But also:
Needs an extra empty space at the top, in case the user wants to pull the content down so that they can reach it without moving their hand from the bottom of the phone (this is a really basic ergonomic feature and I think we should expect to see it become pretty common soon, first led by apps moving more of their key functionality to the bottom of the screen, EG, Firefox's url bar.) (Currently, I'm using the appBar sliver for this, but I can imagine a full solution not using that)
Might need extra empty space at the bottom, whenever the content in that bottom sliver wont be long enough to allow it to be scrolled in all the way. It will seem buggy and irregular otherwise. Ideally I'd impose a minHeight so that the bottom sliver will always at least as tall as the screen, but it's a sliver, so I'm pretty sure that's not possible/ugly-difficult.
The avenue I'm considering right now is, ScrollPhysics wrapper that modifies its ScrollMetrics so that maxExtent and minExtent are larger. As far as I can tell, this will allow the CustomScrollView (given this ScrollPhysics) to overscroll. It feels kinda messy though. It would be nice to know what determines maxExtent and minExtent in the first place and alter that.
Lacking better options, I went ahead with the plan, and made my own custom ScrollPhysics class that allows overscroll by the given amount, extra.
return CustomScrollView(
physics: _ExtraScrollPhysics(extra: 100 * MediaQuery.of(context).devicePixelRatio),
...
And _ExtraScrollPhysics is basically just an extended AlwaysScrollable with all of the methods that take ScrollMetrics overloaded to copy its contents into a ScrollMetric with a minScrollExtent that has been decreased by -extra, then passing it along to the superclass's version of the method. It turns out that adjusting the maxScrollExtent field wasn't necessary for the usecase I described!
This has one drawback, the overscroll glow indicator, on top, appears at the top of the content, rather than the top of the scroll view, which looks pretty bad. It looks like this might be fixable, but I'd far prefer a method where this wasn't an issue.
mako's solution is a good starting point but it does not work for mouse wheel scrolling, only includes overscroll at the top, and did not implement the solution to the glow indicator problem.
A more general solution
For web, use a Listener to detect PointerSignalEvents, and manually scroll the list with a ScrollController.
For mobile, listening for events is not needed.
Extend a ScrollPhysics class as mako suggested but use NeverScrollableScrollPhysics for web to prevent the physics from interfering with the manual scrolling. To fix the glow indicator problem for mobile, wrap your CustomScrollView in a ScrollConfiguration as provided by nioncode.
Add overscroll_physics.dart from the gist.
Add custom_glowing_overscroll_indicator.dart from the other gist.
GestureBinding.instance.pointerSignalResolver.register is used to prevent the scroll event from propogating up the widget tree.
Example
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:my_project/custom_glowing_overscroll_indicator.dart';
import 'package:my_project/overscroll_physics.dart';
class OverscrollList extends StatelessWidget {
final ScrollController _scrollCtrl = ScrollController();
final double _topOverscroll = 200;
final double _bottomOverscroll = 200;
void _scrollList(Offset offset) {
_scrollCtrl.jumpTo(
_scrollCtrl.offset + offset.dy,
);
}
#override
Widget build(BuildContext context) {
return Container(
height: 300,
decoration: BoxDecoration(border: Border.all(width: 1)),
child: Listener(
onPointerSignal: (PointerSignalEvent event) {
if (kIsWeb) {
GestureBinding.instance.pointerSignalResolver.register(event, (event) {
_scrollList((event as PointerScrollEvent).scrollDelta);
});
}
},
child: ScrollConfiguration(
behavior: OffsetOverscrollBehavior(
leadingPaintOffset: -_topOverscroll,
trailingPaintOffset: -_bottomOverscroll,
),
child: CustomScrollView(
controller: _scrollCtrl,
physics: kIsWeb
? NeverScrollableOverscrollPhysics(
overscrollStart: _topOverscroll,
overscrollEnd: _bottomOverscroll,
)
: AlwaysScrollableOverscrollPhysics(
overscrollStart: _topOverscroll,
overscrollEnd: _bottomOverscroll,
),
slivers: [
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.blue),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.yellow),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.red),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.orange),
),
],
),
),
),
);
}
}
dartpad demo
Mobile result:

How do I make a child widget expand to fill a parent container inside of a stack when the child has no parameters to alter its layout?

I'm building a card game and using flame to pull the cards from a sprite sheet. The problem is that I set the width and height of the Container that holds the SpriteWidget, but the SpriteWidget expands to either the width or the height of the container, but not both. I want it to expand/stretch to be the same size as the parent container. Unfortunately, the SpriteWidget really has no parameters that could be used to change its size.
I've spent several hours scouring the internet for a solution and tried a number of widgets including FittedBox, Flex, Positioned.fill, etc., but I'm unable to achieve the desired effect. How can I make the SpriteWidget stretch to fill its parent when it has no parameters to do so?
class _PlayerHandPortraitLayout
extends WidgetView<PlayerHand, _PlayerHandController> {
#override
final state;
const _PlayerHandPortraitLayout(this.state) : super(state);
#override
Widget build(BuildContext build) {
return Stack(
children: state.displayHand().asMap().entries.map((cardItem) {
var index = cardItem.key;
var card = cardItem.value;
return Positioned(
left: index * CARD_OVERLAP_OFFSET,
child: Draggable<Container>(
childWhenDragging: Container(),
child: Container(
color: Colors.purple,
width: state.cardWidth,
height: state.cardHeight,
child: SpriteWidget(
sprite: state.spriteImages[card.suite.index][card.value.index],
),
),
feedback: Container(
color: Colors.yellow,
width: state.cardWidth,
height: state.cardHeight,
child: SpriteWidget(
sprite: state.spriteImages[card.suite.index][card.value.index],
),
),
),
);
}).toList(),
);
}
}
actually this will be not possible, SpriteWidget is designed to expand as long as it fits on the smallest dimension available on its parent, you can check on it source code here.
This is done so the Sprite will not get distorted when its parent has a different aspect ratio than the ratio of the Sprite.
If you have an use case where you would want the Sprite to get intentionally distorted, please open an issue on the Flame repository explaining the case, and we can try to take a look on it.

Flutter clip without space around the clipped widget

I want to clip a widget and use this clipped image in a layout and the bounderies should be the visible part of the imgage when clipped.
Using this custom clipper
#override
Rect getClip(Size size) {
Rect rect = Rect.fromLTRB(25,0,size.width - 25, size.height);
return rect;
}
#override
bool shouldReclip(CustomRect oldClipper) {
return true;
}
}
results in 25 px blank space left of the clipped image and 25 px blank space right of the clipped image.
And at least we ant to copy specific areas of an image and scale/position it exactly in the app... -> more complex desired result:
You need to add a transform to translate the widget over to the newly empty space. Just be aware that the widget itself will still occupy the same width in this example - so if there is some sibling in a row, for example, it will still get "pushed over" by the same amount of space. If you need to change that you'll need to add a SizedBox to the final part of this example so that you can trim down the size of the widget to the portion you've clipped.
Also note that this is not a very good practice - ideally you should be fetching the image you actually want to display. Flutter will still need to load your entire image into memory and then do some non-trivial work to add the clip you want. That takes up plenty of extra CPU and memory. But sometimes you don't have a choice I guess.
This example shows just displaying an image, followed by applying a custom clip, followed by applying a translation, which is what OP is looking for.
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart';
void main() {
final Widget image = Image.network(
'https://via.placeholder.com/300x60?text=This is just a placeholder');
const double widthAmount = 100;
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(
children: <Widget>[
Spacer(),
image,
Spacer(),
ClipRect(
clipper: CustomRect(widthAmount),
child: image,
),
Spacer(),
Transform(
transform: Matrix4.translation(Vector3(-widthAmount, 0.0, 0.0)),
child: ClipRect(
clipper: CustomRect(widthAmount),
child: image,
),
),
Spacer(),
],
),
),
),
));
}
class CustomRect extends CustomClipper<Rect> {
CustomRect(this.widthAmount);
final double widthAmount;
#override
Rect getClip(Size size) {
Rect rect =
Rect.fromLTRB(widthAmount, 0, size.width - widthAmount, size.height);
return rect;
}
#override
bool shouldReclip(CustomRect oldClipper) {
return oldClipper.widthAmount != widthAmount;
}
}
Okay, the solution above is not really working as if we translate the clipped image we get some free space (the amount that we translate).
Isn't there any option in Flutter to just define an area of an widget and get this area as a new Widget/Image with exactly the width and height of the defined area to be able to position it (without any paddings/white space around),to scale it, ...?