I'm rather new to Flutter/Dart so I could have made a foolish mistake. I have defined a LinearPercentIndicator in what I believe is a function inside my class _FourthRouteState. I call this function inside Widget build(BuildContext context) with 3 parameters: context, var1, var2 where var1 is a double between 0.0 and 1.0 and var2 is a text object.
The default PercentIndicator should display 75% whereas if the user presses the Confirm button, the percentage should change to 100%. It would be ideal if the animation of the LinearPercentIndicator starts over, since the value changes from 75 to 100.
The problem is that the LinearPercentIndicator is not displayed on my page and I don't receive any errors so I'm not sure what I'm doing wrong. If you could both help me with my code (I only included the relevant parts) and provide me with some relevant/useful documentation, that would be great!
import 'package:flutter/material.dart';
import 'package:percent_indicator/percent_indicator.dart';
import 'package:page_transition/page_transition.dart';
void main() {
runApp(MaterialApp(
theme:
ThemeData(accentColor: Colors.black87),
home: FourthRoute(),
));
}
class FourthRoute extends StatefulWidget {
final value;
FourthRoute({Key key, this.value}) : super(key: key);
#override
_FourthRouteState createState() => new _FourthRouteState();
}
class _FourthRouteState extends State<FourthRoute> {
var _textController = new TextEditingController();
var _formKey = GlobalKey<FormFieldState>();
getPercentageIndicator(context, var1,var2) {
setState(() {
LinearPercentIndicator(
width: MediaQuery
.of(context)
.size
.width - 50,
animation: true,
lineHeight: 20.0,
animationDuration: 2000,
percent: var1,
center: Text(var2),
linearStrokeCap: LinearStrokeCap.roundAll,
progressColor: Colors.green,
backgroundColor: Colors.green.withOpacity(0.2),
);
});
}
#override
Widget build(BuildContext context) {
Color foreground = Colors.green;
Color background = foreground.withOpacity(0.2);
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Container(
decoration: BoxDecoration(
color: Color.fromRGBO(53, 73, 94, 0.9)),
),
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
backgroundColor: Colors.transparent,
elevation: 0.0,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 50.0),
),
Padding(
padding: EdgeInsets.fromLTRB(20.0,16.0,0.0,0.0), // left, top, right, bottom
child: getPercentageIndicator(context,0.75,"75%"),
),
],
),
Positioned(
bottom: 150.0,
right: 20.0,
child: ButtonTheme(
minWidth: 150.0,
child: RaisedButton(
padding: EdgeInsets.all(8.0),
child: Text('Confirm',
style: TextStyle(
fontSize: 24,
color: Colors.black87,
fontWeight: FontWeight.bold
),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0)
),
color: Colors.white,
splashColor: Colors.blueGrey,
onPressed: () {
if (_formKey.currentState.validate()) {
getPercentageIndicator(context,1.0,"100%");
// Navigator.push(
// context,
// PageTransition(
// type: PageTransitionType.fade,
// child:HomePage(),
// duration: Duration(
// seconds: 1
// ),
// ),
// );
}
}
),
),
),
],
),
);
}
}
Why do you make instance of LinearPercentIndicator widget inside of setState in getPercentageIndicator? It makes no sense. You probably wanted just to return it:
getPercentageIndicator(context, var1,var2) {
return LinearPercentIndicator(
width: MediaQuery
.of(context)
.size
.width - 50,
animation: true,
lineHeight: 20.0,
animationDuration: 2000,
percent: var1,
center: Text(var2),
linearStrokeCap: LinearStrokeCap.roundAll,
progressColor: Colors.green,
backgroundColor: Colors.green.withOpacity(0.2),
);
}
By the way, you shouldn't use mediaquery size here. Use LayoutBuilder to change width depending on how much space your widget has:
getPercentageIndicator(context, var1,var2) {
return LayoutBuilder(
builder: (context, constraints) {
return LinearPercentIndicator(
width: constraints.maxWidth - 50,
animation: true,
lineHeight: 20.0,
animationDuration: 2000,
percent: var1,
center: Text(var2),
linearStrokeCap: LinearStrokeCap.roundAll,
progressColor: Colors.green,
backgroundColor: Colors.green.withOpacity(0.2),
);
}
);
}
Related
What I have done till now is make widget draggable and a drag target according to my requirements. What i want is when the user drops the draggable container into the drag target i want to show that dragged widget inside that big drag target container and able to score if that is the right option or not.
second problem i am having is that i am using Text to speech for the sound and it is not working i tried many things but still it isn't working. i have a play button and give it a letter like A,B,D,G etc. whenever user presses the button i want to play the sound of that button and drag the right container into the drag target. Thankyou for your help.
// ignore_for_file: prefer_const_constructors, file_names, non_constant_identifier_names
import 'package:dyslexia/pages/round_2/Q8sound.dart';
import 'package:dyslexia/utilities/nextButton.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../../utilities/QuestionWidget.dart';
class Q1DAD extends StatefulWidget {
const Q1DAD({Key? key}) : super(key: key);
#override
State<Q1DAD> createState() => _Q1DADState();
}
class _Q1DADState extends State<Q1DAD> {
String question =
"Listen to this letter sound and then choose the letter whose sound you hear";
var changeButton = false;
var score = 0;
final FlutterTts flutterTts = FlutterTts();
speak(word) async {
await flutterTts.setLanguage("en-US");
await flutterTts.setPitch(1);
await flutterTts.setVolume(10.0);
await flutterTts.speak(word);
}
var word = "M";
bool isPlayingMsg = false;
bool draged = false;
bool showDRag = false;
#override
Widget build(BuildContext context) {
bool isPlayingMsg = false;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
backgroundColor: Colors.cyan,
title: Text("AD&DY"),
),
body: Padding(
padding: const EdgeInsets.all(14.0),
child: Column(children: [
QuestionWidget(question: question),
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
Material(
elevation: 5,
color: Colors.cyan[50],
child: ListTile(
onTap: () async {
speak(word);
isPlayingMsg = true;
},
leading: Icon(isPlayingMsg ? Icons.stop : Icons.play_arrow),
title: Text(isPlayingMsg ? "Stop" : "Play",
textScaleFactor: 1.2,
style: TextStyle(
color: Colors.black,
)),
),
),
Divider(
thickness: 1.0,
),
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
Column(
children: [
DragTarget1(),
SizedBox(height: MediaQuery.of(context).size.height * 0.07),
Wrap(
spacing: 10,
runSpacing: 5,
children: [
draggableWord("M", false, score),
draggableWord("N", false, score),
draggableWord("O", false, score),
draggableWord("R", false, score),
],
),
],
),
SizedBox(height: MediaQuery.of(context).size.height * 0.1),
nextButton(changeButton: changeButton, Location: Q8sound()),
]),
),
);
}
int acceptedData = 0;
Widget DragTarget1() {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DragTarget<int>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Material(
elevation: 10,
borderRadius: BorderRadius.circular(80),
child: Container(
height: 100.0,
width: 250.0,
color: Color.fromARGB(255, 78, 166, 178),
child: draged
? Container(
height: 50,
width: 70,
color: Colors.lightGreenAccent,
child: Align(
child: Text(word),
),
)
: Center(
child: Text('Drag target here'),
),
),
);
},
onAccept: (int data) {
setState(() {
draged = true;
showDRag = true;
});
},
),
],
);
}
Widget draggableWord(word, option, score) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
showDRag
? SizedBox()
: Draggable<int>(
// Data is the value this Draggable stores.
data: 10,
feedback: Container(
color: Colors.yellow[100],
height: 50,
width: 70,
child: Center(child: Text(word)),
),
childWhenDragging: Material(
elevation: 5,
borderRadius: BorderRadius.circular(10),
child: Container(
height: 50.0,
width: 70.0,
color: Colors.grey,
child: Center(
child: Text(""),
),
),
),
child: Material(
elevation: 5,
borderRadius: BorderRadius.circular(10),
child: Container(
height: 50,
width: 70,
color: Colors.lightGreenAccent,
child: Center(
child: Text(word),
),
),
),
)
],
);
}
}
here is my question widget
import 'package:flutter/material.dart';
class QuestionWidget extends StatelessWidget {
const QuestionWidget({
Key? key,
required this.question,
}) : super(key: key);
final String question;
#override
Widget build(BuildContext context) {
return Material(
elevation: 12,
color: Color.fromARGB(255, 125, 200, 210),
shadowColor: Colors.grey,
borderRadius: BorderRadius.circular(8),
child: Container(
height: 80,
width: 280,
alignment: Alignment.center,
child: Text(question,
textScaleFactor: 1.2,
style: TextStyle(
color: Colors.black,
))),
);
}
}
I am developing an audio player application using flutter, I m using on_audio_query package to fetch audio files from storage, and just_audio package for the audio player.
I want to know how to create something like the bar that is shown in this image
thanks in advance
I wrote one solution in a dartpad for you: https://dartpad.dev/?id=491a65532b2f92590c71a48be4836135
As in my example, you can use a stream to update the progress indicator around the play button. Look at my getSecondsFromCurrentMinute method. Replace this with the stream from your package.
Full code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.black,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.black,
body: Align(
alignment: Alignment.bottomCenter,
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
// Get the the seconds from current minute.
//
// TODO: Make this your actual progress indicator
Stream<int> getSecondsFromCurrentMinute() async* {
final now = DateTime.now();
final seconds = now.second;
yield seconds;
await Future.delayed(Duration(seconds: 1 - seconds));
yield* getSecondsFromCurrentMinute();
}
#override
Widget build(BuildContext context) {
return FractionallySizedBox(
heightFactor: .15,
widthFactor: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Song cover
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10)),
),
// Padding
SizedBox(width: 15),
// Title and artist
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title
Text(
"AUD-20190208-WA0007",
style: Theme.of(context).textTheme.headline5,
),
// Artist
Text(
"Unknown artist",
style: Theme.of(context)
.textTheme
.bodyText2
?.copyWith(color: Colors.grey.withOpacity(.6)),
),
],
),
// Padding between first 2 columns and Icons
Expanded(child: SizedBox.expand()),
//
// Play button and progress indicator
//
StreamBuilder<int>(
stream: getSecondsFromCurrentMinute(),
builder: (context, AsyncSnapshot<int> snapshot) {
double percentageOfSecond = (snapshot.data ?? 0) / 60;
return Container(
width: 40,
height: 40,
child: Stack(
children: [
// the circle showing progress
Positioned(
top: 0,
left: 0,
child: Container(
width: 40,
height: 40,
child: CircularProgressIndicator(
value: percentageOfSecond,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.red,
),
backgroundColor: Colors.red.withOpacity(0.15),
),
),
),
// the play arrow, inside the circle
Positioned(
top: 0,
left: 0,
child: Container(
width: 35,
height: 35,
child: IconButton(
icon: Icon(
Icons.play_arrow,
color: Colors.red,
),
onPressed: () {},
),
),
),
],
),
);
}),
SizedBox(width: 8),
Container(
width: 40,
height: 40,
child: GestureDetector(
onTap: () {},
child: Icon(
Icons.skip_next,
color: Colors.red,
),
),
),
//
SizedBox(width: 8),
Container(
width: 40,
height: 40,
child: GestureDetector(
onTap: () {},
child: Icon(
Icons.menu,
color: Colors.red,
size: 35,
),
),
),
// Extra padding at the end of the row
SizedBox(width: 30),
],
),
);
}
}
You can use Slider widget to make progress bar.
#override
Widget build(BuildContext context) {
return Slider(
value: position.inSeconds.toDouble(),
min: 0.0,
max: duration.inSeconds.toDouble() ,
onChanged: (value) async {
final position = Duration(seconds: value.toInt());
await player.seek(position);
},
),
And put the duration and position value in the initState()
Duration duration = Duration.zero;
Duration position = Duration.zero;
#override
void initState() {
player.currentPosition.listen((positionValue){
setState(() {
position = positionValue;
});
});
player.current.listen((event) {
setState(() {
duration = event.audio.duration;
});
});
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
var width = MediaQuery.of(context).size.width;
var height = MediaQuery.of(context).size.height;
Color backgroundColor = Color(0xffF5F5F5);
Color buttonColor = Color(0xffF4FCFF);
Color iconColor = Colors.blueGrey.shade200;
Color fontColor = Colors.blueGrey.shade800;
int _currentIndex = 0;
List<Widget> _cardList = [
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: Text(_currentIndex.toString()),
),
Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8)
),
child: Text(_currentIndex.toString()),
),
Container(
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(8)
),
child: Text(_currentIndex.toString()),
),
];
void setIndex(int newIndex){
print("$_currentIndex $newIndex");
_currentIndex = newIndex;
this.setState((){
_currentIndex = newIndex;
});
}
void initState(){
this.setState(() {
_currentIndex = 0;
});
}
Map<int, double> _firstOpacityMap = {
0: 0,
1: 0.6,
2: 0.6
};
Map<int, double> _lastOpacityMap = {
0: 0.6,
1: 0.6,
2: 0
};
return Scaffold(
body: Container(
color: backgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: height * 0.08,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: width * 0.04,
),
Container(
width: 50,
height: 50,
child: FloatingActionButton(
backgroundColor: buttonColor,
foregroundColor: iconColor,
elevation: 0,
child: Icon(Icons.menu_rounded),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
)
),
),
Container(
width: width * 0.45,
),
Container(
width: 50,
height: 50,
child: FloatingActionButton(
backgroundColor: buttonColor,
foregroundColor: iconColor,
elevation: 0,
child: Icon(Icons.home_outlined),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
)
),
),
Container(
width: width * 0.04,
),
],
),
Container(
height: height * 0.02,
),
Container(
margin: EdgeInsets.only(
left: width * 0.11
),
child: Column(
children: [
Text(
'Your Cards',
style: TextStyle(
fontFamily: 'Rubik Light',
fontWeight: FontWeight.w300,
fontSize: 30,
color: fontColor,
),
)
],
),
),
Container(
height: height * 0.02,
),
Container(
margin: EdgeInsets.only(
left: 0,
),
width: width,
height: 200,
child: Swiper(
itemCount: 3,
layout: SwiperLayout.CUSTOM,
customLayoutOption: CustomLayoutOption(
startIndex: -1,
stateCount: 3
).addTranslate([
new Offset(-330.0, 0),
new Offset(0.0, 0.0),
new Offset(330.0, 0)
// Error is below here
]).addOpacity([
_firstOpacityMap[_currentIndex],
1,
_lastOpacityMap[_currentIndex],
]),
itemWidth: 310,
itemHeight: 180,
curve: Curves.easeOut,
scrollDirection: Axis.horizontal,
onIndexChanged: (int newIndex) => setIndex(newIndex),
itemBuilder: (BuildContext context, int index){
return _cardList[index];
}
),
)
],
)
)
);
}
}
I am using flutter_swiper to show the cards in a custom SwiperLayout. Since I don't want them to be fully scrollable, I am trying to set the opacity based on the current index. I am getting the next Index in the onIndexChanged method from flutter_swiper, I wan't to set the variable currentIndex to the current index of the cards. But somehow the index isn't getting changed in the setState?
move int _currentIndex = 0; out of the build function.
I am trying to use FadeTransition to fade in a picture and some text after pressing a button. So far this is working fine, however, on subsequent presses of the button I want a new picture and text to Fade in. Right now the first picture and text fades in, but subsequent ones do not.
Here is my code so far:
import 'package:flutter/material.dart';
class SurpriseReveal extends StatefulWidget {
final Widget child;
final int surpriseNumber;
final List<Map<String, String>> surprises;
final Function randomSurprise;
SurpriseReveal(
{#required this.surpriseNumber,
#required this.surprises,
#required this.randomSurprise,
#required this.child});
#override
_SurpriseRevealState createState() => _SurpriseRevealState();
}
class _SurpriseRevealState extends State<SurpriseReveal> with SingleTickerProviderStateMixin{
AnimationController _controller;
Animation _animation;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
_animation = Tween(
begin: 0.0,
end: 1.0,
).animate(_controller);
}
#override
dispose(){
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
_controller.forward();
return Container(
child: Stack(
children: <Widget>[
Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.share),
splashColor: Colors.pink[900],
backgroundColor: Colors.pink[300],
onPressed: () {},
),
),
Positioned(
bottom: 300,
top: 40,
right: 40,
left: 40,
child: FadeTransition(
opacity: _animation,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.pink[300],
width: 4,
),
borderRadius: BorderRadius.circular(300),
),
child: Padding(
padding: const EdgeInsets.all(40.0),
child: Text(
widget.surprises[widget.surpriseNumber]['Surprises'],
style: TextStyle(fontSize: 20, color: Colors.pink[700]),
),
),
alignment: Alignment.topCenter,
),
),
),
Positioned(
bottom: 380,
top: 120,
right: 70,
left: 70,
child: FadeTransition(
opacity: _animation,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: new AssetImage(
widget.surprises[widget.surpriseNumber]['Image'] ),
fit: BoxFit.fill,
),
border: Border.all(
color: Colors.pink[300],
width: 2,
),
borderRadius: BorderRadius.circular(12),
),
//child: Image.asset(surprises[surpriseNumber]['Image']),
),
),
),
Positioned(
bottom: -60,
top: 400,
right: 120,
left: 120,
child: FloatingActionButton(
onPressed: widget.randomSurprise,
splashColor: Colors.pink[900],
backgroundColor: Colors.pink[300],
child: Container(
// decoration property gives it a color and makes it round like a floating action button
// add your desired padding here
// add the child element
child: Center(
child: Text(
'Surprise me again!',
// style of the text
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.pink[900],
),
),
),
),
),
),
],
overflow: Overflow.visible,
),
);
}
}
I would assume I need my FAB that triggers a new picture and text to also trigger the animation again, but I am unsure how to do this?
Try this:
FloatingActionButton(
onPressed: {widget.randomSurprise; _controller.reset();},
......
If you want your animation controller to play the animation again, you need to reset it.
Here is a custom Switch widget I implemented using Animation.
enum SwitchType {
LockToggle, EnableToggle
}
class DiamondSwitch extends StatefulWidget {
final double width, height;
final SwitchType switchType;
final double switchThumbSize;
final VoidCallback onTapCallback;
DiamondSwitch({
key, this.width, this.height,
this.switchType, this.switchThumbSize,
#required this.onTapCallback
}) : super(key: key);
#override
_DiamondSwitchState createState() => _DiamondSwitchState(
width: width, height: height,
switchType: switchType, switchThumbSize: switchThumbSize,
onTapCallback: onTapCallback
);
}
class _DiamondSwitchState extends State<DiamondSwitch> {
final double width, height;
final int _toggleAnimationDuration = 1000;
bool _isOn = false;
final List<Color>
_darkGradientShades = <Color>[
Colors.black, Color.fromRGBO(10, 10, 10, 1.0)
],
_lightGradientShades = <Color>[
Colors.white, Color.fromRGBO(150, 150, 150, 1.0)
];
final SwitchType switchType;
final double switchThumbSize;
List<Icon> _switchIcons = new List<Icon>();
final double _switchIconSize = 35.0;
final VoidCallback onTapCallback;
_DiamondSwitchState({
this.width = 100.0, this.height = 40.0,
this.switchThumbSize = 40.0, #required this.switchType,
#required this.onTapCallback
});
#override
void initState() {
_switchIcons.addAll(
(switchType == SwitchType.LockToggle)?
<Icon>[
Icon(
Icons.lock,
color: Colors.black,
size: _switchIconSize,
),
Icon(
Icons.lock_open,
color: Colors.white,
size: _switchIconSize,
)
]
:
<Icon>[
Icon(
Icons.done,
color: Colors.black,
size: _switchIconSize,
),
Icon(
Icons.close,
color: Colors.white,
size: _switchIconSize,
)
]
);
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: _toggleAnimationDuration),
width: width, height: height,
decoration: ShapeDecoration(
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(28.0),
side: (_isOn)?
BorderSide(
color: Color.fromRGBO(45, 45, 45, 1.0),
width: 0.5,
)
:
BorderSide.none,
),
gradient: LinearGradient(
colors: <Color>[
...((_isOn)? _darkGradientShades : _lightGradientShades)
],
begin: Alignment(1.0, -0.8), end: Alignment(-0.7, 1.0),
stops: <double>[0.4, 1.0]
),
),
alignment: Alignment(0.0, 0.0),
child: Stack(
alignment: Alignment(0.0, 0.0),
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: _toggleAnimationDuration),
curve: Curves.easeIn,
left: (_isOn)? 0.0 : ((width * 70) / 100),
right: (_isOn)? ((width * 70) / 100) : 0.0,
child: GestureDetector(
onTap: () {
onTapCallback();
setState(() {
_isOn = !_isOn;
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: _toggleAnimationDuration),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
child: Transform.rotate(
alignment: Alignment.center,
angle: (math.pi / 4),
child: Container(
width: switchThumbSize, height: switchThumbSize,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3.0),
color: (_isOn)? Colors.white : Colors.black,
border: (_isOn)?
null
:
Border.all(
color: Color.fromRGBO(87, 87, 87, 1.0),
width: 1.0,
),
),
child: Transform.rotate(
alignment: Alignment.center,
angle: -(math.pi / 4),
child: (_isOn)?
_switchIcons[0]
:
_switchIcons[1],
),
),
),
),
),
)
],
),
);
}
}
I added a onTapCallback because I need to set another flag in the parent widget to trigger a Image change. Here is related code that belongs to parent widget;
DiamondSwitch(
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
When I run this code, animation doesn't work. It detects tap and executes onTap callback, and all code in onTap Works, (I tested with print methods), but as I said, animation isn't happening.
I want to learn why does this happen, is this about how Flutter work? If yes, can you explain?
TY for taking time ^.^!
EDIT
I want to know why does getting a method with setState breaks the animation I'm sharing the current parent widget with the approach of the answerer #pulyaevskiy implemented.
class _SettingsState extends State<Settings> {
bool
_isLockOn = false,
_isPassiconOn = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
children: <Widget>[
/*Lock Toggle Graphic*/
Align(
alignment: Alignment(-0.1, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/*Lock Toggle Text*/
RotatedBox(
quarterTurns: 3,
child: Column(
children: <Widget>[
Text(
(_isLockOn)? "locked" : "unlocked",
style: TextStyle(
color: Colors.white,
fontFamily: "Philosopher",
fontSize: 25.0,
shadows: <Shadow>[
Shadow(
color: Color.fromRGBO(184, 184, 184, 0.68),
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 5.5),
child: Container(
color: Color.fromRGBO(204, 204, 204, 1.0),
width: 30.0, height: 1.0,
),
),
],
),
),
/*Lock Toggle Image*/
Image.asset(
"assets/images/settings_screen/crystal_"
"${(_isLockOn)? "white_light_up" : "black_outline"}.png",
scale: 5.0,
alignment: Alignment.center,
),
],
),
),
/*Lock Toggle Switch*/
Padding(
padding: const EdgeInsets.only(top: 12.5),
child: DiamondSwitch(
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
flagToControl: this._isLockOn,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
),
/*Separator*/
WhiteDiamondSeparator(paddingAmount: 36.5),
/*Passicon Toggle Graphic*/
Align(
alignment: Alignment(-0.32, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/*Lock Toggle Text*/
RotatedBox(
quarterTurns: 3,
child: Column(
children: <Widget>[
Text(
"passicon",
style: TextStyle(
color: Colors.white,
fontFamily: "Philosopher",
fontSize: 25.0,
shadows: <Shadow>[
Shadow(
color: Color.fromRGBO(184, 184, 184, 0.68),
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 5.5),
child: Container(
color: Color.fromRGBO(204, 204, 204, 1.0),
width: 30.0, height: 1.0,
),
),
],
),
),
/*Passicon Toggle Image*/
Padding(
padding: const EdgeInsets.only(left: 40),
child: Image.asset(
"assets/images/settings_screen/emote_"
"${(_isPassiconOn)? "winking" : "nervous"}.png",
scale: 3.25,
alignment: Alignment.center,
),
),
],
),
),
/*Passicon Toggle Switch*/
Padding(
padding: const EdgeInsets.only(top: 42.5),
child: DiamondSwitch(
switchType: SwitchType.PassiconToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
flagToControl: this._isPassiconOn,
onTapCallback: () {
this.setState(() {
this._isPassiconOn = !this._isPassiconOn;
});
},
key: UniqueKey(),
),
),
/*Separator*/
WhiteDiamondSeparator(paddingAmount: 36.5),
],
)
),
);
}
}
And I added flagToControl in DiamondSwitch and using it in _DiamondSwitchState as bool get _isOn => widget.flagToControl;.
In the old approach, if I don't execute and other thing than
setState() { _isOn = !_isOn; }
animation happens as it should. What am I missing?
In your onTapCallback you are changing state of your parent widget, which works indeed. However it does not affect state of the DiamondSwitch widget itself
(I see that in GestureDetector you also setState of the switch widget, but there are a few issues with this approach).
To fix this you can pass the value of this._isLockOn from your parent widget's state to the DiamondSwitch child. This means you need another property on your switch widget. E.g.
class DiamondSwitch extends StatefulWidget {
final bool isOn;
// ... remaining fields go here
DiamondSwitch({this.isOn, ...});
}
Then change _DiamondSwitchState as well. Could simply proxy _isOn to the widget's value:
class _DiamondSwitchState extends State<DiamondSwitch> {
bool get _isOn => widget.isOn;
}
This is much better than keeping state of isOn in two places as you have now (in parent widget AND in the switch itself). With this change your isLockOn state is only kept on the parent widget and you just pass it down to the switch child to use.
This means that for GestureDetector's onTap property you'll simply pass the onTapCallback of the parent widget as well, no need to wrap it with another function.
The last part: in your parent widget's build method:
DiamondSwitch(
isOn: this._isLockOn, // <-- new line here to pass the value down
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
Another benefit of doing it this way is that now you can initialize your switch with a different default value if needed (right now it's hardcoded to always start off as false). So if you load value of isLockOn from a database and it's set to true you can immediately pass this value to the switch child and represent your state correctly.