I've been working with Flutter rotation,
new Matrix4.identity()
..rotateX(degrees * 3.1415927 / 180),
but, the problem, I want it to be similar to the diagram below.
can I achieve a 3D-like rotation on the x-axis with Flutter?
even if there is a mapping from 3D to 2D or there are alternatives
that would get the same result.
thanks in advance.
Example image in OpenCV: How to calculate perspective transform for OpenCV from rotation angles?
thanks to this discussion, and this repo, and after more than a day seeking the answer,
static Matrix4 _pmat(num pv) {
return new Matrix4(
1.0, 0.0, 0.0, 0.0, //
0.0, 1.0, 0.0, 0.0, //
0.0, 0.0, 1.0, pv * 0.001, //
0.0, 0.0, 0.0, 1.0,
);
}
Matrix4 perspective = _pmat(1.0);
// then use it
new Center(
child: new Transform(
child: new FittedBox(
fit: BoxFit.fill,
child: LogoWidget(),
),
alignment: FractionalOffset.center,
transform: perspective.scaled(1.0, 1.0, 1.0)
..rotateX(math.pi - degrees * math.pi / 180)
..rotateY(0.0)
..rotateZ(0.0)
),
);
here is the result image
please read a little theory about this subject.
You can use Transform widget to apply a matrix onto it's child.
Here's an example combining Transform with the animation framework to rotate on X, Y, and Z directions.
import 'dart:math' as math;
import 'package:flutter/material.dart';
void main() {
runApp(
new MaterialApp(
home: new Home(),
),
);
}
class Home extends StatefulWidget {
#override
_HomeState createState() => new _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation<double> rotateX;
Animation<double> rotateY;
Animation<double> rotateZ;
#override
initState() {
super.initState();
animationController = new AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat();
print('bar');
rotateX = new Tween<double>(
begin: .0,
end: 1.0,
).animate(new CurvedAnimation(
parent: animationController,
curve: new Interval(.0, 1 / 3),
));
rotateY = new Tween<double>(
begin: .0,
end: 1.0,
).animate(new CurvedAnimation(
parent: animationController,
curve: new Interval(1 / 3, 2 / 3),
));
rotateZ = new Tween<double>(
begin: .0,
end: .5,
).animate(new CurvedAnimation(
parent: animationController,
curve: new Interval(2 / 3, 1.0),
));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new AnimatedBuilder(
animation: animationController,
builder: (context, child) {
final card = new SizedBox(
width: 42.0,
height: 42.0,
child: new Card(
color:
animationController.value >= 1 / 6 && animationController.value <= 3 / 6 ? Colors.blue : Colors.red,
),
);
return new Transform(
transform: new Matrix4.rotationX(rotateX.value * math.pi)
..multiply(new Matrix4.rotationY(rotateY.value * math.pi))
..multiply(new Matrix4.rotationZ(rotateZ.value * math.pi)),
alignment: Alignment.center,
child: card,
);
},
),
),
);
}
}
class MyTransform extends StatefulWidget {
const MyTransform({Key? key}) : super(key: key);
#override
_MyTransformState createState() => _MyTransformState();
}
class _MyTransformState extends State<MyTransform> {
double x = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.003) // col = 2, row = 3 & 0.003 = depth perception in the Z direction
..rotateX(x), // (Both are equal because both are 4D identity matrix)
// transform: Matrix4(
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0.003,
// 0, 0, 0, 1,
// )..rotateX(x),
alignment: FractionalOffset.center,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
x = x + details.delta.dy / 100;
});
},
child: Container(
color: Colors.red,
height: 200.0,
width: 200.0,
),
),
),
),
);
}
}
Related
how can I achieve something like this in Flutter:
Just replace Text to Image.network
Use below code
UnicornOutlineButton(
strokeWidth: 2,
radius: 24,
gradient: LinearGradient(
colors: [Colors.black, Colors.redAccent]),
child: Text('OMG', style: TextStyle(fontSize: 16)),
onPressed: () {},
),
Make new class with the name UnicornOutlineButton
class UnicornOutlineButton extends StatelessWidget {
final _GradientPainter _painter;
final Widget _child;
final VoidCallback _callback;
final double _radius;
UnicornOutlineButton({
#required double strokeWidth,
#required double radius,
#required Gradient gradient,
#required Widget child,
#required VoidCallback onPressed,
}) : this._painter = _GradientPainter(strokeWidth: strokeWidth, radius: radius, gradient: gradient),
this._child = child,
this._callback = onPressed,
this._radius = radius;
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: _painter,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: _callback,
child: InkWell(
borderRadius: BorderRadius.circular(_radius),
onTap: _callback,
child: Container(
constraints: BoxConstraints(minWidth: 88, minHeight: 48),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_child,
],
),
),
),
),
);
}
}
class _GradientPainter extends CustomPainter {
final Paint _paint = Paint();
final double radius;
final double strokeWidth;
final Gradient gradient;
_GradientPainter({#required double strokeWidth, #required double radius, #required Gradient gradient})
: this.strokeWidth = strokeWidth,
this.radius = radius,
this.gradient = gradient;
#override
void paint(Canvas canvas, Size size) {
// create outer rectangle equals size
Rect outerRect = Offset.zero & size;
var outerRRect = RRect.fromRectAndRadius(outerRect, Radius.circular(radius));
// create inner rectangle smaller by strokeWidth
Rect innerRect = Rect.fromLTWH(strokeWidth, strokeWidth, size.width - strokeWidth * 2, size.height - strokeWidth * 2);
var innerRRect = RRect.fromRectAndRadius(innerRect, Radius.circular(radius - strokeWidth));
// apply gradient shader
_paint.shader = gradient.createShader(outerRect);
// create difference between outer and inner paths and draw it
Path path1 = Path()..addRRect(outerRRect);
Path path2 = Path()..addRRect(innerRRect);
var path = Path.combine(PathOperation.difference, path1, path2);
canvas.drawPath(path, _paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this;
}
Im using the flutter_neumorphics dependency https://pub.dev/packages/flutter_neumorphic
to make my circular Neumorphic containers. However I'm facing an issue when Im trying to resize it.
I want the outer circle to be smaller and more compact.
Here is the code I've tried:
Widget build(BuildContext context) {
final percentage = 50.0;
return Stack(
children: <Widget>[
Align(
child: Neumorphic(
boxShape: NeumorphicBoxShape.circle(),
padding: EdgeInsets.all(80),
style: NeumorphicStyle(
depth: NeumorphicTheme.embossDepth(context),
),
child: CustomPaint(
painter: NeuProgressPainter(
circleWidth: 20,
completedPercentage: percentage,
defaultCircleColor: Colors.transparent,
),
child: Center(),
),
),
),
Align(
child: Neumorphic(
boxShape: NeumorphicBoxShape.circle(),
padding: EdgeInsets.all(80),
style: NeumorphicStyle(
color: Colors.white,
depth: NeumorphicTheme.depth(context),
),
),
),
],
);
}
Where the NeuProgressPainter is defined as:
class NeuProgressPainter extends CustomPainter {
//
Color defaultCircleColor;
Color percentageCompletedCircleColor;
double completedPercentage;
double circleWidth;
NeuProgressPainter(
{this.defaultCircleColor,
this.percentageCompletedCircleColor,
this.completedPercentage,
this.circleWidth});
getPaint(Color color) {
return Paint()
..color = color
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = circleWidth;
}
#override
void paint(Canvas canvas, Size size) {
Paint defaultCirclePaint = getPaint(defaultCircleColor);
Offset center = Offset(size.width / 2, size.height / 2);
double radius = min(size.width / 2, size.height / 2);
Rect boundingSquare = Rect.fromCircle(center: center, radius: radius);
paint(
List<Color> colors,
) {
final Gradient gradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomRight,
colors: colors,
);
return Paint()
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = circleWidth
..shader = gradient.createShader(boundingSquare);
}
canvas.drawCircle(center, radius, defaultCirclePaint);
double arcAngle = 2 * pi * (completedPercentage / 100);
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-pi / 2,
arcAngle,
false,
paint(
[
kcolor,
kDarkOrange,
kOrange,
],
),
);
}
#override
bool shouldRepaint(CustomPainter painter) {
return true;
}
}
This is what my output looks like:
Im trying to achieve something similar to this inspiration:
How about this?
Padding(
padding: const EdgeInsets.all(80.0),
child: Align(
child: Neumorphic(
boxShape: NeumorphicBoxShape.circle(),
padding: EdgeInsets.all(20),
style: NeumorphicStyle(
depth: NeumorphicTheme.embossDepth(context),
),
child: CustomPaint(
painter: NeuProgressPainter(
circleWidth: 20,
completedPercentage: percentage,
defaultCircleColor: Colors.transparent,
),
child: Center(),
),
),
),
),
I have a Draggable Container with the following decoration.
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [ThemeColors.red, ThemeColors.yellow, ThemeColors.green])
I would like to have it animated that my frame is getting greener or redder, further I drag left or right.
Here's a simple example to detect horizontal drag and change between colors inside your gradient:
class GradientScreen extends StatefulWidget {
#override
_GradientScreenState createState() => _GradientScreenState();
}
class _GradientScreenState extends State<GradientScreen> {
var percentage = 0.0;
#override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: Text('Animated Drag Gradient'),
centerTitle: true,
),
body: GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() => percentage = (details.localPosition.dx - 0) / (width - 0));
},
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
_colorTween(Colors.green[900], Colors.red[900]),
Colors.yellow,
_colorTween(Colors.green[900], Colors.red[900])
],
)
),
),
),
);
}
Color _colorTween(Color begin, Color end) {
return ColorTween(begin: begin, end: end).transform(percentage);
}
}
The result of this easy implementation is the following:
I'm trying to draw a shape like this with some text and data in it.
I am using CustomPaint for this
CustomPaint(
painter: new RightBox(context),
child: container()
),
Container container() {
return Container(
height: 200,
width: 200,
color: Colors.black,
margin: EdgeInsets.only(left: 20),
child: Center(child: Text("Hi Khushal",
style: TextStyle(
color: Colors.white
),
),)
);
}
class RightBox extends CustomPainter {
RightBox(this.context);
BuildContext context;
#override
void paint(Canvas canvas, Size size) {
size = MediaQuery.of(context).size;
Paint paint = Paint()
..color = Colors.black;
Path path = Path()
..moveTo(size.width * 0.02 + size.width * 0.5, size.height * 0.85)
..lineTo(size.width * 0.05 + size.width * 0.5, size.height * 0.70)
..lineTo(size.width * 0.45 + size.width * 0.5, size.height * 0.67)
..lineTo(size.width * 0.48 + size.width * 0.5, size.height * 0.85);
path.close();
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(RightBox oldDelegate) {
return true;
}
}
I want the text to be shown in the bottom right CustomPaint Widget,
I am passing a child Container to the CustomPaint but instead, I'm getting a widget positioned separately in the stack.
I know I'm doing something wrong, please help me achieve the UI.
Your painter is taking the full size of the screen. Hence, you need to align the container to put it inside the painter. Here is one way to do it. There are several ways to do it.
You put your container and painter inside a stack widget. Align the container according to your needs. Make sure to give a few padding and margin to get a more accurate result.
Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 200,
width: 200,
alignment: Alignment.bottomRight,
child: Center(
child: Text(
"Hi Khushal",
style: TextStyle(color: Colors.black),
),
)),
),
CustomPaint(
painter: RightBox(context),
),
],
),
I don't really know linear algebra, I'm trying to learn it because of this issue now.
I'm pretty sure it's kind of simple question for those who knows linear algebra.
I'll try to explain and show some pictures, videos and code to describe it better.
First, my goal is pretty simple at first glance.
Is to create this sort of animation
Now I managed to get almost there
Widget getWidget(Type type)
{
Matrix4 _pmat(num pv) {
return new Matrix4(
1.0, 0.0, 0.0, 0.0, //
0.0, 1.0, 0.0, 0.0, //
0.0, 0.0, 1.0, pv * 0.001, //
0.0, 0.0, 0.0, 1.0,
);
}
double delta = _panDown ? _delta : _animationOffset.value;
Matrix4 perspective = _pmat(0.35);
if (type == Type.NEXT)
{
delta -= MAX_DELTA;
}
double xOffset = ((delta / 70) / 2.5) * (deviceWidth / 2);
double zOffset = (delta / 70 / (math.pi - 0.55)).abs() * 180.0;
double rotateY = (math.pi - (delta / 3) * math.pi / 180) - math.pi;
if (type == Type.NEXT)
{
xOffset = ((delta / 70) / 2.5) * (deviceWidth);
zOffset *= 2;
}
perspective.translate(xOffset, 0.0, zOffset);
double angleOffset = delta * -0.5;
return new Transform(
alignment: FractionalOffset.center,
transform: perspective.scaled(1.0, 1.0, 1.0)
..rotateX(0.0)
..rotateY(rotateY)
..rotateZ(0.0),
child: new Container(color: Colors.red),
);
}
As you can see I'm currently moving each "page" with "magic numbers" and I call it magic numbers because I'm pretty dam noob at linear algebra and what I've done here is with the little understanding I have + trial and error. Clearly not the right way.
I have the delta which is basically just an offset taken from the user's GestureDetector to just play with it.
Now, with all the samples of this cube transition I saw let's say for example in CSS, they just rotated/translated each individual side. And then moved/rotated the "camera" or the parent I guess ?
I tried to do that method as well:
Like this:
Widget getWidget(Type type)
{
AlignmentGeometry alignmentGeometry;
Matrix4 matrix4;
switch (type)
{
case Type.PREVIOUS:
alignmentGeometry = FractionalOffset.center;
matrix4 = Matrix4.identity()
..setEntry(3, 2, 0.001) // perspective
..rotateY(-90.0 * math64.degrees2Radians)
..translate(deviceWidth / 2, 0.0, deviceWidth / 2);
break;
case Type.CURRENT:
alignmentGeometry = FractionalOffset.center;
matrix4 = Matrix4.identity()
..setEntry(3, 2, 0.001);
break;
case Type.NEXT:
alignmentGeometry = FractionalOffset.center;
matrix4 = Matrix4.identity()
..setEntry(3, 2, 0.001) // perspective
..rotateY(90.0 * math64.degrees2Radians)
..translate(-deviceWidth / 2, 0.0, deviceWidth / 2);
break;
}
return new Transform(
alignment: alignmentGeometry,
transform: matrix4,
child: new Container(
color: type == Type.NEXT ? Colors.green : type == Type.PREVIOUS ? Colors.red : Colors.black.withOpacity(0.5),
),
);
}
And then let's simply rotate the parent right ?
Widget build(BuildContext context)
{
List<Widget> widgets = [];
widgets.add(getWidget(Type.PREVIOUS));
widgets.add(getWidget(Type.CURRENT));
widgets.add(getWidget(Type.NEXT));
return new Scaffold(
key: _scaffoldKey,
body: new Container(
color: Colors.black,
child: new GestureDetector(
child: new Container(
color: Colors.white,
child: new Transform(
alignment: FractionalOffset.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.0001)
..rotateY(45.0 * math64.degrees2Radians),
child: new Stack(
children: widgets,
),
)
),
)
)
);
}
No:
Having it 90 degrees will make it all disappear.
Now, I do understand that I need sort of "pivot rotation point" or change the "origin" or "anchor point" not sure even how to call it.
So to picture it, let's say for example we also have a bottom side. so I want the center of that bottom side (let's say for example in a cube). So it will look like this:
Now if I'll rotate that "origin point" in the Y axis I should get the right animation right ?