Custom Shape in Flutter - user-interface

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),
),
],
),

Related

How do I align my Neumorphic widgets in concentric circles Flutter?

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(),
),
),
),
),

How to do spin animation like wheel with onSwipe event in flutter?

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

Flutter zoomable image with overlays

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!

Flutter clip png with animation

I'm very new to Flutter and I've encountered a problem. I have to clip a png based on a given height value. That works fine with this Class I made:
class ScaleClipper extends CustomClipper<Rect> {
double value;
#override
Rect getClip(Size size) {
Rect rect = Rect.fromLTWH(0.0, 0.0 + value, size.width, size.height);
return rect;
}
#override
bool shouldReclip(ScaleClipper oldClipper) {
return true;
}
ScaleClipper(double value) {
this.value = value;
}
}
Now I want to animate the change in the image. I've tried to wrap it in Widgets mentioned here: https://flutter.io/docs/development/ui/widgets/animation
But I didn't get it to work properly.
This is the widget where I display the image:
ClipRect(
clipper: ScaleClipper(value),
child: Container(
margin: new EdgeInsets.only(
left: 30.0, top: 30.0, right: 20.0, bottom: 30.0),
width: 150.0,
height: 420.0,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/image.png"),
fit: BoxFit.contain))),
),
Do I need the to change my approach to the problem or can I animate the clipping of the image?
I found the answer for future newcomers. You can just use a simple Tween animation like this
controller = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
..addListener(() {
setState(() {
fracturedValue = desiredClipValue * animation.value;
});
});

Container height Animation starts from the middle

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));

Resources