I'm trying to make a button where when user hold it, there will be a progress, but if the user unpress the button before it finish, the progress will decrease. somthing like the one on the picture
As said by Arnold Parge, you can use the GestureDetector and listen to onTapDown and onTapUp. To create your desired LoadingButton, you can use the following Widget Structure:
- GetureDetector
- Stack
- CircularProgressIndicator // background circle
- CircularProgressIndicator // foreground circle
- Icon
To create the animation, you can add an AnimationController and bind the value of the foreground CircularProgressIndicator to AnimationController.value. Now you'll just have to the add the two listeners to to the GestureDetector:
onTapDown: When tapping on the button, the animation should start. Therefore we call AnimationController.forward()
onTapUp: When releasing the press, we want to check if the animation is already finished. We can use AnimationController.status to check the status. If it is AnimationStatus.forward it is still running. Hence we want to reverse the animation with calling AnimationController.reverse().
Here is the resulting button:
And the complete source code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(),
home: Scaffold(
body: Center(child: LoadingButton()),
),
);
}
}
class LoadingButton extends StatefulWidget {
#override
LoadingButtonState createState() => LoadingButtonState();
}
class LoadingButtonState extends State<LoadingButton>
with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
controller.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => controller.forward(),
onTapUp: (_) {
if (controller.status == AnimationStatus.forward) {
controller.reverse();
}
},
child: Stack(
alignment: Alignment.center,
children: <Widget>[
CircularProgressIndicator(
value: 1.0,
valueColor: AlwaysStoppedAnimation<Color>(Colors.grey),
),
CircularProgressIndicator(
value: controller.value,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
Icon(Icons.add)
],
),
);
}
}
Use GestureDetector.
Start the progress in onTapDown and reverse the progress in onTapUp if the progress is not complete or whatever your conditions are.
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,
),
),
],
),
);
}
}
Managed to create a simple reproduction of an error I've come across in my real program, and I'm not sure why this kind of a thing happens. I think if I can understand this, it might help me.
The key here is that I HAVE to have the two pieces of the program separated in this manner, and they need to communicate, but that communication isn't happening the way I would expect. (So please don't suggest I put the timer function inside the rest of the program).
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool textBool = false;
void textChanger() {
if (textBool) {
setState(() {
textBool = false;
});
} else {
setState(() {
textBool = true;
});
}
}
Text myText() {
if (textBool) {
Text newText = new Text('Original Text');
return newText;
} else {
Text newText = new Text('New Text');
return newText;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: myText(),
),
floatingActionButton: FloatingActionButton(
onPressed: stateModify,
tooltip: 'Change Text',
),
);
}
}
Future<void> stateModify() {
Duration waitTimer = new Duration(seconds: 5);
new Timer.periodic(waitTimer, (timer) {
_MyHomePageState().textChanger();
timer.cancel();
});
}
Expected functionality: Click the button and 5 seconds later it should change to the second set of text. Then I should be able to click the button again and switch it back.
Instead in this example I receive the following error:
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: setState() called in constructor: _MyHomePageState#a5177(lifecycle state: created, no widget, not mounted)
And google doesn't seem to be super helpful on this particular issue.
Any suggestions?
Your stateModify function is not a method of _MyHomePageState. Also when you are accessing the _MyHomePageState().textChanger() in stateModify you are eventually creating a new object of _MyHomePageState() but not inserting in render tree.
You should use Timer instead of Timer.periodic. The Timer.periodic will repeat the task until the Timer.cancel is called. Timer will do a callback after the given time period which you require.
Pass the function textChanger when you are calling stateModify that way you will be able to call the function of _MyHomePageState without creating a new object.
Do the following changes.
floatingActionButton: FloatingActionButton(
onPressed: ()=> stateModify(textChanger),
tooltip: 'Change Text',
),
stateModify(dynamic function) {
Timer(Duration(seconds: 5), function);
}
I am trying to make a Sliver list whose elements are rotated at 90' at first but when tapped they rotate back to 0' and vice verse. The animation itself is working fine but the problem is the scroll only work when the items are at 0'.
Can someone please tell me how can I fix this?
I tried wrapping a container around the list item and observed that the sliver only accounts for the item at 0' but not the item at 90'. I even tried changing the height of the container itself but it gives weird results.
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
Animation<double> animation;
Animation<double> animationOffset;
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: const Duration(seconds: 2));
animation = Tween<double>(begin: -Math.pi / 2, end: 0).animate(controller)
..addStatusListener((status){
if (status == AnimationStatus.completed){
controller.reverse();
}else if (status == AnimationStatus.dismissed){
controller.forward();
}
});
animationOffset = Tween<double>(begin: 87, end: 0).animate(controller)
..addStatusListener(
(status){
print('$status');
print('value: ${animationOffset.value}');
}
);
}
#override
Widget build(BuildContext context) => GestureDetector(
child: CustomScrollView(
slivers: <Widget>[
SliverPadding(
padding: EdgeInsets.all(0),
sliver: SliverList(
delegate: SliverChildListDelegate([
Container(color: Colors.green, child: AnimatedBuilder(animation: animationOffset, builder:(context, child) => Transform.translate(offset: Offset(0, 0),child: RotateTransition(child: TextWidget(), animation: animation)))),
]),
),
),
],
),
onTap: () {
if(animation.status == AnimationStatus.completed){
controller.reverse();
}
else if (animation.status == AnimationStatus.dismissed){
controller.forward();
}
},
);
#override
void dispose() {
// TODO: implement dispose
controller.dispose();
super.dispose();
}
}
I'm not sure but I tried using SliverAnimatedOpacity() for animating opacity and it worked fine. Hope it helps someone
I would like to create a new widget class with a container size of 250x500 with the rest of the class/widget 0.5 opacity - allowing the prior widget - we launched from - to be partially visible.
Is this possible ? if so how ?
-Thanks
Below is the Stateful class I am calling
class ShowMyTitles extends StatefulWidget {
#override
_ShowMyTitlesState createState() => _ShowMyTitlesState();
}
class _ShowMyTitlesState extends State<ShowMyTitles> {
List<Map<String, bool>> myListOfMapTitles;
Map<String, bool> valuesHeaders;
int trueCount = 0;
#override
void initState() {
// TODO: implement initState
super.initState();
SettingsForMap SFM = new SettingsForMap();
myListOfMapTitles = SFM.myListOfMapTitles;
valuesHeaders = SFM.valuesHeaders;
}
#override
Widget build(BuildContext context) {
List myTitles = [];
return new WillPopScope(
onWillPop: (){
myListOfMapTitles.forEach((valuesAll) {
valuesAll.forEach((s,b){
if(b == true) {
myTitles.add(s);
print('My Selecteed titles are = ' + s.toString());
}
});
});
Navigator.pop(context, myTitles);
},
child: new Container(
child: new GestureDetector(
onTap: (){
myListOfMapTitles.forEach((valuesAll) {
valuesAll.forEach((s,b){
if(b == true) {
myTitles.add(s);
print('My Selecteed titles are = ' + s.toString());
}
});
});
Navigator.pop(context, myTitles);
},
child: _titlesDialog(context, 'Select 2 Titles (Max)'),
),
),
);
}
There is an Opacity widget. Wrap the widget you want to be transparent within it.
Opacity(child:MyTransparentWidget(),opacity:0.45)
You can use Stack widget for that.
Surround the widget which you want to change the opacity and the container with Stack widget.
Then wrap the widget which you want to change the opacity with Opacity widget and mention required opacity.
Make sure you put the container after the widget which you want to change the opacity then only it will sit above the transparent widget
To make any widget transparent, you make that widget a child of a parent Theme() widget. e.g
class TransparentWidget extends StatelessWidget {
#override Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
// Set the transparency here
canvasColor: Colors.white70, //or any other color you want. e.g Colors.blue.withOpacity(0.5)
),
child: Container(
height: 250.0,
width: 250.0,
child: Text("Hello World")
)
);
}
}
OR
Simply make the main container background decoration an opaque color.
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.7),
)
);
I would like to draw a user's attention to a submit button anytime a radio button is selected and I would like to know if there's anyway to implement this in flutter. I have looked at the RaisedButton docs but there doesn't seem to be any property that flashes or shakes the button. The code below for instance initially has no radio button selected so the button is grayed out, once a choice is made amongst multiple radio buttons, the submit RaisedButton onPressed property value is no longer null but replaced with the action required; however I also want a situation where if a different radio button is selected, there is some way to add some motion (flashing/shaking button) to the submit button but not change the onPressed property
new Radio<int>(
value: 1,
groupValue: 0,
onChanged: handleRadioValue
)
new RaisedButton(
child: const Text('SUBMIT')
onPressed: selected
)
handleRadioValue(){
selected = groupValue == 0 ? null : submitButton();
//change Raised button to attract attention}
You can attract attention by animating the color of the RaisedButton. This example draws attention to the RaisedButton when the radio button selection changes by changing its color to the current theme's disabledColor and back.
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
int _radioValue;
#override initState() {
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
)..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed)
_controller.reverse();
});
super.initState();
}
void _handleRadioValue(int value) {
// Don't animate the first time that the radio value is set
if (_radioValue != null)
_controller.forward();
setState(() {
_radioValue = value;
});
}
#override
Widget build(BuildContext context) {
ThemeData theme = Theme.of(context);
return new Scaffold(
body: new Center(
child: new Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
new Radio<int>(
value: 0,
groupValue: _radioValue,
onChanged: _handleRadioValue
),
new Radio<int>(
value: 1,
groupValue: _radioValue,
onChanged: _handleRadioValue
),
new AnimatedBuilder(
child: const Text('SUBMIT'),
animation: _controller,
builder: (BuildContext context, Widget child) {
return new RaisedButton(
color: new ColorTween(
begin: theme.primaryColor,
end: theme.disabledColor,
).animate(_controller).value,
colorBrightness: Brightness.dark,
child: child,
onPressed: _radioValue == null ?
null :
() => print('submit')
);
}
)
],
),
),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}