I wanted to animate text and I put a nice animation. So I've made a statefull widget called AnimatedText() and I use it in an other stateful widget that use this AnimatedText and refresh the text every time you clic the screen. However, when I change this text with a setState(), the animation doesn't play again if it had already played...
Here is my code, the AnimatedText() :
class AnimatedText extends StatefulWidget {
double textSize;
String text;
AnimatedText(this.text, {this.textSize = 20});
#override
_AnimatedTextState createState() => _AnimatedTextState();
}
class _AnimatedTextState extends State<AnimatedText> with SingleTickerProviderStateMixin {
Animation _fontSizeAnimation;
AnimationController _controller;
#override
void initState() {
_controller = AnimationController(duration: Duration(milliseconds: 250), vsync: this);
_fontSizeAnimation = CurvedAnimation(parent: _controller, curve: Curves.bounceOut);
_fontSizeAnimation.addListener(() => setState(() {}));
_controller.forward();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Text(widget.text, textAlign: TextAlign.center, style: GoogleFonts.rubik(color: Colors.white, fontSize: _fontSizeAnimation.value*ResponsiveSize().responsiveSizeSmall(widget.textSize)));
}
}
And here is the class that use the AnimatedText():
class BasicDisplay extends StatefulWidget {
Question question;
VoidCallback onTap;
VoidCallback onPressed;
BasicDisplay(this.question, this.onTap, this.onPressed);
#override
_BasicDisplayState createState() => _BasicDisplayState();
}
class _BasicDisplayState extends State<BasicDisplay> with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return GamePageBackground(
onTap: widget.onTap,
question: widget.question,
children: <Widget>[
Expanded(
child: Container(
alignment: Alignment.topRight,
child: IconButton(
onPressed: widget.onPressed,
icon: Icon(Icons.clear, color: Color(0xaaffffff), size: ResponsiveSize().responsiveSize(quitIconButtonSize),),
),
),
),
AnimatedText(widget.question.category, textSize: questionCategoryTextSize,),
AnimatedText(widget.question.question, textSize: questionTextSize,),
Expanded(
child: Container(
padding: EdgeInsets.only(bottom: ResponsiveSize().responsiveSize(10)),
alignment: Alignment.bottomCenter,
child: Text(widget.question.explanation, textAlign: TextAlign.center ,style: GoogleFonts.rubik(color: Colors.white, fontSize: ResponsiveSize().responsiveSize(bottomExplanationTextSize))),
),
),
],
);
}
}
The onPressed call back call the Game class that will refresh the text.
Any one know how to solve this problem ?
I found a solution based on #HBS idea !
I took his function and changed it a little bit :
#override
void didUpdateWidget(AnimatedText oldWidget) {
if(oldWidget.text != widget.text) {
_controller.reset();
_controller.forward();
}
super.didUpdateWidget(oldWidget);
}
You can override the didUpdateWidget in your AnimatedText class:
#override
void didUpdateWidget(AnimatedText oldWidget) {
if(oldWidget.text != widget.text) {
_controller.reset();
_controller.forward();
}
super.didUpdateWidget(oldWidget);
}
Related
I have multiple widgets I want to fade in one after another but without needing a button. Every tutorial I have seen has required the use of a button in order to transition to the next widget. The widgets I want to transition take up the whole screen .
How do you want to trigger the transition?
If time is a suitable trigger.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late Timer timer;
int count = 0;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 1), (_) => setState(() => count += 1));
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(child: child, scale: animation);
},
child: Text(
'$count',
key: ValueKey<int>(count),
style: Theme.of(context).textTheme.headline4,
),
),
],
),
);
}
}
I want to animate the elevation of the widget, without the two hands(:
i`ve tried to change the elevation of material widget but nothing changed
To animate the elevation you'd need to handle the animation yourself, using an AnimationController. Heres a full example:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: AnimatedElevationButton(),
),
),
);
}
}
class AnimatedElevationButton extends StatefulWidget {
#override
_AnimatedElevationButtonState createState() =>
_AnimatedElevationButtonState();
}
class _AnimatedElevationButtonState extends State<AnimatedElevationButton>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animationTween;
#override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(milliseconds: 30),
vsync: this,
);
_animationTween =
Tween(begin: 20.0, end: 0.0).animate(_animationController);
_animationController.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
child: Material(
color: Colors.red,
borderRadius: BorderRadius.circular(8.0),
elevation: _animationTween.value,
child: SizedBox(
width: 100,
height: 50,
),
),
);
}
void _onTapDown(TapDownDetails details) {
_animationController.forward();
}
void _onTapUp(TapUpDetails details) {
_animationController.reverse();
}
}
I have the following widget which will fit into a different parent widget into its body section. So in the parent widget I call this widget as below
body: MapsDemo(),. The issue now is that at this section I run an interval where every 30 seconds I want to call api to get all the latest markers.
Currently I print the count down as this codes print("${30 - timer.tick * 1}"); My issue is very simple I have 3 three floating action button and I have given them their id. Thus on each count down for the last floatingaction button which is btn3.text I am trying to set its value as ${30 - timer.tick * 1} but it does not work on this basis. How can I update the count down in the button?
class MapsDemo extends StatefulWidget {
#override
State createState() => MapsDemoState();
}
class MapsDemoState extends State<MapsDemo> {
GoogleMapController mapController;
#override
void initState() {
super.initState();
startTimer(30);
}
startTimer(int index) async {
print("Index30");
print(index);
new Timer.periodic(new Duration(seconds: 1), (timer) {
if ((30 / 1) >= timer.tick) {
print("${30 - timer.tick * 1}");
btn3.text = ${30 - timer.tick * 1};
} else {
timer.cancel();
var responseJson = NetworkUtils.getAllMarkers(
authToken
);
mapController.clearMarkers();
//startTimer(index + 1);
}
});
}
//Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler().requestPermissions([PermissionGroup.contacts]);import 'package:permission_handler/permission_handler.dart';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
GoogleMap(
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
initialCameraPosition: new CameraPosition(target: LatLng(3.326411697920109, 102.13127606037108))
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Align(
alignment: Alignment.topRight,
child: Column(
children: <Widget>[
FloatingActionButton(
onPressed: () => print('button pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.map, size: 30.0),
heroTag: "btn1",
),
SizedBox(height: 5.0),
FloatingActionButton(
onPressed: () => print('second pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.add, size: 28.0),
heroTag: "btn2",
),
SizedBox(height: 5.0),
FloatingActionButton(
onPressed: () => print('second pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.directions_bike, size: 28.0),
heroTag: "btn3",
),
]
)
),
),
]
)
);
}
}
The below will display a countdown from 30 seconds inside of a FloatingActionButton.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FAB Countdown Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const _defaultSeconds = 30;
Timer _timer;
var _countdownSeconds = 0;
#override
void initState() {
_timer = Timer.periodic(Duration(seconds: 1), (Timer t) => _getTime());
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
void _getTime() {
setState(() {
if (_countdownSeconds == 0) {
_countdownSeconds = _defaultSeconds;
} else {
_countdownSeconds--;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: const Text('FAB Countdown Demo'),
),
floatingActionButton: FloatingActionButton(
child: Text('$_countdownSeconds'), onPressed: () {}),
);
}
}
What are the ways to change a property of a class from outside of the class in flutter?
This is a code for BackDrop layout written in dart for Flutter and I want to close the front panel from the class FrontPanel(and that property is for another class, as you see BackPanel class).
I have tried GlobalKey and Setter method to change that property but I couldn't.
How Can I do that?
Regards.
import 'package:flutter/material.dart';
import 'backdrop.dart';
class SimpleExample extends StatelessWidget {
#override
Widget build(BuildContext context) =>
Scaffold(body: SafeArea(child: Panels()));
}
class Panels extends StatelessWidget {
final frontPanelVisible = ValueNotifier<bool>(false);
#override
Widget build(BuildContext context) {
return Backdrop(
frontLayer: FrontPanel(),
backLayer: BackPanel(
frontPanelOpen: frontPanelVisible,
),
frontHeader: FrontPanelTitle(),
panelVisible: frontPanelVisible,
frontPanelOpenHeight: 40.0,
frontHeaderHeight: 48.0,
frontHeaderVisibleClosed: true,
);
}
}
class FrontPanelTitle extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 16.0, left: 16.0),
child: Text(
'Tap Me',
style: Theme.of(context).textTheme.subhead,
),
);
}
}
class FrontPanel extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).cardColor,
child: Center(child: Text('Hello world')));
}
}
class BackPanel extends StatefulWidget {
BackPanel({#required this.frontPanelOpen});
final ValueNotifier<bool> frontPanelOpen;
#override
createState() => _BackPanelState();
}
class _BackPanelState extends State<BackPanel> {
bool panelOpen;
#override
initState() {
super.initState();
panelOpen = widget.frontPanelOpen.value;
widget.frontPanelOpen.addListener(_subscribeToValueNotifier);
}
void _subscribeToValueNotifier() =>
setState(() => panelOpen = widget.frontPanelOpen.value);
/// Required for resubscribing when hot reload occurs
#override
void didUpdateWidget(BackPanel oldWidget) {
super.didUpdateWidget(oldWidget);
oldWidget.frontPanelOpen.removeListener(_subscribeToValueNotifier);
widget.frontPanelOpen.addListener(_subscribeToValueNotifier);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(
top: 10.0,
),
child: Text('Front panel is ${panelOpen ? "open" : "closed"}'),
)),
Center(
child: RaisedButton(
child: Text('Tap Me'),
onPressed: () {
widget.frontPanelOpen.value = true;
},
)),
// will not be seen; covered by front panel
Center(child: Text('Bottom of Panel')),
]);
}
}
Actually found the answer .
You would be able to do that by making an animation controller and pass the controller to the FrontPages statefulwidget and then using that in the StateClass by widget..(value: newValue)
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?