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?
Related
I am trying to create a profile header sliver that can animate.
If you consider above image, Section 1 is what we see in the fully expanded sliver, and Section 2 is what we want to see in pinned mode.
Now I would like transition to move the image - purple circle - to the side, shrink it slightly, and also move the name and the links.
I can achieve all of that but one thing: How to center them in the expanded view.
As I have to use transform to move widgets around, I cannot simply use a centring widget like column or center. And I didn't find a way to calculate the exact position to center the widget, as it needs the size of the widget, that I don't have.
Firstly I am using SliverPersistentHeaderDelegate and it provides shrinkOffset that will be used on linear interpolation(lerp method).
Then CompositedTransformTarget widget to follow the center widget.
On this example play with targetAnchor and followerAnchor and use t/shrinkOffset to maintain other animation.
class SFeb223 extends StatelessWidget {
const SFeb223({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
delegate: MySliverPersistentHeaderDelegate(),
pinned: true,
),
SliverToBoxAdapter(
child: SizedBox(
height: 1333,
),
)
],
),
);
}
}
class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
final LayerLink layerLink = LayerLink();
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
double t = shrinkOffset / maxExtent;
return Material(
color: Colors.cyanAccent.withOpacity(.2),
child: Stack(
children: [
Align(
alignment:
Alignment.lerp(Alignment.center, Alignment.centerLeft, t)!,
child: CompositedTransformTarget(
link: layerLink,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: lerpDouble(100, kToolbarHeight - 10, t),
width: lerpDouble(100, kToolbarHeight - 10, t),
decoration: const ShapeDecoration(
shape: CircleBorder(),
color: Colors.deepPurple,
),
),
),
),
),
CompositedTransformFollower(
link: layerLink,
targetAnchor: Alignment.lerp(
Alignment.bottomCenter, Alignment.centerRight, t)!,
followerAnchor:
Alignment.lerp(Alignment.topCenter, Alignment.centerLeft, t)!,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Column(
children: [Text("Sheikh")],
),
),
),
),
],
),
);
}
#override
double get maxExtent => kToolbarHeight * 6;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
false;
}
My aim is to postion a text centrally on top of a picture by using a Stack widget. I want that the text stays in the middle independet of how the window is resized.
Based on my research should the solution be a the following structure:
Stack Widget
Impage Widget
Layout Builder
Text Widget
However when trying this my Text sticks to the top left corner. So i create a simpler version of the code and am still unable to reach the desired outcome. My current thinking is that maybe my Layoutbuilder does not correctly receive the size input of the window.
My example code is as follows
main.dart
import 'package:flutter/material.dart';
import 'package:imdb/screens/home_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen());
}
}
home_screen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xfff3f3f3),
body: Stack(
children: [
Container(
height: 200,
width: 200,
color: Colors.red,
),
LayoutBuilder(builder: (context, constraints) {
return Positioned(
left: constraints.maxWidth * 0.5,
top: constraints.maxHeight*0.5,
child: Container(
height: 10,
width: 10,
color: Colors.yellowAccent
),
);
}),
],
));
}
}
As output I would expect that the yellow container is placed centrally on the Red box. However the output looks as follows:
I would suggest directly setting alignment on Stack if you need to use a stack (you could just add the overlapping element as a child):
Scaffold(
body: Container(
color: Colors.blue,
child: Stack(alignment: Alignment.center, children: [
Container(
color: Colors.red,
),
Container(
height: 10,
width: 10,
color: Colors.yellow,
),
],
),
),
);
Stacks that are not explicitly sized (via SizedBox, for example) will be the size of their largest child, unless they have constraints placed upon them ("Size goes up, constraints go down").
To answer your question though, Stacks must have positioned elements as direct children.
Edit: To address your comment, you can use an alignment offset from 0,0:
Scaffold(
body: SizedBox(
height: 200,
width: 200,
child: Stack(children: [
Container(
color: Colors.red,
),
Align(
alignment: Alignment(0,0.5),
child: Container(
height: 20,
width: 20,
color: Colors.yellow,
),
),
]),
),
);
If you don't need to use a stack, you could set the yellow box as a child of your parent container, and use alignment on the parent container:
Scaffold(
body: Container(
color: Colors.red,
height: 200,
width: 200,
alignment: const Alignment(0.5, 0.5),
child: Container(
height: 20,
width: 20,
color: Colors.yellow,
),
),
);
Align uses a fractional offset. If you want to use an absolute offset, you can wrap the child in padding or use Transform.translate. The only thing to keep in mind with Transforms is that difficult bugs can arise when it comes to pointer events (hit boxes are transformed somewhat unexpectedly)
Scaffold(
body: Container(
color: Colors.red,
height: 200,
width: 200,
alignment: Alignment.center,
child: Transform.translate(
offset: const Offset(50, 10),
child: Container(
height: 20,
width: 20,
color: Colors.yellow,
),
),
),
);
If you just want to center something, you can use the Center widget - no need to use LayoutBuilder.
So your code would look something like this:
Stack:
[
Image,
Center:
Text,
]
This way, your stack will match the size of your image, and the Text widget will be centered in the image.
Edit: Apparently the question wants to do more than just center. In that case, use Align widget instead. For the alignment property, you can pass in Alignment(0, 0) for center, or values between [-1.0, +1.0] for both x and y offsets. For example, "far-left" would be Alignment (-1.0, 0).
As long as the position of the y-axis is the same, the x-axis can be adjusted freely
this is example
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _switch = ValueNotifier<bool>(false);
#override
void initState() {
super.initState();
}
#override
void dispose() {
_switch.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var screenWidth = MediaQuery.of(context).size.width;
var screenHeight = MediaQuery.of(context).size.height;
var bgBoxHeight = screenHeight * .5;
var bgBoxWidth = screenWidth * .5;
var secendBoxHeight = bgBoxHeight * .2;
var secendBoxWidth = bgBoxWidth * .2;
// Just need to adjust the left and right offset
var xAxisStartPosition = ((bgBoxWidth / 2) - secendBoxWidth / 2) * .3;
var yAxisStartPosition = (bgBoxHeight / 2) - secendBoxHeight / 2;
var rightPading = bgBoxWidth * .1;
var bottomPading = bgBoxHeight * .1;
return Scaffold(
backgroundColor: Color(0xfff3f3f3),
body: Stack(
fit: StackFit.loose,
children: [
Container(
height: bgBoxHeight,
width: bgBoxWidth,
color: Colors.red,
),
Positioned(
left: xAxisStartPosition,
bottom: yAxisStartPosition,
child: LayoutBuilder(builder: (context, constraints) {
return Container(
height: secendBoxHeight,
width: secendBoxWidth,
color: Colors.yellowAccent);
}),
),
Positioned(
left: xAxisStartPosition,
right: rightPading,
// bottom: bottomPading,
bottom: yAxisStartPosition,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: bgBoxWidth, maxHeight: bgBoxHeight),
child: Text("""Easy to use, stylish placeholders
Just add your desired image size (width & height) after our URL, and you'll get a random image."""),
))
],
));
}
}
The CircularProgressIndicator widget is taking width of its grandparent widget, the SizedBox of width 200. I'm expecting the dimension to be 10x10, but the dimensions are 200x10. I get the same behavior if the innermost widget is a box drawn with a Container and BoxDecoration.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Color.fromARGB(255, 18, 32, 47),
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const SizedBox(
width: 200,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()));
}
}
This is a simplified version of my app, and I need the outer SizedBox.
There are some limitation on Widget constrains.
If a child wants a different size from its parent and the parent doesn’t have enough information to align it, then the child’s size might be ignored. Be specific when defining alignment
A widget can decide its own size only within the constraints given to it by its parent. This means a widget usually can’t have any size it wants.
A widget can’t know and doesn’t decide its own position in the screen, since it’s the widget’s parent who decides the position of the widget.
readmore here: https://docs.flutter.dev/development/ui/layout/constraints
thats why, the second SizedBox size was ignored, because its doesnt
know the position and aligment. and because of that, the
CircularProgressIndicator() take the grandparent size.
Solution:
set the alignment to the SizedBox.
const SizedBox(
width: 200,
child: Align(alignment:Alignment.center,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator())));
The short answer and solution is just wrap your inner SizedBox with Center. It will help.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(),
),
),
);
}
}
I recommend you read this article which describes how sizes and constraints work in Flutter. https://docs.flutter.dev/development/ui/layout/constraints
You can use Center widget to centralize the child widget
SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()),
));
Or you can use Align widget to re-position the child in the available area
SizedBox(
width: 200,
child: Align(
alignment: Alignment.topRight,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()),
));
Remove the outer SizedBox which is having the width of 200.
or use like this.
const SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(),
),
),
);
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,
),
),
),
);
}
}
I have a basic flag widget, with code here:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: Flag(),
),
);
}
}
class Flag extends StatelessWidget {
#override
Widget build(BuildContext context) {
var scaleFactor = 1.0;
final flag = Transform.scale(
scale: scaleFactor,
child: AspectRatio(
aspectRatio: 5 / 3,
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.black),
),
child: Center(
child: Container(
height: 400,
width: 150,
color: Colors.purple,
),
),
),
),
);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => flag,
);
}
}
It looks like this:
If I change scaleFactor to 0.5, it scales as expected:
But if I rotate my emulator to landscape, the proportions are off:
If I make the stripe proportional to the landscape view (e.g. change the stripe width to 250), it gets too big in portrait mode:
How do I ensure the purple stripe takes up the same proportion of space regardless of device size?
The flag is going to get way more complicated so I don't want to use MediaQuery.of(context).size to get the device width and calculate a percentage of that for every single child widget...
Do I need to tell my widget its "canonical" size? Do I need to pass a scaleFactor to every child widget?
Any ideas very appreciated :)
So, I see two ways to solve this, depending on what your eventual goal is. I think the first one is probably what you actually want.
1) Use a Row and an Expanded widget for each part of the flag, with a flex of one. The AspectRatio will keep the aspect ratio fixed, so the proportions should remain the same for the containers. Since the default flex factor is one anyhow, you can also just leave that out. If you need to the white parts to be transparent, just give Colors.transparent as the color.
class Flag extends StatelessWidget {
#override
Widget build(BuildContext context) {
var scaleFactor = 1.0;
final flag = Transform.scale(
scale: scaleFactor,
child: AspectRatio(
aspectRatio: 5 / 3,
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.black),
),
child: Row(
children: <Widget>[
Expanded(
flex:1,
child: Container(
color: Colors.white,
),
),
Expanded(
flex:1,
child: Container(
color: Colors.purple,
),
),
Expanded(
flex:1,
child: Container(
color: Colors.white,
),
),
],
),
),
),
);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => flag,
);
}
}
2) You could just scale the contents of the center with a Transform widget, but that will squish any content you put inside it, so that's probably not what you want.
class Flag extends StatelessWidget {
#override
Widget build(BuildContext context) {
var scaleFactor = 1.0;
final flag = Transform.scale(
scale: scaleFactor,
child: AspectRatio(
aspectRatio: 5 / 3,
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.black),
),
child: Center(
child: Transform(
transform: Matrix4.diagonal3Values(0.33, 1.0, 1.0),
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.purple,
),
),
),
),
),
);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => flag,
);
}
}