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:
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 an image of wheel and trying to add spin animation on it with onSwipe event.
actually what I did..
I used AnimatedBuider class but image is spinning on initially.
Here the image
AnimatedBuilder(
animation: animationController,
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/wheel.png", ),
fit: BoxFit.contain,
),
borderRadius: BorderRadius.all(Radius.circular(210.0)),
),
height: MediaQuery.of(context).size.height/2.3,
width: MediaQuery.of(context).size.width/1,
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
Animation controller
#override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this,
duration: new Duration(seconds: 7),
);
animationController.repeat();
}
I found the solution from flutter_spinning_wheel package
I'm trying to make a custom widget that the user can zoom and pan an image and tap to overlay points on the image. I think I'm close but can't quite get the points to line up under the tap.
I copied the relevant zoom-pan from here:
https://github.com/flutter/flutter/blob/master/examples/layers/widgets/gestures.dart
I also have my GestureDetector pulled out into its own widget as per this post to get the correct RenderBox for global to local offset conversion
flutter : Get Local position of Gesture Detector
import 'package:flutter/material.dart';
class ImageWithOverlays extends StatefulWidget {
final List<FractionalOffset> fractionalOffsets;
ImageWithOverlays(this.fractionalOffsets);
#override
_ImageWithOverlaysState createState() => _ImageWithOverlaysState();
}
class _ImageWithOverlaysState extends State<ImageWithOverlays> {
Offset _startingFocalPoint;
Offset _previousOffset;
Offset _offset = Offset.zero;
double _previousZoom;
double _zoom = 1.0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: _handleScaleStart,
onScaleUpdate:_handleScaleUpdate,
onTapDown: (details){
setState(() {
widget.fractionalOffsets.add(_getFractionalOffset(context, details.globalPosition));
});
},
onDoubleTap: _handleScaleReset,
child: Transform(
transform: Matrix4.diagonal3Values(_zoom, _zoom, 1.0) + Matrix4.translationValues(_offset.dx, _offset.dy, 0.0),
child: Center(
child: Container(
child:Stack(
children: <Widget>[
Image.network(
'https://picsum.photos/250?image=9',
), ]
..addAll(_getOverlays(context)),
)),
),
));
}
void _handleScaleStart(ScaleStartDetails details) {
setState(() {
_startingFocalPoint = details.focalPoint;
_previousOffset = _offset;
_previousZoom = _zoom;
});
}
void _handleScaleUpdate(ScaleUpdateDetails details) {
setState(() {
_zoom = _previousZoom * details.scale;
// Ensure that item under the focal point stays in the same place despite zooming
final Offset normalizedOffset = (_startingFocalPoint - _previousOffset) / _previousZoom;
_offset = details.focalPoint - normalizedOffset * _zoom;
});
}
void _handleScaleReset() {
setState(() {
_zoom = 1.0;
_offset = Offset.zero;
widget.fractionalOffsets.clear();
});
}
List<Widget> _getOverlays(BuildContext context) {
return widget.fractionalOffsets
.asMap()
.map((i, fo) => MapEntry(
i,
Align(
alignment: fo,
child: _buildIcon((i + 1).toString(), context)
)
))
.values
.toList();
}
Widget _buildIcon(String indexText, BuildContext context) {
return FlatButton.icon(
icon: Icon(Icons.location_on, color: Colors.red),
label: Text(
indexText,
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
),
);
}
Widget _buildCircleIcon(String indexText) {
return Container(
margin: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
indexText,
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.bold
),
));
}
FractionalOffset _getFractionalOffset(BuildContext context, Offset globalPosition) {
var renderbox = context.findRenderObject() as RenderBox;
var localOffset = renderbox.globalToLocal(globalPosition);
var width = renderbox.size.width;
var height = renderbox.size.height;
return FractionalOffset(localOffset.dx/width,localOffset.dy/height);
}
}
But I'm having a rough time getting the layout right. I think I have two issues:
The image likes to jump to the upper left. I'm not sure how to get it to stay centered.
When I zoom in, my _getFractionalOffset function is not returning the correct fraction. The icons in this screenshot are not showing up under my tap
My matrix math is poor, as is my understanding of how Flutter does its transforms, so I would appreciate any insight.
Figured it out, I had a couple issues with my code that when compounded made debugging annoying.
First, I should have been multiplying the scale/translate matrices, not adding them:
vector.Matrix4 get _transformationMatrix {
var scale = vector.Matrix4.diagonal3Values(_zoom, _zoom, 1.0);
var translation = vector.Matrix4.translationValues(_offset.dx, _offset.dy, 0.0);
var transform = translation * scale;
return transform;
}
Second, I was trying to fuss around with doing the translating from local offset to the image frame instead of letting vector_math do it for me. The new overlay offset calculation:
Offset _getOffset(BuildContext context, Offset globalPosition) {
var renderbox = context.findRenderObject() as RenderBox;
var localOffset = renderbox.globalToLocal(globalPosition);
var localVector = vector.Vector3(localOffset.dx, localOffset.dy,0);
var transformed = Matrix4.inverted(_transformationMatrix).transform3(localVector);
return Offset(transformed.x, transformed.y);
}
Since _transformationmatrix takes a point from the image frame to the screen frame, I needed to left multiply the inverse to get from the screen frame (localOffset) to the image frame (returned value).
Finally, I'm using the Positioned widget instead of Align so I can set the positioning in pixels:
double _width = 100;
double _height = 50;
List<Widget> _getOverlays(BuildContext context) {
return widget.points
.asMap()
.map((i, offset) => MapEntry(
i,
Positioned(
left: offset.dx-_width/2,
width:_width,
top: offset.dy -_height/2,
height:_height,
child: _buildIcon((i + 1).toString(), context)
)
))
.values
.toList();
}
where widget.points is a List<Offset> of offsets returned by _getOffset above, and width/height are the width and height of my icon widget.
Hope this helps someone!
The Flutter Container height animation starts from the middle, but I need it to start from the bottom here is my code
import 'dart:math';
import 'package:flutter/material.dart';
class CorrectWrongOverlay extends StatefulWidget {
final bool _isCorrect;
final VoidCallback _onTap;
final double percentR;
final double percentW;
CorrectWrongOverlay(
this._isCorrect, this.percentR, this.percentW, this._onTap);
#override
State createState() => CorrectWrongOverlayState();
}
class CorrectWrongOverlayState extends State<CorrectWrongOverlay>
with SingleTickerProviderStateMixin {
Animation<double> _iconAnimation;
AnimationController _iconAnimationController;
#override
void initState() {
super.initState();
_iconAnimationController = AnimationController(
duration: Duration(seconds: 3), vsync: this);
_iconAnimation = CurvedAnimation(
parent: _iconAnimationController, curve: Curves.fastOutSlowIn);
_iconAnimation.addListener(() => this.setState(() {}));
_iconAnimationController.forward();
}
#override
void dispose() {
_iconAnimationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Material(
color: Colors.black54,
child: InkWell(
onTap: () => widget._onTap(),
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 80.0,
height: 200.0 * _iconAnimation.value,
color: Colors.green,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 80.0,
height: 200.0,
color: Colors.green,
),
)
],
),
),
),
),
);
}
}
I am trying to achieve this kind of UI with height grow animations in Flutter I want the animations to start from the bottom but it starts from the center of the container and animated it both side.
to start scale animation from certain points. You can wrap your Container with Align widget and give certain start positions with simplicty.
define your controller and animation variables;
AnimationController animationController;
Animation<double> tween;
init them in initState method;
#override
void initState() {
super.initState();
animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 800));
tween = Tween<double>(begin:0,end:1).animate(CurvedAnimation(parent: animationController, curve: Curves.easeIn));
animationController.addListener(() {
setState(() {});
});
}
then in build method or where ever you add use return Widget like below;
Widget animatedContainer(){
return Align(
alignment: Alignment.topRight,// .topCenter, .topLeft, .bottomLeft, bottomCenter...etc
child: Container(
width: (size.width * tween.value).abs(),
height: (200 *tween.value).abs(),
color:Colors.red
),
);
}
You could use TweenMax for Flutter package: https://pub.dartlang.org/packages/tweenmax
Here is an example: https://github.com/mrgoonie/flutter_tweenmax/blob/master/lib/screens/animated_column_chart.dart
Click on the bottom button to animate those bars.
Cheers,
If you're animating it once and don't require advanced animation control, then you don't even need to keep track of AnimationController yourself. Just use TweenAnimationBuilder, pass to it your tween, duration and curve, and let it animate widget for you:
Widget _animatedBarWidget(Widget barWidget) => TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
duration: const Duration(seconds: 3),
curve: Curves.fastOutSlowIn,
builder: (BuildContext context, double value, Widget? child) {
return Align(
alignment: Alignment.bottomCenter,
heightFactor: value,
child: child!,
);
},
child: barWidget,
);
Then, just call
_animatedBarWidget(Container(width: 80.0, height: 200.0, color:Colors.red));