So, I'm trying to develop a photobook app which shows the photos in front and back, Now I used card to flip but I need a animation to make it look natural like having a curvature for the card during the flip and the flip should be like turning a stiffened page.
This is what I have tried
Widget __transitionBuilder(Widget widget, Animation<double> animation) {
final rotateAnim = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotateAnim,
child: widget,
builder: (context, widget) {
final isUnder = (ValueKey(_showFrontSide) != widget.key);
var tilt = ((animation.value - 0.5).abs() - 0.5) * 0.003;
tilt *= isUnder ? -1.0 : 1.0;
final value = isUnder ? min(rotateAnim.value, pi / 2) : rotateAnim.value;
return Transform(
transform: Matrix4.rotationY(value)..setEntry(3, 0, tilt),
child: widget,
alignment: Alignment.center,
);
},
);
}
But it produces the result as follows. Here the card depth is felt but need curvature at the center is it possible to do so?
The result I need is like this image
The curvature at the center is it possible without curvature?
Related
In my application, I am showing a video on the screen.
When application starts, it show full screen of the video, overlay with some information about the video.
This screen is called glass screen.
When the user click a button 3/4 of the screen is adjusted to show the video and bottom part show some other information.
The screen height adjustment is done with "Expanded" flex set to 3/4, by the parent widget and it is working fine.
In both cases the video must fill the height of the screen and user can zoom and pan to see parts outside the screen. I am using "InteractiveViewer" for that.
"WidgetSize" allow me to get the size of the area a Widget occupies.
I have several factors to consider area size video going to display and size of the video
I need to maintain the aspect ratio of the video.
When the glass door is closed, it seems the video is displaying correctly.
But when it is closed (3/4 area) I can't figure out how to find out the correct scale for the video and how to adjust the position of it with "InteractiveViewer".
This is just a test to figure out how to use it
_viewTransformationController.value.setIdentity();
_viewTransformationController.value.setEntry(0, 0, 2);
_viewTransformationController.value.setEntry(1, 1, 2);
_viewTransformationController.value.setEntry(2, 2, 2);
_viewTransformationController.value.setEntry(1, 3, -75);
zoom level 2 is not correct value. -75 is to adjust the Y position (not really sure about this)
Following is the full code.
ScaleVideoController _scaleController;
final _viewTransformationController =
TransformationController(Matrix4.identity());
#override
void initState() {
super.initState();
_scaleController = widget.controller;
this._scaleController.videoSizeChanged = (double width, double height) {
this._videoSize = Size(width, height);
this._updateScale();
};
this._scaleController.glassDoorOpenTriggered = () {
this._glassDoorClosed = false;
this._updateScale();
};
}
_updateScale() {
final physicalScreenSize = window.physicalSize;
if (this._glassDoorClosed) {
this._displayVideoHeight = this._areaSize.height;
this._displayVideoWidth =
this._areaSize.width * this._displayVideoHeight / _areaSize.height;
this._scale = physicalScreenSize.height / this._displayVideoHeight;
} else {
_viewTransformationController.value.setIdentity();
_viewTransformationController.value.setEntry(0, 0, 2);
_viewTransformationController.value.setEntry(1, 1, 2);
_viewTransformationController.value.setEntry(2, 2, 2);
_viewTransformationController.value.setEntry(1, 3, -75);
}
setState(() {});
}
Widget _buildScaledRemoteVideo() {
return WidgetSize(
onChange: (Size s) {
this._areaSize = s;
this._updateScale();
},
child: InteractiveViewer(
transformationController: _viewTransformationController,
scaleEnabled: !_glassDoorClosed,
minScale: 0.1,
constrained: false,
child: Transform.scale(
scale: _glassDoorClosed ? _scale : 1,
child: Container(
width: this._displayVideoWidth,
height: this._displayVideoHeight,
child: widget.remoteVideoRenderer(),
),
),
),
);
}
This is how it should look like,
The read area is the video..
Can anyone provide some advice on how to adjust the screen when it
I have a panel as a tabbar at the bottom with custom animation and with page names. I have 5 pages that can be switched with the next animation: the new page should slowly increase visibility (FadeTransition) and the old page should move right or left depending on what page should be next. If the new page is placed on the right hand of the current page then the slide animation should go left, if it's placed on the left hand then the slide animation should go right.
As far as I know, there is no possibility of using secondaryAnimation to use different animations based on the next page. Because when we create the current page, we don't know what closing animation for the current page should be applied, we can only use one behavior for all transitions.
In this case, maybe anyone has suggestions on how can I implement this behavior?
I've drawn a scheme for the easiest understanding.
I've made this behavior by using the provider.
Before calling Navigator.pushReplacementNamed, when I know what the next page should be, I've defined the necessary direction that the old page should move. Remember it in my model variable. I've taken then this value right in my custom PageRouteBuilder with help of the Provider.
class MainPageRoute extends PageRouteBuilder {
// ignore: unused_field
final Widget _page;
MainPageRoute(this._page)
: super(
pageBuilder: (context, animation, secondaryAnimation) => _page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
//here I've take necessary direction true = right, false = left
final _isAnimationDirectionRight =
context.read<ContextHandlingModel>().isAnimationDirectionRight;
const _begin = Offset.zero;
final _end = (_isAnimationDirectionRight)
? const Offset(0.1, 0.0)
: const Offset(-0.1, 0.0);
const _offsetCurve = Curves.fastOutSlowIn;
const _opacityCurve = Curves.easeIn;
final _offcetTween = Tween(begin: _begin, end: _end).chain(
CurveTween(curve: _offsetCurve),
);
final _opacityOldTween = Tween(begin: 1.0, end: 0.0)
.chain(CurveTween(curve: _opacityCurve));
final _opacityNewTween = Tween(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: _opacityCurve));
return SlideTransition(
position: _offcetTween.animate(secondaryAnimation),
child: FadeTransition(
opacity: _opacityOldTween.animate(secondaryAnimation),
child: FadeTransition(
opacity: _opacityNewTween.animate(animation),
child: child,
),
),
);
},
);
}
I would like to be able to always pop a screen from top to bottom. This is already working if I push like this:
static Route _createRouteWithBottomToTopTransition(Widget destinationPage) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => destinationPage,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var begin = Offset(0.0, 1.0);
var end = Offset.zero;
var curve = Curves.ease;
var tween =
Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}
If I push with this Route I can simply call Navigator.pop(context) and the animation is as expected from top to bottom.
Problem: I don't always push with the customTransition. In some cases I simply push like this: Navigator.of(context).pushNamed('/page1') because for push I want the standard slide in fro the right animation. But page1 should still be pop from top to bottom. Is this possible? And if so, how? I tried searching for it but couldn't find anything.. I thought about creating a different customPushTransition but could not get a top to bottom animation...
Happy for every help! Let me know if you need more info!
I'm trying to build a Flutter Web version of the threejs Periodic Table Helix view (see here: https://mrdoob.com/lab/javascript/threejs/css3d/periodictable/ and click on "Helix")
Currently, I place all the element tiles in a stack, and position and rotate them with matrix transformations. See below code:
#override
Widget build(BuildContext context) {
double initialRotateX = (widget.atomicNumber - 1) *
(math.pi / Constants.numberOfElementsPerHalfCircle);
return Transform(
transform: Matrix4.identity()
..translate(
math.sin((widget.atomicNumber - 1) *
(math.pi / Constants.numberOfElementsPerHalfCircle)) *
Constants.radius,
widget.atomicNumber * 4,
-math.cos((widget.atomicNumber - 1) *
(math.pi / Constants.numberOfElementsPerHalfCircle)) *
Constants.radius,
)
..translate(Constants.elementCardHalfSize)
..rotateY(-initialRotateX)
..translate(-Constants.elementCardHalfSize),
child: Container(
decoration: BoxDecoration(
color: Constants.elementCardColor,
border: Border.all(width: 1, color: Colors.white.withOpacity(0.3))),
child: SizedBox(
width: Constants.elementCardWidth.toDouble(),
height: Constants.elementCardHeight.toDouble(),
child: ElementText()
),
),
);
All these cards are then placed inside a stack widget in another class, and that widget is rotated, like so:
return AnimatedBuilder(
animation: widget.animationControllerX,
builder: (context, _) {
double rotateX =
widget.animationControllerX.value / Constants.turnResolution;
double rotateY =
widget.animationControllerY.value / Constants.turnResolution;
return Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(-rotateX)
..rotateX(rotateY),
alignment: Alignment.center,
child: Container(
child: Stack(
children: List<Widget>.generate(
Constants.numberOfElements,
(index) => ElementCard(
atomicNumber:
Constants.elements[(index + 1).toString()].atomicNumber,
),
),
),
),
);
},
);
As you can see, there is a "..rotateY" transformation applied, which makes a card appear to move forward when the stack is rotated.
However, when rotating, cards that should be in the back are smaller, yet paint over the cards that should be in the front. (There is no doubt that the cards are in the right positions, since I can rotate them along the x axis and see that for myself, but when painting, the card on the back is painted over the text in the card on the front. I believe this is because a stack always positions the elevation of its widgets according to the order they're provided in, and the order is fixed in the list. Is there any way I can fix this?
Screenshots to explain what I mean:
I can change the opacity of the tiles to make the issue more obvious:
As you can see, the larger cards are closer to the screen, since that's how they're transformed, but they're painting behind the smaller cards. Any ideas on how to make this happen?
So I found a solution. It's not perfect, but it works so far.
The issue is that Flutter paints widgets in the order that they appear. Since the larger cards are higher up on the list, even though they have a closer z coordinate, they are painted first, and the smaller card is painted on top of them.
The solution is to use a Flow() widget to dynamically change the order in which the cards are painted. In the children widgets, where I'd normally build them, I instead stored their transformations in a map. Then, in the flow delegate function, access these transformations, calculate the z coordinate of each widget after transforming (row 3, column 3 (1 indexed) in the transformation matrix). Sort the widgets by z coordinate, and then paint them on screen in that order.
Here is the code for the same:
The part with the root Flow widget:
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: widget.animationControllerX,
builder: (context, _) {
double rotateX =
widget.animationControllerX.value / Constants.turnResolution;
double rotateY =
widget.animationControllerY.value / Constants.turnResolution;
return Container(
transform: Matrix4.identity()
..translate(
MediaQuery.of(context).size.width / 2,
),
child: Flow(
clipBehavior: Clip.none,
delegate: CustomFlowDelegate(rotateY: -rotateX, rotateX: rotateY),
children: List<Widget>.generate(
Constants.numberOfElements,
(index) => ElementCard(
atomicNumber:
Constants.elements[(index + 1).toString()].atomicNumber,
),
),
),
);
},
);
}
The FlowDelegate for the widget:
class CustomFlowDelegate extends FlowDelegate {
CustomFlowDelegate({this.rotateX, this.rotateY});
final rotateY, rotateX;
#override
void paintChildren(FlowPaintingContext context) {
Map<int, double> zValue = {};
for (int i = 0; i < context.childCount; i++) {
var map = Constants.transformationsMap[i + 1];
Matrix4 transformedMatrix = Matrix4.identity()
..setEntry(3, 2, 0.001)
..setEntry(3, 1, 0.001)
..rotateY(this.rotateY)
..rotateX(this.rotateX)
..translate(
map['translateOneX'], map['translateOneY'], map['translateOneZ'])
..translate(map['translateTwoX'])
..rotateY(map['rotateThreeY'])
..translate(map['translateFourX']);
zValue[i + 1] = transformedMatrix.getRow(2)[2];
}
var sortedKeys = zValue.keys.toList(growable: false)
..sort((k1, k2) => zValue[k1].compareTo(zValue[k2]));
LinkedHashMap sortedMap = new LinkedHashMap.fromIterable(sortedKeys,
key: (k) => k, value: (k) => zValue[k]);
for (int key in sortedMap.keys) {
var map = Constants.transformationsMap[key];
context.paintChild(
key - 1,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..setEntry(3, 1, 0.001)
..rotateY(this.rotateY)
..rotateX(this.rotateX)
..translate(
map['translateOneX'], map['translateOneY'], map['translateOneZ'])
..translate(map['translateTwoX'])
..rotateY(map['rotateThreeY'])
..translate(map['translateFourX']),
);
}
}
#override
bool shouldRepaint(covariant FlowDelegate oldDelegate) {
return true;
}
}
Here's what the result looks like:
And with the opacity turned up a bit so you can really see the fix in action:
And yes, I know storing the transformations in a map and then sorting them from another page isn't best practice.
Hope this helps someone.
I'm building an apps, that contain floatingactionbutton which I made with my own custom, when it pressed it shows 3 menu icons below it.
There are 2 problems:
When the orientation change to landscape, and we press floatingactionbutton it won't show instead of it will show when we press long, continue no 2
In current landscape it shows with long press like I said in no 1, and when we back again to portrait it need long press again to make it show
I've been trying some ways to make it fix, but it still doesn't work.
Here's the code and screenshot
When portrait menu show up
When changing to landscape, menu icons are missing and need long press to make it show
for floatingActionButton
floatingActionButton: OrientationBuilder(
builder: (BuildContext context, Orientation orientation){
return orientation == Orientation.landscape
? _buildMenu(context)
: _buildMenu(context);
},
),
_buildMenu() that call floatingActionButton
Widget _buildMenu(BuildContext context){
final icons = [ Icons.swap_vert, Icons.check_circle_outline, Icons.filter_list ];
var nowOrientation = MediaQuery.of(context).orientation;
var b = Container(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints){
return OverlayBuilder(
showOverlayTrue: true,
overlayBuild: (BuildContext overlayContext){
RenderBox box = context.findRenderObject() as RenderBox;
final center = box.size.center(box.localToGlobal(const Offset(0.8, 0.8)));
return new Positioned(
top: Offset(center.dx, center.dy - icons.length * 35.0).dy,
left: Offset(center.dx, center.dy - icons.length * 35.0).dx,
child: new FractionalTranslation(
translation: const Offset(-0.5, -0.6),
child: FabIcons(
icons: icons,
),
),
);
},
);
},
),
);}
To make it simple in viewing I put some code on github
OverlayBuilder Class https://github.com/ubaidillahSriyudi/StackOverflowhelp/blob/master/OverlayBuilderClass
Fabicons Class
https://github.com/ubaidillahSriyudi/StackOverflowhelp/blob/master/FabIcons
Thanks so much to someone that could help it
Happens because every time you change orientation, your FloatingActionButton rebuilds and loses state.
You should find a way to save the state of FabIcons widget.
Simple solution :
Constructing FabIcons widget with
icons,
iconTapped;
These variables should be saved in the Parent Widget, so every time you call _buildMenu(context); you pass in those variables