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.
Related
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 ?
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. :)
I'm writing a flutter program in which you can move a widget with your finger by using GestureDetector instead of Draggable Widget.
Here is my code. I use Transform.translate to move.
import 'package:flutter/material.dart';
class AAA extends StatefulWidget {
#override
AAAState createState() => AAAState();
}
class AAAState extends State<AAA> {
double x = 0.0;
double y = 0.0;
#override
Widget build(BuildContext context) {
return Transform.translate(
offset: Offset(x, y),
child: GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
RenderBox getBox = context.findRenderObject();
final localOffset = getBox.globalToLocal(details.globalPosition);
setState(() {
x = localOffset.dx;
y = localOffset.dy;
});
},
child: ElevatedButton(
onPressed: null,
child: Text('Button'),
),
),
);
}
}
//Actually I'm using AAA widget in a List like this.
Container(
width: double.infinity,
height: double.infinity,
child: Column(
children: widgetList,
),
),
But in my code, when I move the widget with my finger, the origin of the widget is constantly fixed at top-left.
I tried using Transform instead of Transform.translate and changing origin: property, but it wasn't going well.
Like this:
gif image
I want the origin to be at the position where I touched first.
Sorry for my poor English, but give me some advice!
What I want to build is a widget that can make its child widget zoomable similar to the zoomable behavior.
Gestures I want to cover are
Pinch To Zoom
Double Tap to Zoom
Tap to get the local Position of the widget
Here is my widget plan:
ZoomableWidget(
child: // My custom Widget which should be zoomable.
)
Here is my current progress:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:vector_math/vector_math_64.dart';
class ZoomableWidget extends StatefulWidget {
final Widget child;
const ZoomableWidget({Key key, this.child}) : super(key: key);
#override
_ZoomableWidgetState createState() => _ZoomableWidgetState();
}
class _ZoomableWidgetState extends State<ZoomableWidget> {
double _scale = 1.0;
double _previousScale;
#override
Widget build(BuildContext context) {
return ClipRect(
child: GestureDetector(
onScaleStart: (ScaleStartDetails details) {
_previousScale = _scale;
},
onScaleUpdate: (ScaleUpdateDetails details) {
setState(() {
_scale = _previousScale * details.scale;
});
},
onScaleEnd: (ScaleEndDetails details) {
_previousScale = null;
},
child: Transform(
transform: Matrix4.diagonal3(Vector3(_scale.clamp(1.0, 5.0),
_scale.clamp(1.0, 5.0), _scale.clamp(1.0, 5.0))),
alignment: FractionalOffset.center,
child: widget.child,
),
),
);
}
}
The problem I have faced is, I cannot change the center of the pinch thus the image only zooms at (0,0) even after I zoom in the corner. Also, I cannot access horizontal drag and vertical drag to scroll the widget.
Thanks in advance.
As of Flutter 1.20, InteractiveViewer widget supports pan and Zoom out of the box.
To make any widget zoomable you need to simply wrap the child with InteractiveViewer.
#override
Widget build(BuildContext context) {
return Center(
child: InteractiveViewer(
panEnabled: false, // Set it to false to prevent panning.
boundaryMargin: EdgeInsets.all(80),
minScale: 0.5,
maxScale: 4,
child: FlutterLogo(size: 200),
),
);
}
This is working perfectly now, thanks for the reference #pskink.
import 'package:flutter/material.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
class ZoomableWidget extends StatefulWidget {
final Widget child;
const ZoomableWidget({Key key, this.child}) : super(key: key);
#override
_ZoomableWidgetState createState() => _ZoomableWidgetState();
}
class _ZoomableWidgetState extends State<ZoomableWidget> {
Matrix4 matrix = Matrix4.identity();
#override
Widget build(BuildContext context) {
return MatrixGestureDetector(
onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
setState(() {
matrix = m;
});
},
child: Transform(
transform: matrix,
child: widget.child,
),
);
}
}
I loved de resolution, you should put that in a packged in pub, you can even put some custom options, in my code I put doubletap to reset the zoom and locked the rotation.
import 'package:flutter/material.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
class ZoomableWidget extends StatefulWidget {
final Widget child;
const ZoomableWidget({Key key, this.child}) : super(key: key);
#override
_ZoomableWidgetState createState() => _ZoomableWidgetState();
}
class _ZoomableWidgetState extends State<ZoomableWidget> {
Matrix4 matrix = Matrix4.identity();
Matrix4 zerada = Matrix4.identity();
#override
Widget build(BuildContext context) {
return GestureDetector(
onDoubleTap: (){
setState(() {
matrix = zerada;
});
},
child: MatrixGestureDetector(
shouldRotate: false,
onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
setState(() {
matrix = m;
});
},
child: Transform(
transform: matrix,
child: widget.child,
),
),
);
}
}
You can use Zoom Widget Zoom Widget only need set a canvas size and child
Zoom(
width: 1800,
height: 1800,
child: Center(
child: Text("Happy zoom!!"),
)
);
As an alternative to MatrixGestureDetector, you can use the photo_view package: https://pub.dev/packages/photo_view
It has good limiting of the screen constraints so you can't drag the child off-screen, a bounce effect when hitting min/max size, and many other options.
It can be used with a custom child like this:
PhotoView.customChild(
child: <your widget>
)
I use zoom widget
first, add to pubsec.yaml :
dependencies:
zoom_widget: ^2.0.0
then import:
import 'package:zoom_widget/zoom_widget.dart';
Center text with max width and max height:
Zoom(
maxZoomWidth: 1800,
maxZoomHeight: 1800,
child: Center(
child: Text("Happy zoom!!"),
)
);
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