I want to show the animation of feedback of draggable when it is not accepted by the DropTarget.Flutter doesn't show the feedback. Is there any way we can show that or control it. Like this example, I want to achieve this effect. I somehow achieve this effect but it is not proper accurate returning to the original offset. It is moving ahead to its original position.
Animation effect I want.
Here is my Code, I have one drag box when I lift it to a certain position and leave him from there and it should animate back to original position, but it is returning to some other Offset like this.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: DragBox()),
);
}
}
class DragBox extends StatefulWidget {
DragBox({
Key key,
}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyDragBox();
}
}
class _MyDragBox extends State<DragBox> with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Offset begin;
Offset cancelledOffset;
Offset _offsetOfWidget;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((s) {
_afeteLayout();
});
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);
}
void _afeteLayout() {
final RenderBox box = _globalKey.currentContext.findRenderObject();
Offset offset = -box.globalToLocal(Offset(0.0, 0.0));
_offsetOfWidget = offset;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Center(
child: Draggable(
key: _globalKey,
onDraggableCanceled: (v, o) {
setState(() {
cancelledOffset = o;
});
_controller.forward();
},
feedback: Container(
color: Colors.red,
height: 50,
width: 50,
),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
),
),
_controller.isAnimating
? SlideTransition(
position: Tween<Offset>(
begin: cancelledOffset * 0.01,
end: _offsetOfWidget * 0.01)
.animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.stop();
} else {
_controller.reverse();
}
}),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
)
: Container(
child: Text('data'),
)
],
);
}
}
I think, this documentation about "Animate a widget using a physics simulation" is the closest example suited for what you are trying to achieve.
This recipe demonstrates how to move a widget from a dragged point
back to the center using a spring simulation.
To appreciate it in action, take a look on the example:
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: DraggableCard(
child: FlutterLogo(
size: 128,
),
),
);
}
}
/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget {
final Widget child;
DraggableCard({required this.child});
#override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
/// The alignment of the card as it is dragged or being animated.
///
/// While the card is being dragged, this value is set to the values computed
/// in the GestureDetector onPanUpdate callback. If the animation is running,
/// this value is set to the value of the [_animation].
Alignment _dragAlignment = Alignment.center;
late Animation<Alignment> _animation;
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanDown: (details) {
_controller.stop();
},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
);
}
}
Sample output:
A simple way to solve this problem is to create a widget that overrides Draggable and make it a child of an AnimatedPositioned. Here is the example:
import 'package:flutter/material.dart';
class XDraggable extends StatefulWidget {
const XDraggable(
{Key? key,
required this.child,
required this.original_x,
required this.original_y,
this.animation_speed = 200})
: super(key: key);
final Widget child;
final double original_x;
final double original_y;
final double animation_speed;
#override
_XDraggableState createState() => _XDraggableState();
}
class _XDraggableState extends State<XDraggable> {
double x = 200;
double y = 200;
int animation_speed = 0;
#override
void initState() {
x = widget.original_x;
y = widget.original_y;
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedPositioned(
left: x,
top: y,
duration: Duration(milliseconds: animation_speed),
child: Draggable(
onDragUpdate: (details) => {
setState(() {
animation_speed = 0;
x = x + details.delta.dx;
y = y + details.delta.dy;
}),
},
onDragEnd: (details) {
setState(() {
animation_speed = 200;
x = widget.original_x;
y = widget.original_y;
});
},
child: widget.child,
feedback: SizedBox(),
),
);
}
}
Then, just use the widget as a Stack child:
...
child: Stack(
fit: StackFit.expand,
children: [
XDraggable(
original_x: 20,
original_y: 20,
child: Container(
height: 50.0,
width: 50.0,
color: Colors.green,
),
)
],
),
...
Related
I am trying to move the container on the screen by giving begin and end offset like from Offset(0.0,0.0) to Offset(400.0,300.0). I am using Slide Transition to animate the container I am using Tween<Offset>(begin: const Offset(3.0, 4.0), end: Offset(0.0, 0.0)) to move it on the screen I want to pass these Offset(400.0,300.0) and animate it.
Here is my code
class MoveContainer extends StatefulWidget {
MoveContainer({Key key, }) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyMoveContainer();
}
}
class _MyMoveContainer extends State<MoveContainer>
with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Animation<Offset> _offset;
Offset local;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
);
_offset =
Tween<Offset>(begin: const Offset(3.0, 4.0), end: Offset(0.0, 0.0))
.animate(_controller);
_offset.addListener(() {
setState(() {});
});
_controller.forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: _offset,
child: GestureDetector(
onPanStart: (start) {
RenderBox getBox = context.findRenderObject();
local = getBox.localToGlobal(start.globalPosition);
print('point are $local');
},
child: Container(
color: Colors.cyan,
height: 200.0,
width: 200.0,
child: Text("hello ")),
),
);
}
}
Probably this question is not actual for the author. (Asked 7 months ago).
But maybe my answer will help someone else.
Usually Slide Transition is used for transitions between pages. That is why, one unit of position value here is the size of one page. When you put there Offset(400.0,300.0) it's equal 400 screen right, and 300 pages down.
For your case it better to use AnimatedPositioned Widget.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
backgroundColor: Colors.blue,
body: MoveContainer(),
),
);
}
}
class MoveContainer extends StatefulWidget {
#override
_MoveContainerState createState() => _MoveContainerState();
}
class _MoveContainerState extends State<MoveContainer> {
Offset offset = Offset.zero;
final double height = 200;
final double width = 200;
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (details) {
RenderBox getBox = context.findRenderObject();
setState(() {
offset = getBox.localToGlobal(details.globalPosition);
});
},
child: Stack(
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: 300),
top: offset.dy - (height / 2),
left: offset.dx - (width / 2),
child: Container(
color: Colors.cyan,
height: height,
width: width,
child: Text("hello "),
),
),
],
),
);
}
}
I'm a beginner on Flutter development and I have some problems. I will try to explain me.
I have a slider bar (custom widget stateful). I want to integrate this widget on my main view, but I have to change the value of the slider (this value can also change with a tap gesture detector).
I tried several things after research on the net.
For example, with a getter for the VerticalSliderState and access VerticalSliderState method to change the value.
This worked only one time after I got a the state is null error, I think I miss a flutter concept with the setState().
Any explanation? Thanks :P
This is my code:
The Custom Widget Stateful:
import 'package:flutter/material.dart';
class VerticalSlider extends StatefulWidget {
....
final double value;
.....
final void Function(double) onValueChanged;
VerticalSlider({
Key key,
#required this.height,
#required this.width,
this.onValueChanged,
this.value,
....
}) : super(key: key);
VerticalSliderState state;
#override
VerticalSliderState createState(){
state = new VerticalSliderState();
return state ;
}
}
class VerticalSliderState extends State<Vertical Slider>{
.....
double _value;
double _currentHeight;
Widget _movingDecoratedBox;
Widget _fixedDecoratedBox;
void setValue(double value){
_setValue(value, false, widget.height);
}
initState() {
super.initState();
_value = widget.value ?? 5.0;
_currentHeight = _convertValueToHeight();
_movingDecoratedBox = widget.movingBox ?? DecoratedBox(
decoration: BoxDecoration(color: Colors.red)
);
_fixedDecoratedBox = widget.fixedBox ?? DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
);
}
..........
void _onTapUp(TapUpDetails tapDetails) {
RenderBox renderBox = context.findRenderObject();
var newHeight = widget.height - renderBox.globalToLocal(tapDetails.globalPosition).dy;
var newValue = _convertHeightToValue(newHeight);
setState(() {
_currentHeight = (widget.height/10.5) * (newValue);
_setValue(newValue, true, widget.height);
});
}
void _setValue(double newValue, bool userRequest, double height) {
_value = newValue;
if(userRequest){
widget.onValueChanged(_value);
}
else{
setState(() {
_currentHeight = (height/10.5) * (newValue);
});
}
}
Widget _buildMovingBox() {
return Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
width: widget.width,
height: _currentHeight,
child: _movingDecoratedBox,
),
);
}
..........
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: _onTapUp,
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
_buildFixedBox(),
_buildMovingBox(),
],
),
);
}
My main view:
seekBar1 = new VerticalSlider(
height: mediaQueryData.size.height / 2.7,
width: 40.0,
max: 11.0,
min: 0.0,
value: 5.5,
movingBox: new Container(color: Colors.teal),
fixedBox: new Container(color: Colors.grey[200]),
onValueChanged: onValueChanged1,
);
void onValueChanged1(double newValue) {
seekBar2.state.setValue(10-newValue);
print(newValue);
}
One solution would be the one in the full example below. The slider widget updates the value in the main page and it is used to set the value in the other slider.
I have made up some of the code since your snippet was not complete.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _value = 9;
void onValueChanged(double newValue) {
setState(() {
_value = newValue;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Center(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
VerticalSlider(
height: MediaQuery.of(context).size.height / 2.7,
width: 40.0,
max: 11.0,
min: 0.0,
value: _value,
movingBox: new Container(color: Colors.teal),
fixedBox: new Container(color: Colors.grey[200]),
onValueChanged: onValueChanged,
),
SizedBox(
width: 50.0,
),
VerticalSlider(
height: MediaQuery.of(context).size.height / 2.7,
width: 40.0,
max: 11.0,
min: 0.0,
value: _value,
movingBox: new Container(color: Colors.teal),
fixedBox: new Container(color: Colors.grey[200]),
onValueChanged: onValueChanged,
),
],
),
),
);
}
}
class VerticalSlider extends StatefulWidget {
final double height;
final double width;
final double max;
final double min;
final double value;
final Widget movingBox;
final Widget fixedBox;
final void Function(double) onValueChanged;
VerticalSlider({
Key key,
#required this.height,
#required this.width,
this.onValueChanged,
this.value,
this.max,
this.min,
this.movingBox,
this.fixedBox,
}) : super(key: key);
#override
VerticalSliderState createState() {
return VerticalSliderState();
}
}
class VerticalSliderState extends State<VerticalSlider> {
double _value;
double _currentHeight;
#override
void didUpdateWidget(VerticalSlider oldWidget) {
super.didUpdateWidget(oldWidget);
_init();
}
initState() {
super.initState();
_init();
}
void _init() {
_value = widget.value ?? widget.max / 2;
_currentHeight = _convertValueToHeight();
}
double _convertValueToHeight() {
return _value * widget.height / widget.max;
}
double _convertHeightToValue(double height) {
return height * widget.max / widget.height;
}
void _onTapUp(TapUpDetails tapDetails) {
RenderBox renderBox = context.findRenderObject();
var newHeight =
widget.height - renderBox.globalToLocal(tapDetails.globalPosition).dy;
var newValue = _convertHeightToValue(newHeight);
widget.onValueChanged(newValue);
setState(() {
_currentHeight = newHeight;
});
}
Widget _buildMovingBox() {
return Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
width: widget.width,
height: _currentHeight,
child: widget.movingBox ??
DecoratedBox(decoration: BoxDecoration(color: Colors.red)),
),
);
}
Widget _buildFixedBox() {
return Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
width: widget.width / 2,
height: double.infinity,
child: widget.fixedBox ??
DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
),
),
);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: _onTapUp,
child: SizedBox(
width: widget.width,
height: widget.height,
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
_buildFixedBox(),
_buildMovingBox(),
],
),
),
);
}
}
There are a few things you show keep in mind:
You don't want to have a state variable in your Stateful Widget,
neither you'll be wanting to access it by myWidget.state. Let the
widget itself handle its own state.
You are getting null because when you do setState in your
VerticalSliderState object, it will rebuild your tree, thus,
generating a new updated VerticalSliderState. That means that your
state reference will now be null.
If you want to access some data within your state, you can take
advantage of final callbacks (like your onValueChanged) or you
could use Streams.
Also, use methods to return Widgets instead of variables, that's just more correct.
For example, where you have seekBar2.state.setValue(10-newValue); you'd want to just create a new seekBar2 = VerticalSlider(updated properties) with updated value instead of doing it this way.
I want a to place a dot on a card, that can be moved arbitrarily inside the card.
This is my solution so far.
class RoomCard extends StatefulWidget {
final Room room;
RoomCard({
#required this.room,
}) : assert(room != null);
#override
_RoomCardState createState() => _RoomCardState();
}
class _RoomCardState extends State<RoomCard> {
double x = 0.0;
double y = 0.0;
#override
Widget build(BuildContext context) {
return SizedBox(
height: 400.0,
width: 400.0,
child: GestureDetector(
onPanUpdate: (p) {
setState(() {
x += p.delta.dx;
y += p.delta.dy;
});
},
child: Card(
child: Stack(
children: <Widget>[
Marker(
x: x,
y: y,
),
],
),
),
),
);
}
}
class Marker extends StatelessWidget {
final double x;
final double y;
Marker({this.x: 0.0, this.y: 0.0});
#override
Widget build(BuildContext context) {
print("x: $x, y: $y");
return Padding(
padding: EdgeInsets.only(left: x, top: y),
child: CircleAvatar(),
);
}
}
I couldn't find any other way to place the marker in card based on x,y location except for using Padding widget to do that. Let me know if there is some another better way to do it.
Secondly, this works for first time (moving it for the first time). Having issue while moving it afterwards. Am I missing any logic here?
I want to further extend this to have multiple of such dots in the card that can be irritably placed and moved.
I am happy if you can suggest any 3rd party packages that do this.
you can use Transform like below
class Marker extends StatelessWidget {
final double x;
final double y;
Marker({this.x: 0.0, this.y: 0.0});
#override
Widget build(BuildContext context) {
print("x: $x, y: $y");
return Transform(
transform: Matrix4.translationValues(x, y, 0.0), child: CircleAvatar());
}
}
you need to check your x,y constraints to bound the transform to a certain area
Edit:
this is a complete working code for how to constrain your marker for the bottom edge of the card
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(new MaterialApp(
home: new Scaffold(
body: RoomCard(room: Room()),
),
));
}
class Room {}
class RoomCard extends StatefulWidget {
final Room room;
RoomCard({
#required this.room,
}) : assert(room != null);
#override
_RoomCardState createState() => _RoomCardState();
}
class _RoomCardState extends State<RoomCard> {
double x = 0.0;
double y = 0.0;
#override
Widget build(BuildContext context) {
//This hight should be known or calculated for the Widget need to be moved
const double markerHight = 50.0;
double ymax = context.findRenderObject()?.paintBounds?.bottom ?? markerHight ;
return SizedBox(
height: 300.0,
width: 400.0,
child: GestureDetector(
onPanUpdate: (p) {
setState(() {
x += p.delta.dx;
y = (y+p.delta.dy) >ymax - markerHight ? ymax -markerHight : y+p.delta.dy;
});
},
child: Card(
child: Stack(
children: <Widget>[
Marker(
x: x,
y: y,
),
],
),
),
),
);
}
}
class Marker extends StatelessWidget {
final double x;
final double y;
Marker({this.x: 0.0, this.y: 0.0});
#override
Widget build(BuildContext context) {
print("x: $x, y: $y");
return Transform(
transform: Matrix4.translationValues(x, y, 0.0),
child: CircleAvatar());
}
}
What you're looking for is probably either CustomSingleChildLayout or CustomMultiChildLayout.
Using CustomSingleChildLayout would look something like this:
class RoomCard extends StatefulWidget {
#override
_RoomCardState createState() => _RoomCardState();
}
class _RoomCardState extends State<RoomCard> {
Offset position = Offset.zero;
#override
Widget build(BuildContext context) {
return SizedBox(
height: 400.0,
width: 400.0,
child: GestureDetector(
onPanUpdate: (p) {
setState(() => position += p.delta);
},
child: CustomSingleChildLayout(
delegate: MarkerLayoutDelegate(position),
child: Marker(),
),
),
);
}
}
class CallableNotifier extends ChangeNotifier {
void notify() {
this.notifyListeners();
}
}
class MarkerLayoutDelegate extends SingleChildLayoutDelegate with ChangeNotifier {
Offset position;
MarkerLayoutDelegate(this.position);
#override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return constraints.loosen();
}
#override
Offset getPositionForChild(Size size, Size childSize) {
return Offset(min(position.dx, size.width - childSize.width), min(position.dy, size.height - childSize.height));
}
#override
bool shouldRelayout(MarkerLayoutDelegate oldDelegate) {
return position != oldDelegate.position;
}
}
class Marker extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
width: 30,
height: 30,
child: CircleAvatar(),
);
}
}
Or you could use a listener to do it in such a way that the main widget doesn't need to rebuild every time the position of the dot is changed:
class RoomCard extends StatefulWidget {
#override
_RoomCardState createState() => _RoomCardState();
}
class _RoomCardState extends State<RoomCard> {
double x = 0.0;
double y = 0.0;
MarkerLayoutDelegate delegate = MarkerLayoutDelegate(relayout: CallableNotifier());
#override
Widget build(BuildContext context) {
return SizedBox(
height: 400.0,
width: 400.0,
child: GestureDetector(
onPanUpdate: (p) {
delegate.position += p.delta;
},
child: CustomSingleChildLayout(
delegate: delegate,
child: Marker(),
),
),
);
}
}
class CallableNotifier extends ChangeNotifier {
void notify() {
this.notifyListeners();
}
}
class MarkerLayoutDelegate extends SingleChildLayoutDelegate with ChangeNotifier {
Offset _position;
CallableNotifier _notifier;
MarkerLayoutDelegate({CallableNotifier relayout, Offset initialPosition = Offset.zero})
: _position = initialPosition,
_notifier = relayout,
super(relayout: relayout);
set position(Offset position) {
_position = position;
_notifier.notifyListeners();
}
Offset get position => _position;
#override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return constraints.loosen();
}
#override
Offset getPositionForChild(Size size, Size childSize) {
return Offset(min(_position.dx, size.width - childSize.width), min(_position.dy, size.height - childSize.height));
}
#override
bool shouldRelayout(MarkerLayoutDelegate oldDelegate) {
return _position != oldDelegate._position;
}
}
class Marker extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
width: 30,
height: 30,
child: CircleAvatar(),
);
}
}
I want to create a screen for the coach mark. Idea is to blur and make darker everything except the region where is my icon located.
I could cut a circle with feather edges. But the icon on the background is also blurred.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => new _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
_buildScaffold(),
CustomPaint(
child: Container(
constraints: BoxConstraints.expand(),
child: BackdropFilter(
filter: new ui.ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
child: Container(
decoration: new BoxDecoration(
color: Colors.grey[900].withOpacity(0.7)),
))),
foregroundPainter: CoachMarksPainter(),
),
]);
}
Widget _buildScaffold() {
return Scaffold(
appBar: AppBar(
title: Text("Hello"),
actions: <Widget>[
new IconButton(
onPressed: () => print("press"),
icon: new Icon(Icons.calendar_today),
),
PopupMenuButton<String>(
itemBuilder: (BuildContext context) {},
),
],
),
body: new Container(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new NetworkImage(
"http://www.mobileswall.com/wp-content/uploads/2015/03/640-Sunset-Beach-2-l.jpg"),
fit: BoxFit.cover))));
}
}
class CoachMarksPainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
print("Paint size=$size canvas=${canvas.getSaveCount()}");
canvas.save();
Path path = Path()
..addOval(Rect.fromCircle(center: Offset(287.0, 52.0), radius: 25.0))
..addRect(new Rect.fromLTWH(
-10.0, -10.0, size.width + 20.0, size.height + 20.0))
//to have rect a bit larger than screen, so blurred edges won't be seen
..fillType = PathFillType.evenOdd;
Paint paint = Paint()
..blendMode = BlendMode.dstOut
..color = Colors.white.withOpacity(0.4)
..maskFilter = new MaskFilter.blur(
BlurStyle.normal, 2.0); //BoxShadow.convertRadiusToSigma(25.0)
canvas.drawPath(path, paint);
canvas.restore();
}
#override
bool shouldRepaint(CoachMarksPainter oldDelegate) => false;
#override
bool shouldRebuildSemantics(CoachMarksPainter oldDelegate) => false;
}
blurred background with a highlighted icon in a circle
Is it possible to use ImageFilter.blur for Canvas? I use MaskFilter, but it does not blur canvas as much as ImageFilter for BackdropFilter widget.
Ideally, I want to get a semitransparent blurring layer with a hole with soft edges.
P.S. I read this question but I need to invert it.
#Marica Hopefully this is doing what you want.
https://gist.github.com/slightfoot/76043f8f3fc4a8b20fc24c5a6f22b0a0
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Coach Mark Demo',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final GlobalKey<ScaffoldState> _scaffold = GlobalKey();
final GlobalKey<CoachMarkState> _calendarMark = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffold,
appBar: AppBar(
title: Text("Hello"),
actions: <Widget>[
CoachMark(
key: _calendarMark,
id: 'calendar_mark',
text: 'Tap here to use the Calendar!',
child: GestureDetector(
onLongPress: () => _calendarMark.currentState.show(),
child: IconButton(
onPressed: () => print('calendar'),
icon: Icon(Icons.calendar_today),
),
),
),
PopupMenuButton<String>(
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'reset',
child: Text('Reset'),
),
];
},
onSelected: (String value) {
if (value == 'reset') {
_calendarMark.currentState.reset();
_scaffold.currentState.showSnackBar(SnackBar(
content: Text('Hot-restart the app to see the coach-mark again.'),
));
}
},
),
],
),
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage("http://www.mobileswall.com/wp-content/uploads/2015/03/640-Sunset-Beach-2-l.jpg"),
fit: BoxFit.cover),
),
),
);
}
}
class CoachMark extends StatefulWidget {
const CoachMark({
Key key,
#required this.id,
#required this.text,
#required this.child,
}) : super(key: key);
final String id;
final String text;
final Widget child;
#override
CoachMarkState createState() => CoachMarkState();
}
typedef CoachMarkRect = Rect Function();
class CoachMarkState extends State<CoachMark> {
_CoachMarkRoute _route;
String get _key => 'mark_${widget.id}';
#override
void initState() {
super.initState();
test().then((bool seen) {
if (seen == false) {
show();
}
});
}
#override
void didUpdateWidget(CoachMark oldWidget) {
super.didUpdateWidget(oldWidget);
_rebuild();
}
#override
void reassemble() {
super.reassemble();
_rebuild();
}
#override
void dispose() {
dismiss();
super.dispose();
}
#override
Widget build(BuildContext context) {
_rebuild();
return widget.child;
}
void show() {
if (_route == null) {
_route = _CoachMarkRoute(
rect: () {
final box = context.findRenderObject() as RenderBox;
return box.localToGlobal(Offset.zero) & box.size;
},
text: widget.text,
padding: EdgeInsets.all(4.0),
onPop: () {
_route = null;
mark();
},
);
Navigator.of(context).push(_route);
}
}
void _rebuild() {
if (_route != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_route.changedExternalState();
});
}
}
void dismiss() {
if (_route != null) {
_route.dispose();
_route = null;
}
}
Future<bool> test() async {
return (await SharedPreferences.getInstance()).getBool(_key) ?? false;
}
void mark() async {
(await SharedPreferences.getInstance()).setBool(_key, true);
}
void reset() async {
(await SharedPreferences.getInstance()).remove(_key);
}
}
class _CoachMarkRoute<T> extends PageRoute<T> {
_CoachMarkRoute({
#required this.rect,
#required this.text,
this.padding,
this.onPop,
this.shadow = const BoxShadow(color: const Color(0xB2212121), blurRadius: 8.0),
this.maintainState = true,
this.transitionDuration = const Duration(milliseconds: 450),
RouteSettings settings,
}) : super(settings: settings);
final CoachMarkRect rect;
final String text;
final EdgeInsets padding;
final BoxShadow shadow;
final VoidCallback onPop;
#override
final bool maintainState;
#override
final Duration transitionDuration;
#override
bool didPop(T result) {
onPop();
return super.didPop(result);
}
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
Rect position = rect();
if (padding != null) {
position = padding.inflateRect(position);
}
position = Rect.fromCircle(center: position.center, radius: position.longestSide * 0.5);
final clipper = _CoachMarkClipper(position);
return Material(
type: MaterialType.transparency,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (d) => Navigator.of(context).pop(),
child: IgnorePointer(
child: FadeTransition(
opacity: animation,
child: Stack(
children: <Widget>[
ClipPath(
clipper: clipper,
child: BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
child: Container(
color: Colors.transparent,
),
),
),
CustomPaint(
child: SizedBox.expand(
child: Center(
child: Text(text,
style: const TextStyle(
fontSize: 22.0,
fontStyle: FontStyle.italic,
color: Colors.white,
)),
),
),
painter: _CoachMarkPainter(
rect: position,
shadow: shadow,
clipper: clipper,
),
),
],
),
),
),
),
);
}
#override
bool get opaque => false;
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
}
class _CoachMarkClipper extends CustomClipper<Path> {
final Rect rect;
_CoachMarkClipper(this.rect);
#override
Path getClip(Size size) {
return Path.combine(PathOperation.difference, Path()..addRect(Offset.zero & size), Path()..addOval(rect));
}
#override
bool shouldReclip(_CoachMarkClipper old) => rect != old.rect;
}
class _CoachMarkPainter extends CustomPainter {
_CoachMarkPainter({
#required this.rect,
#required this.shadow,
this.clipper,
});
final Rect rect;
final BoxShadow shadow;
final _CoachMarkClipper clipper;
void paint(Canvas canvas, Size size) {
final circle = rect.inflate(shadow.spreadRadius);
canvas.saveLayer(Offset.zero & size, Paint());
canvas.drawColor(shadow.color, BlendMode.dstATop);
canvas.drawCircle(circle.center, circle.longestSide * 0.5, shadow.toPaint()..blendMode = BlendMode.clear);
canvas.restore();
}
#override
bool shouldRepaint(_CoachMarkPainter old) => old.rect != rect;
#override
bool shouldRebuildSemantics(_CoachMarkPainter oldDelegate) => false;
}
I'm not sure I understand the question. It seems that what you want would be achievable by using 3 layers in a stack. Lowest is your background, second is the darker frosted blur and put your icon on top.
Am I misunderstanding something?
I am trying to create an animation that "pops" the widget to the front and returns to it
import "package:flutter/material.dart";
class ScoreCounter extends StatefulWidget {
#override
_ScoreCounter createState() => new _ScoreCounter();
}
class _ScoreCounter extends State<ScoreCounter> with SingleTickerProviderStateMixin{
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..forward();
}
#override
build(BuildContext context){
return new AnimatedBuilder(
animation: _controller,
child: new Container(width: 200.0, height: 200.0, color: Colors.green),
builder: (BuildContext context, Widget child) {
//What to return that scales the element
},
);
}
}
For rotating, I would use a Transform and return a Matrix. But what should I return to accomplish the scaling animation?
Thanks in advance
Just as a alternative, to set just scale for a specific Widget.
Transform.scale(
scale: 1.0,
child: Container(
height: 200.0,
width: 200.0,
color: Colors.pink,
),
),
If you're going to size your contents manually you could just read controller.value in your builder function and use that to set the size of the container.
Alternatively, you could consider a pair of SizeTransitions for each axis. The Align class may also be useful because you can set a sizeFactor in each dimension.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new HomePage(),
));
}
class HomePage extends StatefulWidget {
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.sort),
onPressed: () {},
),
body: new Center(
child: new ScoreCounter(),
),
);
}
}
class ScoreCounter extends StatefulWidget {
#override
_ScoreCounter createState() => new _ScoreCounter();
}
class _ScoreCounter extends State<ScoreCounter>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)
..forward();
}
#override
build(BuildContext context){
return new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
double size = _controller.value * 200.0;
return new Container(width: size, height: size, color: Colors.green);
},
);
}
}
Another Approach is to create generalized Scale Transformation.
Simply add this method to your component
Matrix4 scaleXYZTransform({
double scaleX = 1.00,
double scaleY = 1.00,
double scaleZ = 1.00,
}) {
return Matrix4.diagonal3Values(scaleX, scaleY, scaleZ);
}
Now you can easily Scale any widget by wrapping it:
Transform(
transform: scaleXYZTransform(scaleX: .5, scaleY: .5),
child: Container(
child: myAwesomeWidget(),
));