Flutter clip without space around the clipped widget - flutter

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, ...?

Related

Is it possible to have a listview maintain its position as images of variable height load in flutter?

Lets say you have a ListView of variable height:
List items are a Container with a mix of text and images. As such, the list items are of variable height. Sometimes no images. The text renders immediately as expected, but the images may take time to retrieve and render on screen
The images are retrieved from the network using CachedNetworkImage
Images are of variable height
When the Screen is opened the ListView automatically scrolls to item#11 (using ensureVisible technique)
So there are items both above and below your current position
At this point, when one of the network images above your position load up, the entire ListView will be pushed and you will no longer be looking at Item #11, rather somewhere randomly higher up
I considered initiating a new scroll in a callback after each image loads, however, due to network speeds, usually the listview scroll will finish before all the images load. If there are a lot of images, the images could take time to load, so it would be unreasonable to initiate a new scroll each time a new image is loaded, the screen just keeps scrolling forward every few seconds. It becomes dizzying and annoying.
Alternatively, the scrollview could jumpTo a new position as soon as the image loads, but I'm imagining there would be a slight delay between the two events and the user perceive a small "glitch" as the image loads and the listview immediately jumps to offset the image load. Even using a Future.microtask there is a very small perceptible 'glitch' as the image loads and the jumpto fires
It would be most preferable to have the listview expand the content upward somehow, so that the users current scroll position is maintained, as far as they are concerned.
Is it possible to have the ListView keep its position as the images load?
Assuming you have a predefined size for your images, you can wrap the image in a SizedBox(). This way your list will always have the same height and your items won't get pushed around.
EDIT:
Since your images are of variable size, I would probably animate to the desired location on every image load.
CachedNetworkImage has a callback
imageBuilder: (context, imageProvider) {
/// Animate to desired index
return Image(image: imageProvider);
}
Animated container, might help you. It can adjust the height automatically, depending on the height u provide in builder.
Also you can use this answer to determnin image height and width in rnutime.
Images are of variable height
To overcome this, Either we take the image size or aspect ratio of the image while storing the image along with other data.
While retrieving data, along with other text data we will receive the aspect ratio or height for the image.
I would use the same height or ratio and show placeholder image till images are loaded.
CachedNetworkImage(
imageUrl: countryList[index].flagUrl,
height: 60, // Set your height according to aspect ratio or fixed height
width: 60,
fit: BoxFit.cover,
placeholder: (_, __) => Container(
alignment: Alignment.center,
height: 60, // Set your height
width: 60,
color: Colors.red.withAlpha(80),
child: Text(
countryList[index].name[0],
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
)
Image must be of specific aspect ratio I believe. You can define height according to aspect ratio.
Try as follows:
ListView.builder(
shrinkWrap: true,
itemCount: images.length,
itemBuilder: (ctx, i) {
return Column(children: [
ButtonItems(i),
const SizedBox(height: 10),
]);
}));
Button Items class
class ButtonItems extends StatefulWidget {
final int i;
ButtonItems(this.i);
#override
_ButtonItems createState() => _ButtonItems();
}
class _ButtonItems extends State<ButtonItems> {
var images = [
"https://opengraph.githubassets.com/2ddb0ff05ef9ccfce35abb56e30d9c5068e01d1d10995484cfd07becee9accf7/dartpad/dartpad.github.io",
null,
"https://opengraph.githubassets.com/2ddb0ff05ef9ccfce35abb56e30d9c5068e01d1d10995484cfd07becee9accf7/dartpad/dartpad.github.io"
];
#override
Widget build(BuildContext context) {
print(images[widget.i]);
return Container(
height: 50,
color: Colors.grey,
child: Row(children: [
AspectRatio(
aspectRatio: 3 / 2,
child: images[widget.i] == null
? Container()
: Image.network(images[widget.i]!, fit: BoxFit.cover),
),
Text("Title " + widget.i.toString()),
]));
}
}

Positioning some images inside a container taking into account image size and container size

I have a set of images that represent different parts of an animal. I have images for legs, heads, eyes, etc.
So, I need to compose the current animal based on its set of images. The main problem is that I want to resize/translate the images based on the container size and the size of the images themselves in order to always have the full composed animal in the middle of the screen maximizing screen coverage.
I have started with a very naive approximation that could work using a stack, and Positioned widgets and a LayoutBuilder, but I need the size of the images to calculate their position and resizing factor and haven't found anyway to do it:
class AnimalBuilderPage extends StatelessWidget {
const AnimalBuilderPage ({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
double horizontalWidth = constraints.maxWidth/2.0;
double verticalCenter= constraints.maxHeight/2.0;
double bodyOffsetY, bodyOffsetX = "Here I need body image size to know how much I should resyze it and translated";
double headOffsetY, headOffsetX = "Here I need body image size to know how much I should reisze it and translated"
return Stack(
children: [
Positioned(top: bodyOffsetY, left: bodyOffsetX, child: Image.asset('assets/images/body/b1.png',scale: 0.8)),
Positioned(top: headOffsetY, left: headOffsetY, child: Image.asset('assets/images/heads/h1.png', scale:0.7)),
],
);
},));}}

Flutter. Set parent's height based on child canvas drawing's

Is there any way to set parent container's height based on height of what is drawn in the child canvas?
I am using a custom painter like this:
double width = MediaQuery.of(context).size.width;
return Container(
color: Colors.yellow,
height: 240,
width: width,
child: CustomPaint(
painter: ShapePainter(),
),
);
Then ShapePainter() draws different shapes (1 shape for each canvas in the list).
But as you can see, some shapes like 2nd rectangle, take twice the space they actually need.
I can calculate height of a shape inside ShapePainter() easily,
but I have no idea how to apply it to its parent's height. (Except calling an insta-setState() from child, but there should be a better way, because this may flicker for one frame)
Thanks for any help!
Flutter has several phases that it goes through when creating a frame, including building, layout, and painting. The size of widgets is determined during the layout phase. So you can't set the height based it what was painted. (Except, as you've said, by using something like setState to generate a new frame.) You'll have to determine the the size you want before any painting has happened. For example, you can give your CustomPainter a getter to provide the shape:
class ShapePainter extends CustomPainter {
ShapePainter({#required this.shape});
final Shape shape;
void paint(Canvas canvas, Size size) {
// the painting
}
Size get size {
// determine size based on shape
}
}
...
Widget build(BuildContext context) {
final shapePainter = ShapePainter();
return Container(
color: Colors.yellow,
height: shapePainter.size.height,
align: Alignment.center,
child: CustomPaint(
painter: shapePainter,
),
);
}

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 MatrixGestureDetector - Minimum and maximum scale

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'),
),
),
);
}
}