i have the following full simple code
import 'dart:developer';
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
var globalKey = GlobalKey();
getCurrentOffest(){
RenderBox renderBoxRed = globalKey.currentContext!.findRenderObject() as RenderBox;
log(renderBoxRed.localToGlobal(Offset.zero).toString());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: RotatedBox(
quarterTurns: 0,
child: GestureDetector(
onTap: (){
getCurrentOffest();
},
child: Container(
key: globalKey,
color: Colors.red,
width: 250,
height: 250,
),
),
),
);
}
}
Now as shown , i provided global key to my red Container in order to know the current offset of where my red Container is located , as long as my container is not rotated yet (quarterTurns: 0,) it prints correct result . when i tap on it.. it show the following
Offset(0.0, 0.0)
and that's seems correct .
But when i rotate my Container using quarterTurns from zero to 2 and get to know the current offset it prints the following
Offset(250.0, 250.0)
How ? Why? Where did these values come from?
my container is still at his offset also i gave the width and height same value in order to make sure there is no any effect of rotation !!!
why this happening ? How Could i prevent these weird values ?
EDIT
that's happening because offset collocate the values according to the firt point in widget (dx ,dy)and when widget get rotated so the first point is also rotated .. i think the best way is to know the center point offset in widget to avoid this behavior but how to know the center offset ?
Related
let's say I have a Stack that takes all the device's width, in this Stack, I have a TextButton widget which I want to shift by 100% to hide on the right side of the Stack. How could I achieve that ?
Context : The goal is to make a component that the user can swipe, when swiping to the left on this component, the TextButton widget appears from the right. Hope that was clear.
There's a flutter widget for that, called dismissible, take a look on those links:
https://www.youtube.com/watch?v=iEMgjrfuc58
https://docs.flutter.dev/cookbook/gestures/dismissible
You can use Animated Container and wrap it inside the gesture detector. Detect the horizontal drag/swipe and then increase the width of the container. It will give the impression that the text button is appearing from the right.
Try this:
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
double _width = 0;
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dx > 0){
print("Dragging in +X direction");
setState(() {
_width = size.width;
});
}
else
print("Dragging in -X direction");
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
width: _width,
height: 100,
child: Text('Your text goes here...'),
),
);
}
}
Hope it helps. :)
ListView.builder Need a size to be displayed, so often we put a container as parent of ListView.builder to display correctly the list.
The problem are 2.
The height we set are different per different device and I noticed problem with the last element of the list if there is a BottomNavigation bar that will cover the last element of the list
this is an image:
sorry if I cover but for privacy I need...
anyway.. as you can see the last item of the list is covered by the BottomNav Bar
how to define and fix this issue?
Of could I could resize the container and make it more smaller but the problem will be with different device.
you can wrap your ListView.builder with Expanded instead of Container. and more better to put it in Column then wrap it with Expanded widget.
you should use MediaQuery under BuildContext then you can get the height and the width then do some math and that all you need to make a responsive app the picture of the app
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: TheSize(),
)),
);
}
}
class TheSize extends StatelessWidget {
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
var height = size.height;
var width = size.width;
return Container(
height: height / 2,
width: width / 1.5,
color: Colors.blue,
child: Text('the height = $height | the width = $width '),
);
}
}
I would like to put the following widgets into a SingleChildScrollView, but I want the blue box to lie right below the fold when the page renders. The user can choose to scroll it into view as needed, but to start with, it is out of the view.
I see a simple way of achieving this if I could calculate the space my red and green boxes are occupying in the screen and setting the top margin of the red box to the remaining space. A complication is that the height of the red and green boxes is dynamic based on their content, as well as the width of the boxes in portrait vs landscape layouts.
Perhaps I should use a SizedBox instead of adjusting the margin of my red box, which won't be a problem. But I still need to be able to calculate the height of the blank space at the top of my layout.
#pskink had the right idea. Here is what I did with it in code to achieve the layout.
enum AuthWidgets { form, action, disclaimer }
class AuthLayoutDelegate extends MultiChildLayoutDelegate {
AuthLayoutDelegate({#required screenHeight}) : _screenHeight = screenHeight;
double _overlap = 50;
double _screenHeight;
#override
void performLayout(Size size) {
Size formSize;
Size actionSize;
Size disclaimerSize;
// if (formSize == null && hasChild(AuthWidgets.form)) {
formSize = layoutChild(AuthWidgets.form, BoxConstraints.loose(size));
// }
// if (actionSize == null && hasChild(AuthWidgets.action)) {
actionSize = layoutChild(AuthWidgets.action, BoxConstraints.loose(size));
// }
// if (disclaimerSize == null && hasChild(AuthWidgets.disclaimer)) {
disclaimerSize =
layoutChild(AuthWidgets.disclaimer, BoxConstraints.loose(size));
// }
if (formSize != null && actionSize != null && disclaimerSize != null) {
print('laying out children');
// Need the height of the form and action area, minus the overlap
var aboveTheFold = formSize.height + actionSize.height - _overlap;
var offsetY = _screenHeight - aboveTheFold;
offsetY += formSize.height - _overlap;
positionChild(AuthWidgets.action, Offset(0, offsetY));
offsetY += actionSize.height;
positionChild(AuthWidgets.disclaimer, Offset(0, offsetY));
positionChild(AuthWidgets.form, Offset(0, _screenHeight - aboveTheFold));
}
}
#override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
return false;
}
}
class AuthLayout extends StatelessWidget {
const AuthLayout({#required this.screenHeight, Key key}) : super(key: key);
final double screenHeight;
#override
Widget build(BuildContext context) {
return CustomMultiChildLayout(
delegate: AuthLayoutDelegate(screenHeight: screenHeight),
children: [
LayoutId(id: AuthWidgets.action, child: ActionMenu()),
LayoutId(id: AuthWidgets.disclaimer, child: Disclaimer()),
LayoutId(id: AuthWidgets.form, child: SignInForm()),
],
);
}
}
class SignInForm extends StatelessWidget {
const SignInForm({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16),
child: Container(
color: Colors.red.withOpacity(0.5),
height: 350,
child: Center(
child: Text('SignInForm'),
),
),
);
}
}
class Disclaimer extends StatelessWidget {
const Disclaimer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 300,
color: Colors.blue.withOpacity(0.5),
child: Center(child: Text('Disclaimer')),
);
}
}
class ActionMenu extends StatelessWidget {
const ActionMenu({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 200,
color: Colors.green.withOpacity(0.5),
child: Center(child: Text('ActionMenu')),
);
}
}
By using the delegate and the performLayout function, I was able to position my widgets precisely where I needed them. I did want my red area to appear "over" the top of the green area, so I had to ensure it was added to the CustomMultiChildLayout last (or at least after the green box).
Now I have been struggling with getting this to scroll on the page, but that is a question for a different thread.
I implemented an effect that I found on this medium article https://medium.com/flutter/perspective-on-flutter-6f832f4d912e
that allows you rotate widgets in 3d space. My only problem with this is that all of my widgets seem to have no thickness in the z direction so unsightly things like this happen:
My unrotated logo looks like this:
And this is the code I used to create this rotation effect:
import 'package:flutter/material.dart';
class PerspectiveContainer extends StatefulWidget {
final Widget child;
PerspectiveContainer({Key key, #required this.child}) : super(key: key);
#override
_PerspectiveState createState() => _PerspectiveState();
}
class _PerspectiveState extends State<PerspectiveContainer> {
Offset _offset = Offset.zero;
#override
Widget build(BuildContext context) {
return Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001) // perspective
..rotateX(0.004 * _offset.dy)
..rotateY(-0.004 * _offset.dx),
alignment: FractionalOffset.center,
child: GestureDetector(
onPanUpdate: (details) => setState(() => _offset += details.delta),
onDoubleTap: () => setState(() => _offset = Offset.zero),
child: widget.child,
),
);
}
}
So would it be possible to give my BINGO widget thickness in the z direction?(The bingo logo was made with a Table widget)
Material Design works sometimes with shadows and elevation here, which would give you some 3D look, although it is actually 2d. https://material.io/design/environment/elevation.html#elevation-in-material-design
Some Widgets in Flutter have an elevation attribute, e.g. Card.
I imagine a new kind of screen, and I would like to do it with Flutter since it's very powerful for rendering fast and smoothly.
I want to achieve a kind of infinite screen with kind of square or zone where you can move into. Actually exactly like a map (in fact not infinite but very large) but where I can:
Drag and translate
Zoom in and out
Click and press on the different component of the screen (square or whatever)
I imagine use GestureDetector on my widget "map" combine with transform on each component insde and refresh the screen after each move, or redrawing each component with draw but I'm not sure it's the best way to follow with this.
Thanks for helping if you have any idea !!
I've implemented part of things you asked for except for the "infinite map".
The code is quite beefy so I've described this stuff in an article on Medium.
It allows:
move map by dragging
place new objects on the map
zoom in/out
click objects
GitHub repo.
Interesting proposal. I don't have the implementation, after all, it's up to you but I have some pointers.
Translation, I imagine, can easily be handled by 2 nested ListViews. One, that scrolls X and one that scrolls in Y direction. ScrollController can be queries for all kinds of info.
Zoom is also fairly easy at first blick: you can wrap the entire screen in a Transform.scale() widget.
You could wrap each tappable widget in a GuestureDetector, query for their RenderBox to get their position on screen in local or global coordinates, get their size.
Note: in games, there is a concept called clipping distance. Figuring out how to implement that in Flutter is going to be a fun challenge. It allows you not to render those Widgets that are too small, you zoomed out a lot eg. Let me know how it goes! Curious.
The InteractiveViewer widget
One solution could be using the InteractiveViewer widget with its constrained property set to false as it will out of the box support:
Drag and translate
Zooming in and out - Simply set minScale and maxScale
Clicking and pressing widgets like normal
InteractiveViewer as featured on Flutter Widget of the Week: https://www.youtube.com/watch?v=zrn7V3bMJvg
Infinite size
Regarding the question's infinite size part, a maximum size for the child widget must be specified, however this can be very large, so large that it is actually hard to re-find widgets in the center of the screen.
Alignment of the child content
By default the child content will start from the top left, and panning will show content outside the screen. However, by providing a TransformationController the default position can be changed by providing a Matrix4 object in the constructor, f.ex. the content can be center aligned if desired this way.
Example code
The code contains an example widget that uses the InteractiveViewer to show an extremely large widget, the example centers the content.
class InteractiveViewerExample extends StatefulWidget {
const InteractiveViewerExample({
Key? key,
required this.viewerSize,
required this.screenHeight,
required this.screenWidth,
}) : super(key: key);
final double viewerSize;
final double screenHeight;
final double screenWidth;
#override
State<InteractiveViewerExample> createState() =>
_InteractiveViewerExampleState();
}
class _InteractiveViewerExampleState extends State<InteractiveViewerExample> {
late TransformationController controller;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: InteractiveViewer.builder(
boundaryMargin: const EdgeInsets.all(40.0),
minScale: 0.001,
maxScale: 50,
transformationController: controller,
builder: (BuildContext context, vector.Quad quad) {
return Center(
child: SizedBox(
width: widget.viewerSize,
height: widget.viewerSize,
child: const InteractiveViewerContent(),
),
);
},
),
),
);
}
#override
void initState() {
super.initState();
// Initiate the transformation controller with a centered position.
// If you want the InteractiveViewer TopLeft aligned remove the
// TransformationController code, as the default controller in
// InteractiveViewer does that.
controller = TransformationController(
Matrix4.translation(
vector.Vector3(
(-widget.viewerSize + widget.screenWidth) / 2,
(-widget.viewerSize + widget.screenHeight) / 2,
0,
),
),
);
}
}
// Example content; some centered and top left aligned widgets,
// and a gradient background.
class InteractiveViewerContent extends StatelessWidget {
const InteractiveViewerContent({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
TextStyle? style = Theme.of(context).textTheme.headline6;
return Container(
padding: const EdgeInsets.all(32.0),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[Colors.orange, Colors.red, Colors.yellowAccent],
),
),
child: Stack(
alignment: Alignment.center,
children: [
Align(
alignment: Alignment.topLeft,
child: SelectableText("Top Left", style: style),
),
SelectableText("Center", style: style),
],
),
);
}
}
Usage
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' as vector;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: InteractiveViewerScreen(),
);
}
}
class InteractiveViewerScreen extends StatelessWidget {
const InteractiveViewerScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: InteractiveViewerExample(
viewerSize: 50000,
screenHeight: MediaQuery.of(context).size.height,
screenWidth: MediaQuery.of(context).size.width,
),
),
);
}
}
What does the code do