Flutter transform rotate inside row / column - flutter

I have a Column with some widgets inside.
If I trying to rotate one of them, it behaves like in the stack widget.
But I want to save layout of the rotated widget inside parent widget.
My question is how to revert (return) layout of the rotated widget inside Column, Row, etc... ?
The explanation and code example:
Right layout without rotation:
right layout screenshot
Column(
children: [
Text('Progress'),
SizedBox(height: 8.0,),
LinearProgressIndicator(
value: _counter / 100,
minHeight: 20,
),
],
)
Wrong layout with rotated widget:
wrong layout screenshot
Column(
children: [
Text('Progress'),
SizedBox(height: 8.0,),
Transform.rotate(
angle: -45 / (180 / pi),
child: LinearProgressIndicator(
value: _counter / 100,
minHeight: 20,
),
),
],
)
Wanted result screenshot
Thanks to Yeasin Sheikh for good idea,
task solved, final code here

Transform widget is just painting on UI, doesn't take actual space. You can wrap with SizedBox to provide the height.
If you can tweak the flexibility of your own from
deg2Ran(double deg) => deg / (180 / pi);
double boxHeight() {
final value = (((500 * deg2Ran(angle))) / (deg2Ran(-45))).abs();
return value < 20 ? 20.0 : value; // 20 your min height
}
class SX extends StatefulWidget {
const SX({Key? key}) : super(key: key);
#override
State<SX> createState() => _SXState();
}
class _SXState extends State<SX> {
double angle = -45;
deg2Ran(double deg) => deg / (180 / pi);
double boxHeight() {
final value = (((500 * deg2Ran(angle))) / (deg2Ran(-45))).abs();
return value < 20 ? 20.0 : value; // 20 your min height
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Slider(
min: -180,
max: 180,
value: angle,
onChanged: (v) {
setState(() {
angle = v;
});
}),
Text('Progress'),
SizedBox(
height: 8.0,
),
SizedBox(
height: boxHeight(),
child: Center(
child: Transform.rotate(
angle: deg2Ran(angle),
alignment: Alignment.center,
child: LinearProgressIndicator(
value: 22 / 100,
minHeight: 20,
),
),
),
),
],
),
);
}
}
You can handle overflow and some decoration

Related

How to animate multiple widgets while scrolling in Flutter

I need to implement custom animation while scrolling the list of users. See an example
My current view is composed of next elements:
SingleChildScrollView contains Column with:
Row of three top elements (each of is a custom widget with basically Stack of avatar, medal and details (Column))
Row as a table header
ListView of other users.
SingleChildScrollView is wrapped with NotificationListener for ScrollNotification which is populated to provider. The scroll value is then listened in every top element to perform animation of its own.
I would like to know some general path and algorithm here to take. I tried AnimatedPositioned but as soon as it is applied on multiple elements it causes performance issues. Should I use AnimationController or some more custom things so far? Any help would be appreciated.
As pskink mentioned, using SliverPersistentHeader can be archive, This is a demo widget to illustrate how it can be done. You need to play with value. My favorite part is using .lerp , doubleLerp... to position the items.
class Appx extends StatelessWidget {
const Appx({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: CustomSliverPersistentHeaderDelegate(),
),
const SliverToBoxAdapter(
child: SizedBox(
height: 3333,
width: 200,
),
),
],
),
);
}
}
class CustomSliverPersistentHeaderDelegate
extends SliverPersistentHeaderDelegate {
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return LayoutBuilder(builder: (_, constraints) {
final t = shrinkOffset / maxExtent;
final width = constraints.maxWidth;
final itemMaxWidth = width / 4;
double xFactor = -.4;
return ColoredBox(
color: Colors.cyanAccent.withOpacity(.3),
child: Stack(
children: [
Align(
alignment:
Alignment.lerp(Alignment.center, Alignment(xFactor, -.2), t)!
..x,
child: buildRow(
color: Colors.deepPurple, itemMaxWidth: itemMaxWidth, t: t),
),
Align(
alignment: Alignment.lerp(
Alignment.centerRight, Alignment(xFactor, 0), t)!,
child:
buildRow(color: Colors.red, itemMaxWidth: itemMaxWidth, t: t),
),
Align(
alignment: Alignment.lerp(
Alignment.centerLeft, Alignment(xFactor, .2), t)!,
child: buildRow(
color: Colors.amber, itemMaxWidth: itemMaxWidth, t: t),
),
],
),
);
});
}
Container buildRow(
{required Color color, required double itemMaxWidth, required double t}) {
return Container(
width: lerpDouble(itemMaxWidth, itemMaxWidth * .3, t),
height: lerpDouble(itemMaxWidth, itemMaxWidth * .3, t),
color: color,
);
}
/// you need to increase when it it not pinned
#override
double get maxExtent => 400;
#override
double get minExtent => 300;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}

Wrap stacked cards in flutter

I'm trying to stack some playing cards like the image shows. BUT I want the cards to wrap into a new line when there is no more space on the device.
This is what I have so far
class CollectionOfCards extends StatelessWidget {
List<PlayingCard> cards;
CollectionOfCards(this.cards);
#override
Widget build(BuildContext context) {
return Stack(
children: [
...cards
.asMap()
.map(
(key, value) => MapEntry(
key,
Positioned(
child: PlayingCardWidget(value),
left: 35.0 * key,
),
),
)
.values
.toList(),
],
overflow: Overflow.visible,
);
}
}
Any ideas how to do this? I imagine it is somewhat a common task.
Another idea would be to make a fan of cards that can always fit but I imagine thats harder to achieve!
Assume we want to have 2 rows of cards, each containing 10 cards. We need to use Positioned in a way that every card is positioned according to its index in the row(from 0 to 9) and its column(column 0 or column 1). So we need to set top and left in the Positioned widget. The 0.7 * cardHeight in top is for the vertical stacking, you can change that to cardHeight if you don't want to have vertical stacking.
Note: Set width and height of the cards using MediaQuery size. I didn't have the cards so I just made empty containers instead.
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _cards = [];
void _initCards() {
double cardHeight = 400 / 2;
double cardWidth = 2500 / 20;
for (int i = 0; i < 20; i++) {
_cards.add(Positioned(
left: ((i >= 10) ? i - 10 : i) * cardWidth / 2,
top: (i >= 10) ? 0.7 * cardHeight : 0,
child: Card(
elevation: 10,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Container(width: cardWidth, height: cardHeight),
),
));
}
}
#override
void initState() {
super.initState();
_initCards();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Stack(
children: _cards
),
decoration: BoxDecoration(
color: Colors.yellow,
),
),
);
}
}
Result:
Just for reference. I ended up with solution like this inspired by #Mobina's answer
class CollectionOfCards extends StatelessWidget {
final List<PlayingCard> cards;
final double stackSkew = 0.4;
CollectionOfCards(this.cards);
#override
Widget build(BuildContext context) {
final double widgetWidth = MediaQuery.of(context).size.width - 16;
final double cardWith = PlayingCardWidget.defaultWidth(context);
final int cardsPerRow =
(widgetWidth / cardWith / stackSkew - 1 / stackSkew).floor();
return Stack(
children: [
...cards
.asMap()
.map(
(index, card) {
return MapEntry(
index,
Positioned(
child: Draggable(
childWhenDragging: Container(),
feedback: PlayingCardWidget(card: card),
child: PlayingCardWidget(
card: card,
),
),
left: stackSkew *
cardWith *
(index - (cardsPerRow) * (index / cardsPerRow).floor()),
top: (card.heightFromWidth(cardWith) + 2) *
(index / cardsPerRow).floor(),
),
);
},
)
.values
.toList(),
],
overflow: Overflow.visible,
);
}
}
It seems to work flawless.
One way you can do this is by passing left as well as top parameter to Positioned calculated based on -
Screen width
Card index ("key" in your code)
Card width
Card height
See the demo on dartpad - https://dartpad.dev/d36e018c1f1c7a6cd4b91d5b09e69a7c
Try using Wrap() widget inside a Column()
Column(
children: <Widget>[
Wrap(
children: <Widget>[
//your card widget
]
)
]
)

Is there any way to get this kind of functionality in Flutter? Sticker View for Flutter?

I need to implement of such feature where Text or Any widget can be resized by user using a corner icons. I'm attaching a reference. Any help would be great.
I want to implement One Finger Rotate using the Icon.
I've tried implementing some similar features of few Android Sticker View library but I am not getting proper result.
I've tried the Following code.
import 'package:flutter/material.dart';
import 'package:matrix4_transform/matrix4_transform.dart';
import 'dart:math';
import 'package:render_metrics/render_metrics.dart';
class DragRotate extends StatefulWidget {
DragRotate({Key key}) : super(key: key);
#override
_DragRotateState createState() => _DragRotateState();
}
class _DragRotateState extends State<DragRotate> {
Matrix4 matrixForObject = Matrix4.identity();
double centerx, centery;
RenderParametersManager renderManager = RenderParametersManager();
GlobalKey sdsd = new GlobalKey();
#override
Widget build(BuildContext context) {
return Container(
child: Container(
child: Stack(
children: [
Center(
child: Transform(
origin: Offset(100, 100),
transform: matrixForObject,
child: Container(
key: sdsd,
height: 200,
width: 200,
color: Colors.amber,
child: Stack(
children: [
RenderMetricsObject(
manager: renderManager,
id: "w1",
child: Container(
margin: EdgeInsets.all(10),
color: Colors.amberAccent,
),
),
Positioned(
top: 0,
right: 0,
child: GestureDetector(
onPanStart: (p) {
//// THIS IS USED TO GET THE CENTER X CORDINATE OF THE ROTATING OBJECT
centerx =
renderManager.getRenderData("w1").center.x;
//// THIS IS USED TO GET THE CENTER Y CORDINATE OF THE ROTATING OBJECT
centery =
renderManager.getRenderData("w1").center.y;
},
onPanUpdate: (s) {
double angel = atan2(
s.globalPosition.dy - centery,
s.globalPosition.dx - centerx) *
180 /
pi;
setState(() {
matrixForObject = Matrix4Transform()
.rotateByCenterDegrees(angel, Size(1, 1))
.matrix4;
});
},
child: Icon(Icons.rotate_90_degrees_ccw))),
],
),
),
),
),
],
),
),
);
}
}

Layout not centered after implementing SizeConfig

I am using the SizeConfig method to set the height and width of my app's layout but for some reason, after implementing these functions, the layout is showing up all shifted to the left (i.e. not center). I do not see why this is happening, especially as without the SizeConfig functions the layout is centered.
Does anyone know what could be causing this?
class SubscribePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
final ThemeData theme = Theme.of(context);
return Consumer<SubscriptionModel>(
builder: (context, subscriptionModel, _) {
if (subscriptionModel.entitlements != null) {
final subscription = subscriptionModel.entitlements['subscription'];
if (subscription != null) {
final monthlySub = subscription.offerings["monthly"];
if (monthlySub != null) {
return Scaffold(
body: SingleChildScrollView(
child: Container(
height: SizeConfig.blockSizeVertical * 90,
width: SizeConfig.blockSizeHorizontal * 90,
child: Center(
child: Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height/2.5,
child: SvgPicture.asset(
'assets/svg/reading-student.svg'),
),
Padding(padding: EdgeInsets.all(MediaQuery.of(context).size.height/40)),
AutoSizeText(
'Your pizza.',
style: theme.textTheme.title,
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(MediaQuery.of(context).size.height/40)),
AutoSizeText(
'Your very own pizza.',
style: theme.textTheme.body1,
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(MediaQuery.of(context).size.height/35)),
PurchaseButton(product: monthlySub),
],
),
),
),
),
);
}
}
}
size_config.dart file:
import 'package:flutter/widgets.dart';
class SizeConfig {
static MediaQueryData _mediaQueryData;
static double screenWidth;
static double screenHeight;
static double blockSizeHorizontal;
static double blockSizeVertical;
void init(BuildContext context) {
_mediaQueryData = MediaQuery.of(context);
screenWidth = _mediaQueryData.size.width;
screenHeight = _mediaQueryData.size.height;
blockSizeHorizontal = screenWidth / 100;
blockSizeVertical = screenHeight / 100;
}
}
The SizeConfig.init() is working as expected. The reason your layout is aligned a little towards left is because you are centering your content inside a container which has a height and width equal to 90% of the screen height and width.
Explanation :-
Let's assume that the screen width is 500 and height is 1000. So after calling the SizeConfig.init() these are the values of the fields -
screenWidth = 500.0
screenHeight = 1000.0
blockSizeHorizontal = 5.0
blockSizeVertical = 10.0
Now when you set the size of the under mentioned container, you have the following values for width and height, 450.0 and 900.0 respectively.
body: SingleChildScrollView(
child: Container(
height: SizeConfig.blockSizeVertical * 90, // 900.0
width: SizeConfig.blockSizeHorizontal * 90, // 450.0
child: Center(
Then you center the rest of content inside this container, which means the content is centered according to the 450 x 900 block rather than the required 500 x 1000 (original screen) block. You can see this by changing the color of the container.
Result of changing the color of the container

Sizing elements to percentage of screen width/height

Is there a simple (non-LayoutBuilder) way to size an element relative to screen size (width/height)? For example: how do I set the width of a CardView to be 65% of the screen width.
It can't be done inside the build method (obviously) so it would have to be deferred until post build. Is there a preferred place to put logic like this?
This is a supplemental answer showing the implementation of a couple of the solutions mentioned.
FractionallySizedBox
If you have a single widget you can use a FractionallySizedBox widget to specify a percentage of the available space to fill. Here the green Container is set to fill 70% of the available width and 30% of the available height.
Widget myWidget() {
return FractionallySizedBox(
widthFactor: 0.7,
heightFactor: 0.3,
child: Container(
color: Colors.green,
),
);
}
Expanded
The Expanded widget allows a widget to fill the available space, horizontally if it is in a row, or vertically if it is in a column. You can use the flex property with multiple widgets to give them weights. Here the green Container takes 70% of the width and the yellow Container takes 30% of the width.
If you want to do it vertically, then just replace Row with Column.
Widget myWidget() {
return Row(
children: <Widget>[
Expanded(
flex: 7,
child: Container(
color: Colors.green,
),
),
Expanded(
flex: 3,
child: Container(
color: Colors.yellow,
),
),
],
);
}
Supplemental code
Here is the main.dart code for your reference.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("FractionallySizedBox"),
),
body: myWidget(),
),
);
}
}
// replace with example code above
Widget myWidget() {
return ...
}
FractionallySizedBox may also be useful.
You can also read the screen width directly out of MediaQuery.of(context).size and create a sized box based on that
MediaQuery.of(context).size.width * 0.65
if you really want to size as a fraction of the screen regardless of what the layout is.
This might be a little more clear:
double width = MediaQuery.of(context).size.width;
double yourWidth = width * 0.65;
Hope this solved your problem.
There are several possibilities:
1- The first one is the use of the MediaQuery :
Code :
MediaQuery.of(context).size.width //to get the width of screen
MediaQuery.of(context).size.height //to get height of screen
Example of use :
Container(
color: Colors.yellow,
height: MediaQuery.of(context).size.height * 0.65,
width: MediaQuery.of(context).size.width,
)
Output :
2- The use of FractionallySizedBox
Creates a widget that sizes its child to a fraction of the total available space.
Example :
FractionallySizedBox(
widthFactor: 0.65, // between 0 and 1
heightFactor: 1.0,
child:Container(color: Colors.red
,),
)
Output :
3- The use of other widgets such as Expanded , Flexible and AspectRatio and more .
You could build a Column/Row with Flexible or Expanded children that have flex values that add up to the percentages you want.
You may also find the AspectRatio widget useful.
There is many way to do this.
1. Using MediaQuery : Its return fullscreen of your device including appbar,toolbar
Container(
width: MediaQuery.of(context).size.width * 0.50,
height: MediaQuery.of(context).size.height*0.50,
color: Colors.blueAccent[400],
)
2. Using Expanded : You can set width/height in ratio
Container(
height: MediaQuery.of(context).size.height * 0.50,
child: Row(
children: <Widget>[
Expanded(
flex: 70,
child: Container(
color: Colors.lightBlue[400],
),
),
Expanded(
flex: 30,
child: Container(
color: Colors.deepPurple[800],
),
)
],
),
)
3. Others Like Flexible and AspectRatio and FractionallySizedBox
First get the size of screen.
Size size = MediaQuery.of(context).size;
After this you can get width and multiply it with 0.5 to get 50% of screen width.
double width50 = size.width * 0.5;
But problem generally comes in height, by default when we use
double screenHeight = size.height;
The height we get is global height which includes StatusBar + notch + AppBar height. So, in order to get the left height of the device, we need to subtract padding height (StatusBar + notch) and AppBar height from total height. Here is how we do it.
double abovePadding = MediaQuery.of(context).padding.top;
double appBarHeight = appBar.preferredSize.height;
double leftHeight = screenHeight - abovePadding - appBarHeight;
Now we can use following to get 50% of our screen in height.
double height50 = leftHeight * 0.5
MediaQuery.of(context).size.width
you can use MediaQuery with the current context of your widget and get width or height like this
double width = MediaQuery.of(context).size.width
double height = MediaQuery.of(context).size.height
after that, you can multiply it with the percentage you want
Use the LayoutBuilder Widget that will give you constraints that you can use to obtain the height that excludes the AppBar and the padding. Then use a SizedBox and provide the width and height using the constraints from the LayoutBuilder
return LayoutBuilder(builder: (context2, constraints) {
return Column(
children: <Widget>[
SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
...
if you are using GridView you can use something like Ian Hickson's solution.
crossAxisCount: MediaQuery.of(context).size.width <= 400.0 ? 3 : MediaQuery.of(context).size.width >= 1000.0 ? 5 : 4
Code :
MediaQuery.of(context).size.width //to get the width of screen
MediaQuery.of(context).size.height //to get height of screen
You can use the Align widget. The heightFactor and widthFactor parameters are multiplied by the size of the child widget. Here is an example that will make a widget with a fixed height in% ratio
Align(
alignment: Alignment.topCenter,
heightFactor: 0.63,
widthFactor: ,
child: Container(
width: double.infinity,
),
Use scaler to define the layout width and height in percentage
dependencies:
scaler: ^1.1.0+1
After setting this in pubspec.yaml you can use this by following the code -
import 'package:scaler/scaler.dart';
Example After import use this -
import 'package:scaler/scaler.dart';
#override
Widget build(BuildContext context) {
/**
* Container with 25% width of screen
* and 25% height of screen
*/
return Container(
height: Scaler.height(0.25, context),
width: Scaler.width(0.25, context),
child: Container()
);
}
To more detail about this
https://pub.dev/packages/scaler
For width
double width = MediaQuery.of(context).size.width;
double yourWidth = width * 0.75;
For Height
double height = MediaQuery.of(context).size.height;
double yourHeight = height * 0.75;
If you don't want static height and width just use Expanded widget\
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Expanded Row Sample'),
),
body: Center(
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.amber,
height: 100,
),
),
Container(
color: Colors.blue,
height: 100,
width: 50,
),
Expanded(
child: Container(
color: Colors.amber,
height: 100,
),
),
],
),
),
);
}
}
I am surprised that no one has yet suggested LayoutBuilder in 2023 which gives you access to the parent's width BoxConstraints.constraints.maxWidth, which is the most versatile method.
Expanded can only set the percentage of the available spacing, but what if you really want to set a percentage based on the actual parent's widget only, not the entire screen width, what if have a fixed width widget in a row, even a more complex, what if you also want an Expanded to expand the remaining Row.
MediaQuery.of(context).size.width is relative to the entire screen, not to the actual parent.
FractionallySizedBox works similarly but you can't put it in Row
Also, this method the perfect emulation of CSS % unit of measurement.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, BoxConstraints constraints) {
return SizedBox(
width: 470,
child: Row(
children: [
SizedBox(
width: 200,
child: Icon(
Icons.link,
color: Colors.white,
),
),
SizedBox(
width: constraints.maxWidth*0.6,
child: Icon(
Icons.message,
color: Colors.white,
),
),
Expanded(
child: Icon(
Icons.phone,
color: Colors.white,
),
),
SizedBox(
width: constraints.maxWidth*0.3,
child: Icon(
Icons.account_balance,
color: Colors.white,
),
),
],
),
);
}
);
}
}
In this example, we are setting a parent widget of 470 width with Row inside. In the Row, one element has a 200 fixed width, another with a 60% of the parent with 470, another with 30% of that same parent, and another expanding any remaining space.