I want to remove the padding of Flutter's stepper widget in order to create control buttons that don't have any space between them and the horizontal edges of the screen.
What I've tried:
I've found this similar question, where the answer says the only
way to do that would be to create my own version of the stepper
widget, but I did not understand what that person meant by that: should I try
to create from scratch a copy of the built-in widget and adjust it to
fit my needs? It seems like too much time and effort only to change a
small detail like this.
Also, I tried to modify the padding of the built-in
stepper widget (stepper.dart), navigating to it's source code and,
inside the _buildHorizontal() function, changing the value of the
padding property from EdgeInsets.all(24) to EdgeInsets.all(0). It
worked, but would it be a good idea to do this? Modify a bundled
flutter widget?
Furthermore, I've managed to bypass this padding restriction
surrounding the buttons with the "UnconstrainedBox" widget. The only
problem is that, as expected, the child (buttons) overflow it's
parent (the stepper), causing those overflow stripes to be shown
during development. Would it be bad if I left this overflow happen?
Here's my code where the problem appears:
import 'package:flutter/material.dart';
class TestStepperScreen extends StatefulWidget {
const TestStepperScreen({Key? key}) : super(key: key);
#override
_TestStepperScreenState createState() => _TestStepperScreenState();
}
class _TestStepperScreenState extends State<TestStepperScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: Icon(Icons.arrow_back_ios_new),
),
title: Text(
'Test stepper',
),
),
body: Container(
child: Stepper(
margin: EdgeInsets.all(0),
type: StepperType.horizontal,
controlsBuilder: (BuildContext context, ControlsDetails details) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ElevatedButton(
onPressed: null,
child: Text('Cancel'),
),
),
Expanded(
child: ElevatedButton(
onPressed: null,
child: Text('Continue'),
),
),
],
),
);
},
steps: [
Step(
title: SizedBox(),
content: Column(
children: [
Container(
child: Text('test'),
),
],
),
),
Step(
title: SizedBox(),
content: Column(
children: [
Container(),
],
),
),
Step(
title: SizedBox(),
content: Column(
children: [
Container(),
],
),
),
],
),
),
);
}
}
And here's my code using the UnconstrainedBox (desired behaviour):
import 'package:flutter/material.dart';
class TestStepperScreen extends StatefulWidget {
const TestStepperScreen({Key? key}) : super(key: key);
#override
_TestStepperScreenState createState() => _TestStepperScreenState();
}
class _TestStepperScreenState extends State<TestStepperScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: Icon(Icons.arrow_back_ios_new),
),
title: Text(
'Test stepper',
),
),
body: Container(
child: Stepper(
margin: EdgeInsets.all(0),
type: StepperType.horizontal,
controlsBuilder: (BuildContext context, ControlsDetails details) {
return UnconstrainedBox(
child: Container(
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ElevatedButton(
onPressed: null,
child: Text('Cancel'),
),
),
Expanded(
child: ElevatedButton(
onPressed: null,
child: Text('Continue'),
),
),
],
),
),
);
},
steps: [
Step(
title: SizedBox(),
content: Column(
children: [
Container(
child: Text('test'),
),
],
),
),
Step(
title: SizedBox(),
content: Column(
children: [
Container(),
],
),
),
Step(
title: SizedBox(),
content: Column(
children: [
Container(),
],
),
),
],
),
),
);
}
}
Any kind of help would be highly appreciated. Thank you for your time.
you can customize the whole stepper widget to delete the padding, follow these steps:
create dart file called custom_stepper for example
put the code at the bottom here in this file(note that it's very big code)
use the CustomStepper widget not Stepper
the result, the next image with Stepper:
the next image with CustomStepper:
import 'package:flutter/material.dart';
typedef ControlsWidgetBuilder = Widget Function(
BuildContext context, ControlsDetails details);
const TextStyle _kStepStyle = TextStyle(
fontSize: 12.0,
color: Colors.white,
);
const Color _kErrorLight = Colors.red;
final Color _kErrorDark = Colors.red.shade400;
const Color _kCircleActiveLight = Colors.white;
const Color _kCircleActiveDark = Colors.black87;
const Color _kDisabledLight = Colors.black38;
const Color _kDisabledDark = Colors.white38;
const double _kStepSize = 24.0;
const double _kTriangleHeight =
_kStepSize * 0.866025;
class CustomStepper extends StatefulWidget {
const CustomStepper({
Key? key,
required this.steps,
this.physics,
this.type = StepperType.vertical,
this.currentStep = 0,
this.onStepTapped,
this.onStepContinue,
this.onStepCancel,
this.controlsBuilder,
this.elevation,
this.margin,
})
: assert(steps != null),
assert(type != null),
assert(currentStep != null),
assert(0 <= currentStep && currentStep < steps.length),
super(key: key);
final List<Step> steps;
final ScrollPhysics? physics;
final StepperType type;
final int currentStep;
final ValueChanged<int>? onStepTapped;
final VoidCallback? onStepContinue;
final VoidCallback? onStepCancel;
final ControlsWidgetBuilder? controlsBuilder;
final double? elevation;
final EdgeInsetsGeometry? margin;
#override
State<CustomStepper> createState() => _CustomStepperState();
}
class _CustomStepperState extends State<CustomStepper>
with TickerProviderStateMixin {
late List<GlobalKey> _keys;
final Map<int, StepState> _oldStates = <int, StepState>{};
#override
void initState() {
super.initState();
_keys = List<GlobalKey>.generate(
widget.steps.length,
(int i) => GlobalKey(),
);
for (int i = 0; i < widget.steps.length; i += 1)
_oldStates[i] = widget.steps[i].state;
}
#override
void didUpdateWidget(CustomStepper oldWidget) {
super.didUpdateWidget(oldWidget);
assert(widget.steps.length == oldWidget.steps.length);
for (int i = 0; i < oldWidget.steps.length; i += 1)
_oldStates[i] = oldWidget.steps[i].state;
}
bool _isFirst(int index) {
return index == 0;
}
bool _isLast(int index) {
return widget.steps.length - 1 == index;
}
bool _isCurrent(int index) {
return widget.currentStep == index;
}
bool _isDark() {
return Theme
.of(context)
.brightness == Brightness.dark;
}
Widget _buildLine(bool visible) {
return Container(
width: visible ? 1.0 : 0.0,
height: 16.0,
color: Colors.grey.shade400,
);
}
Widget _buildCircleChild(int index, bool oldState) {
final StepState state =
oldState ? _oldStates[index]! : widget.steps[index].state;
final bool isDarkActive = _isDark() && widget.steps[index].isActive;
assert(state != null);
switch (state) {
case StepState.indexed:
case StepState.disabled:
return Text(
'${index + 1}',
style: isDarkActive
? _kStepStyle.copyWith(color: Colors.black87)
: _kStepStyle,
);
case StepState.editing:
return Icon(
Icons.edit,
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
size: 18.0,
);
case StepState.complete:
return Icon(
Icons.check,
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
size: 18.0,
);
case StepState.error:
return const Text('!', style: _kStepStyle);
}
}
Color _circleColor(int index) {
final ColorScheme colorScheme = Theme
.of(context)
.colorScheme;
if (!_isDark()) {
return widget.steps[index].isActive
? colorScheme.primary
: colorScheme.onSurface.withOpacity(0.38);
} else {
return widget.steps[index].isActive
? colorScheme.secondary
: colorScheme.background;
}
}
Widget _buildCircle(int index, bool oldState) {
return Container(
width: _kStepSize,
height: _kStepSize,
child: AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
decoration: BoxDecoration(
color: _circleColor(index),
shape: BoxShape.circle,
),
child: Center(
child: _buildCircleChild(
index, oldState && widget.steps[index].state == StepState.error),
),
),
);
}
Widget _buildTriangle(int index, bool oldState) {
return Container(
width: _kStepSize,
height: _kStepSize,
child: Center(
child: SizedBox(
width: _kStepSize,
height:
_kTriangleHeight,
child: CustomPaint(
painter: _TrianglePainter(
color: _isDark() ? _kErrorDark : _kErrorLight,
),
child: Align(
alignment: const Alignment(
0.0, 0.8),
child: _buildCircleChild(index,
oldState && widget.steps[index].state != StepState.error),
),
),
),
),
);
}
Widget _buildIcon(int index) {
if (widget.steps[index].state != _oldStates[index]) {
return AnimatedCrossFade(
firstChild: _buildCircle(index, true),
secondChild: _buildTriangle(index, true),
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: widget.steps[index].state == StepState.error
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: kThemeAnimationDuration,
);
} else {
if (widget.steps[index].state != StepState.error)
return _buildCircle(index, false);
else
return _buildTriangle(index, false);
}
}
Widget _buildVerticalControls(int stepIndex) {
if (widget.controlsBuilder != null)
return widget.controlsBuilder!(
context,
ControlsDetails(
currentStep: widget.currentStep,
onStepContinue: widget.onStepContinue,
onStepCancel: widget.onStepCancel,
stepIndex: stepIndex,
),
);
final Color cancelColor;
switch (Theme
.of(context)
.brightness) {
case Brightness.light:
cancelColor = Colors.black54;
break;
case Brightness.dark:
cancelColor = Colors.white70;
break;
}
final ThemeData themeData = Theme.of(context);
final ColorScheme colorScheme = themeData.colorScheme;
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
const OutlinedBorder buttonShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)));
const EdgeInsets buttonPadding = EdgeInsets.symmetric(horizontal: 16.0);
return Container(
margin: const EdgeInsets.only(top: 16.0),
child: ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 48.0),
child: Row(
children: <Widget>[
TextButton(
onPressed: widget.onStepContinue,
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
return states.contains(MaterialState.disabled)
? null
: (_isDark()
? colorScheme.onSurface
: colorScheme.onPrimary);
}),
backgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
return _isDark() ||
states.contains(MaterialState.disabled)
? null
: colorScheme.primary;
}),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
buttonPadding),
shape: MaterialStateProperty.all<OutlinedBorder>(buttonShape),
),
child: Text(localizations.continueButtonLabel),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 8.0),
child: TextButton(
onPressed: widget.onStepCancel,
style: TextButton.styleFrom(
primary: cancelColor,
padding: buttonPadding,
shape: buttonShape,
),
child: Text(localizations.cancelButtonLabel),
),
),
],
),
),
);
}
TextStyle _titleStyle(int index) {
final ThemeData themeData = Theme.of(context);
final TextTheme textTheme = themeData.textTheme;
assert(widget.steps[index].state != null);
switch (widget.steps[index].state) {
case StepState.indexed:
case StepState.editing:
case StepState.complete:
return textTheme.bodyText1!;
case StepState.disabled:
return textTheme.bodyText1!.copyWith(
color: _isDark() ? _kDisabledDark : _kDisabledLight,
);
case StepState.error:
return textTheme.bodyText1!.copyWith(
color: _isDark() ? _kErrorDark : _kErrorLight,
);
}
}
TextStyle _subtitleStyle(int index) {
final ThemeData themeData = Theme.of(context);
final TextTheme textTheme = themeData.textTheme;
assert(widget.steps[index].state != null);
switch (widget.steps[index].state) {
case StepState.indexed:
case StepState.editing:
case StepState.complete:
return textTheme.caption!;
case StepState.disabled:
return textTheme.caption!.copyWith(
color: _isDark() ? _kDisabledDark : _kDisabledLight,
);
case StepState.error:
return textTheme.caption!.copyWith(
color: _isDark() ? _kErrorDark : _kErrorLight,
);
}
}
Widget _buildHeaderText(int index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
AnimatedDefaultTextStyle(
style: _titleStyle(index),
duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
child: widget.steps[index].title,
),
if (widget.steps[index].subtitle != null)
Container(
margin: const EdgeInsets.only(top: 2.0),
child: AnimatedDefaultTextStyle(
style: _subtitleStyle(index),
duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
child: widget.steps[index].subtitle!,
),
),
],
);
}
Widget _buildVerticalHeader(int index) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
children: <Widget>[
Column(
children: <Widget>[
_buildLine(!_isFirst(index)),
_buildIcon(index),
_buildLine(!_isLast(index)),
],
),
Expanded(
child: Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(index),
),
),
],
),
);
}
Widget _buildVerticalBody(int index) {
return Stack(
children: <Widget>[
PositionedDirectional(
start: 24.0,
top: 0.0,
bottom: 0.0,
child: SizedBox(
width: 24.0,
child: Center(
child: SizedBox(
width: _isLast(index) ? 0.0 : 1.0,
child: Container(
color: Colors.grey.shade400,
),
),
),
),
),
AnimatedCrossFade(
firstChild: Container(height: 0.0),
secondChild: Container(
margin: widget.margin ??
const EdgeInsetsDirectional.only(
start: 60.0,
end: 24.0,
),
child: Column(
children: <Widget>[
widget.steps[index].content,
_buildVerticalControls(index),
],
),
),
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: _isCurrent(index)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: kThemeAnimationDuration,
),
],
);
}
Widget _buildVertical() {
return ListView(
shrinkWrap: true,
physics: widget.physics,
children: <Widget>[
for (int i = 0; i < widget.steps.length; i += 1)
Column(
key: _keys[i],
children: <Widget>[
InkWell(
onTap: widget.steps[i].state != StepState.disabled
? () {
Scrollable.ensureVisible(
_keys[i].currentContext!,
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
);
widget.onStepTapped?.call(i);
}
: null,
canRequestFocus: widget.steps[i].state != StepState.disabled,
child: _buildVerticalHeader(i),
),
_buildVerticalBody(i),
],
),
],
);
}
Widget _buildHorizontal() {
final List<Widget> children = <Widget>[
for (int i = 0; i < widget.steps.length; i += 1) ...<Widget>[
InkResponse(
onTap: widget.steps[i].state != StepState.disabled
? () {
widget.onStepTapped?.call(i);
}
: null,
canRequestFocus: widget.steps[i].state != StepState.disabled,
child: Row(
children: <Widget>[
SizedBox(
height: 72.0,
child: Center(
child: _buildIcon(i),
),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(i),
),
],
),
),
if (!_isLast(i))
Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
height: 1.0,
color: Colors.grey.shade400,
),
),
],
];
final List<Widget> stepPanels = <Widget>[];
for (int i = 0; i < widget.steps.length; i += 1) {
stepPanels.add(
Visibility(
maintainState: true,
visible: i == widget.currentStep,
child: widget.steps[i].content,
),
);
}
return Column(
children: <Widget>[
Material(
elevation: widget.elevation ?? 2,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
children: children,
),
),
),
Expanded(
child: ListView(
physics: widget.physics,
padding: const EdgeInsets.symmetric(horizontal: 24.0),
children: <Widget>[
AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: stepPanels),
),
_buildVerticalControls(widget.currentStep),
],
),
),
],
);
}
#override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
assert(debugCheckHasMaterialLocalizations(context));
assert(() {
if (context.findAncestorWidgetOfExactType<CustomStepper>() != null)
throw FlutterError(
'Steppers must not be nested.\n'
'The material specification advises that one should avoid embedding '
'steppers within steppers. '
'https:
);
return true;
}());
assert(widget.type != null);
switch (widget.type) {
case StepperType.vertical:
return _buildVertical();
case StepperType.horizontal:
return _buildHorizontal();
}
}
}
class _TrianglePainter extends CustomPainter {
_TrianglePainter({
required this.color,
});
final Color color;
#override
bool hitTest(Offset point) => true;
#override
bool shouldRepaint(_TrianglePainter oldPainter) {
return oldPainter.color != color;
}
#override
void paint(Canvas canvas, Size size) {
final double base = size.width;
final double halfBase = size.width / 2.0;
final double height = size.height;
final List<Offset> points = <Offset>[
Offset(0.0, height),
Offset(base, height),
Offset(halfBase, 0.0),
];
canvas.drawPath(
Path()
..addPolygon(points, true),
Paint()
..color = color,
);
}
}
Related
I got this error in debug console:
Launching lib\main.dart on Chrome in debug mode...
lib\main.dart:1
Error: Expected an identifier, but got ')'.
lib/apilar_codigo/stacked_all.dart:20
Try inserting an identifier before ')'.
StartPomodoro(end: ),
Failed to compile application.
Exited (sigterm)
How can I solved it?
This is my code:
startPomodoro.dart
class StartPomodoro extends StatefulWidget {
StartPomodoro({super.key, required this.end});
final DateTime end;
#override
State<StartPomodoro> createState() => _StartPomodoroState();
}
class _StartPomodoroState extends State<StartPomodoro>
with TickerProviderStateMixin {
final now = DateTime.now();
List<bool> isSelected = [true, false];
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
double progress = 1.0;
bool LongBreak = true;
void notify() {
if (countText == '00:00:00') {}
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 0),
);
controller.addListener(() {
notify();
if (controller.isAnimating) {
setState(() {
progress = controller.value;
});
} else {
setState(() {
progress = 1.0;
LongBreak = true;
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor:
LongBreak ? const Color(0xffD94530) : const Color(0xff6351c5),
body: SizedBox(
height: 460,
child: Stack(
children: [
Positioned(
top: 220,
left: 16,
child: GestureDetector(
onTap: () {
if (controller.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
height: 300,
child: CupertinoTimerPicker(
initialTimerDuration: controller.duration!,
onTimerDurationChanged: (time) {
setState(() {
controller.duration = time;
});
},
),
),
);
}
},
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: const Color(0xffD94530),
height: controller.value *
MediaQuery.of(context).size.height,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: <Widget>[
Expanded(
child: Align(
alignment: Alignment.center,
child: Align(
alignment: FractionalOffset.center,
child: Container(
width: MediaQuery.of(context)
.size
.width -
64,
height: 210,
decoration: const BoxDecoration(
color: Color.fromARGB(
255, 245, 245, 245),
boxShadow: [
BoxShadow(
color: Color(0x22000000),
blurRadius: 64,
blurStyle: BlurStyle.outer,
offset: Offset(0, 16),
),
],
),
child: Container(
padding: const EdgeInsets.all(24.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
"Hyper focused on...",
style: TextStyle(
fontSize: 22.0,
fontWeight:
FontWeight.w600,
),
),
const SizedBox(height: 16),
Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
Text(
countText,
style:
const TextStyle(
letterSpacing:
4,
fontSize: 65.0,
color: Color(
0xff3B3B3B),
),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment
.center,
children: const [
Text(
' Hours Minutes Seconds ',
style: TextStyle(
letterSpacing:
2,
fontSize: 20.0,
color: Color(
0xff3B3B3B),
),
),
],
),
],
),
),
],
),
),
),
),
),
),
),
],
),
),
],
);
}),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 2.0, horizontal: 15.0),
);
}),
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 2.0, horizontal: 15.0),
child: FloatingActionButton.extended(
backgroundColor: const Color(0xffFAFAFA),
onPressed: () {
if (controller.isAnimating) {
controller.stop();
setState(() {
LongBreak = false;
});
} else {
controller.reverse(
from: controller.value == 0
? 1.0
: controller.value);
setState(() {
LongBreak = false;
});
}
},
icon: Icon(
controller.isAnimating
? Icons.pause
: Icons.play_arrow,
color: const Color(0xff3B3B3B),
),
label: Text(
controller.isAnimating ? "Pause" : "Start",
style:
const TextStyle(color: Color(0xff3B3B3B)),
)),
);
}),
],
),
),
],
),
),
),
);
}
AnimationController _buildClockAnimation(TickerProvider tickerProvider) {
return AnimationController(
vsync: tickerProvider,
duration: const Duration(milliseconds: 750),
);
}
void _animateLeftDigit(
int prev,
int current,
AnimationController controller,
) {
final prevFirstDigit = (prev / 10).floor();
final currentFirstDigit = (current / 10).floor();
if (prevFirstDigit != currentFirstDigit) {
controller.forward();
}
}
}
StackPages.dart
class StackPages extends StatelessWidget {
const StackPages({super.key});
#override
Widget build(BuildContext context) {
return Stack(
// ignore: prefer_const_literals_to_create_immutables
children: [
// ChartBar()
// PieChartSample1(),
// ToDoPage(),
StartPomodoro(end: ),
// HomePageTimerUI(),
],
);
}
}
How to get rid of this problem? I tried this but no luck:
StartPomodoro(end: end),
Thank you for any help you can offer
Parameter end is of type DateTime.
To get over this error provide value of type DateTime in end parameter.
Try following code:
StartPomodoro(end: DateTime.now().add(const Duration(days: 1)))
: Error: Expected an identifier, but got ':'.
../…/painting/_network_image_io.dart:147
Try inserting an identifier before ':'.
:rethrow;
^
: Error: Expected ';' after this.
../…/painting/_network_image_io.dart:146
});
^
: Error: Unexpected token ':'.
../…/painting/_network_image_io.dart:147
:rethrow;
getting this error in my code i haven't created anything yet but still showing this error it has to show the basic hello world counter interface,but its not showing that also ,what's the problem I am getting I don't know previously I creating a project but while I was in the middle of that project the same issue had occure after that project everytime if I creating a new project also it showing the same error.
This is what I mean:
As you can see the animation starts from the top to bottom, the problem begins when I integrate another file into it
Example:
Here, I wrapped with another widget and don't respect the height of the app bar
This is my code:
home_page_timer.dart
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:google_nav_bar/google_nav_bar.dart';
import 'package:pomodoro/5.hourglass_animation/countdown_timer/pomodoro_animation.dart';
import 'dart:async';
class HomePageTimerUI extends StatefulWidget {
const HomePageTimerUI({Key? key}) : super(key: key);
#override
State<HomePageTimerUI> createState() => _HomePageTimerUIState();
}
class _HomePageTimerUIState extends State<HomePageTimerUI>
with TickerProviderStateMixin {
late TabController _tabController;
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
#override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
void notify() {
if (countText == '00:00:00') {
_tabController.animateTo(1, duration: const Duration(milliseconds: 300));
}
}
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
child: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(35),
child: Container(
color: Colors.transparent,
child: SafeArea(
child: Column(
children: <Widget>[
TabBar(
controller: _tabController,
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(
color: Color(0xff3B3B3B), width: 4.0),
insets:
EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 11.0)),
indicatorWeight: 15,
indicatorSize: TabBarIndicatorSize.label,
labelColor: const Color(0xff3B3B3B),
labelStyle: const TextStyle(
fontSize: 12,
letterSpacing: 1.3,
fontWeight: FontWeight.w500),
unselectedLabelColor: const Color(0xffD7D7D7),
tabs: const [
Tab(
text: "POMODORO",
icon: Icon(Icons.work_history, size: 35),
),
Tab(
text: "SHORT BREAK",
icon: Icon(Icons.ramen_dining, size: 35),
),
Tab(
text: "LONG BREAK",
icon: Icon(Icons.battery_charging_full_rounded,
size: 35),
),
])
],
),
),
),
),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
Center(
child: Container(
height: MediaQuery.of(context).size.height,
child: StartPomodoro(end: DateTime.now())),
),
const Center(
child: Text('short break'),
),
const Center(
child: Text('long break '),
),
],
),
),
),
);
}
}
start_pomodoro.dart
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:pomodoro/5.hourglass_animation/countdown_timer/responsive.dart';
class StartPomodoro extends StatefulWidget {
const StartPomodoro({super.key, required this.end});
final DateTime end;
#override
State<StartPomodoro> createState() => _StartPomodoroState();
}
class _StartPomodoroState extends State<StartPomodoro>
with TickerProviderStateMixin {
final now = DateTime.now();
List<bool> isSelected = [true, false];
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
double progress = 1.0;
bool LongBreak = true;
void notify() {
if (countText == '00:00:00') {}
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 0),
);
controller.addListener(() {
notify();
if (controller.isAnimating) {
setState(() {
progress = controller.value;
});
} else {
setState(() {
progress = 1.0;
LongBreak = true;
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor:
LongBreak ? const Color(0xffD94530) : const Color(0xff6351c5),
body: Stack(
children: [
GestureDetector(
onTap: () {
if (controller.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => SizedBox(
height: 300,
child: CupertinoTimerPicker(
initialTimerDuration: controller.duration!,
onTimerDurationChanged: (time) {
setState(() {
controller.duration = time;
});
},
),
),
);
}
},
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: const Color(0xffD94530),
height: controller.value *
MediaQuery.of(context).size.height,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Responsive(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Align(
alignment: FractionalOffset.bottomCenter,
child: Container(
width:
MediaQuery.of(context).size.width,
height: 210,
decoration: const BoxDecoration(
color: Color.fromARGB(
255, 245, 245, 245),
),
child: Container(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
"Hyper-focused on... (+add task)",
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: Text(
countText,
style:
const TextStyle(
fontWeight:
FontWeight.w600,
letterSpacing: 4,
fontSize: 65.0,
color: Color(
0xff3B3B3B),
),
),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Center(
child: Text(
' Hours Minutes Seconds ',
style: TextStyle(
fontWeight:
FontWeight.w500,
letterSpacing: 2,
fontSize: 20.0,
color: Color(
0xff3B3B3B),
),
),
),
],
),
],
),
),
],
),
),
),
),
),
//Spacer(),
Responsive(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 2.0,
horizontal: 15.0),
);
}),
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding:
const EdgeInsets.symmetric(
vertical: 2.0,
horizontal: 15.0),
child: FloatingActionButton
.extended(
backgroundColor:
const Color(
0xffFAFAFA),
onPressed: () {
if (controller
.isAnimating) {
controller.stop();
setState(() {
LongBreak = false;
});
} else {
controller.reverse(
from: controller
.value ==
0
? 1.0
: controller
.value);
setState(() {
LongBreak = false;
});
}
},
icon: Icon(
controller.isAnimating
? Icons.pause
: Icons
.play_arrow,
color: const Color(
0xff3B3B3B),
),
label: Text(
controller.isAnimating
? "Pause"
: "Start",
style: const TextStyle(
color: Color(
0xff3B3B3B)),
)),
);
}),
],
),
),
],
),
),
),
],
);
}),
),
],
),
),
);
}
AnimationController _buildClockAnimation(TickerProvider tickerProvider) {
return AnimationController(
vsync: tickerProvider,
duration: const Duration(milliseconds: 750),
);
}
void _animateLeftDigit(
int prev,
int current,
AnimationController controller,
) {
final prevFirstDigit = (prev / 10).floor();
final currentFirstDigit = (current / 10).floor();
if (prevFirstDigit != currentFirstDigit) {
controller.forward();
}
}
}
How can I provide a height from the animation widget which respects the app bar widget and there is no lag when I started the timer?
What I mean is that I want to start the animation here:
Thank you for any help you can offer
As #Henrique Zanferrari suggested you are using height of the screen in
height: MediaQuery.of(context).size.height
Which is limiting the widgets to follow along with the appBar.
Try replacing this height with more general Widget like Expanded or Flexible like so.
This is my problem:
I want to achieve this design:
How can I separate one to each other, first the homePageTimerUI and below the countDownTimer
?
This is my code:
homePageTimerUI.dart
class HomePageTimerUI extends StatefulWidget {
const HomePageTimerUI({Key? key}) : super(key: key);
#override
State<HomePageTimerUI> createState() => _HomePageTimerUIState();
}
class _HomePageTimerUIState extends State<HomePageTimerUI> {
#override
Widget build(BuildContext context) {
return SizedBox(
height: double.maxFinite,
width: double.infinity,
child: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(40),
child: Container(
color: Colors.transparent,
child: SafeArea(
child: Column(
children: <Widget>[
TabBar(
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(
color: Color(0xff3B3B3B), width: 4.0),
insets:
EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 11.0)),
indicatorWeight: 15,
indicatorSize: TabBarIndicatorSize.label,
labelColor: const Color(0xff3B3B3B),
labelStyle: const TextStyle(
fontSize: 12,
letterSpacing: 1.3,
fontWeight: FontWeight.w500),
unselectedLabelColor: const Color(0xffD7D7D7),
tabs: const [
Tab(
text: "POMODORO",
icon: Icon(Icons.work_history, size: 40),
),
Tab(
text: "SHORT BREAK",
icon: Icon(Icons.ramen_dining, size: 40),
),
Tab(
text: "LONG BREAK",
icon: Icon(Icons.battery_charging_full_rounded,
size: 40),
),
])
],
),
),
),
),
),
body: TabBarView(
children: const <Widget>[
// Center(
// child: StartPomodoro(),
// ),
// Center(
// child: ShortBreak(),
// ),
// Center(child: LongBreak()),
],
),
),
),
);
}
}
countDownTimer.dart
class CountDownTimer extends StatefulWidget {
#override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
late AnimationController controller;
String get timerString {
Duration duration = controller.duration! * controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
);
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: Colors.white10,
body: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.amber,
height:
controller.value * MediaQuery.of(context).size.height,
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Align(
alignment: FractionalOffset.center,
child: Stack(
children: <Widget>[
CustomPaint(
painter: CustomTimerPainter(
animation: controller,
backgroundColor: Colors.white,
color: themeData.indicatorColor,
)),
Align(
alignment: FractionalOffset.center,
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Text(
timerString,
style: TextStyle(
fontSize: 112.0,
color: Colors.white),
),
],
),
),
],
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return FloatingActionButton.extended(
onPressed: () {
if (controller.isAnimating)
controller.stop();
else {
controller.reverse(
from: controller.value == 0.0
? 1.0
: controller.value);
}
},
icon: Icon(controller.isAnimating
? Icons.pause
: Icons.play_arrow),
label: Text(
controller.isAnimating ? "Pause" : "Play"));
}),
],
),
],
),
),
],
);
}),
);
}
}
class CustomTimerPainter extends CustomPainter {
CustomTimerPainter({
required this.animation,
required this.backgroundColor,
required this.color,
}) : super(repaint: animation);
final Animation<double> animation;
final Color backgroundColor, color;
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = backgroundColor
..strokeWidth = 10.0
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke;
canvas.drawCircle(size.center(Offset.zero), size.width / 2.0, paint);
paint.color = color;
double progress = (1.0 - animation.value) * 2 * math.pi;
canvas.drawArc(Offset.zero & size, math.pi * 1.5, -progress, false, paint);
}
#override
bool shouldRepaint(CustomTimerPainter old) {
return animation.value != old.animation.value ||
color != old.color ||
backgroundColor != old.backgroundColor;
}
}
This is my own attempt to solve the issue, but didn't work
class StackedPages extends StatelessWidget {
const StackedPages({super.key});
#override
Widget build(BuildContext context) {
return Column(
children: [
HomePageTimerUI(),
CountDownTimer(),
],
);
}
}
How can achieve this design?
Thank you for any help you can offer
Are you asking for Stack() widget?
You want to replace one for another?
You could use AnimatedSwitcher().
AnimatedSwitcher(
duration: const Duration(seconds: 1), child: MyWidget());
Where MyWidget() is asigned to the widget you want to show via SetState(). The widgets are switched with a nice animation.
If you need to have one Widget placed one above the other in the vertical axis, you should try to remove the scaffold and the SizedBox with infity values.
if this class can't stack.
Just using static type.
add these codes at the same file with CountDownTimer.
static const Widget countDownTimer = CountDownTimer();
...
// class CountDownTimer
Then, you must import this file and call this widget by this parameter.
example
#override
Widget build(BuildContext context) {
return Column(
children: [
HomePageTimerUI(),
countDownTimer,
],
);
}
but you don't need to use StackPages just put countDownTimer in your pages.
This is my design for now:
I want to achive this design:
As you can see, the container starts at the top and want I want to do is center it like the second screenshot.
This is my code:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:pomodoro/5.hourglass_animation/countdown_timer/responsive.dart';
class StartPomodoro extends StatefulWidget {
const StartPomodoro({super.key, });
//final DateTime end;
#override
State<StartPomodoro> createState() => _StartPomodoroState();
}
class _StartPomodoroState extends State<StartPomodoro>
with TickerProviderStateMixin {
final now = DateTime.now();
List<bool> isSelected = [true, false];
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
double progress = 1.0;
bool LongBreak = true;
void notify() {
if (countText == '00:00:00') {}
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 0),
);
controller.addListener(() {
notify();
if (controller.isAnimating) {
setState(() {
progress = controller.value;
});
} else {
setState(() {
progress = 1.0;
LongBreak = true;
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return SafeArea(
child: Scaffold(
backgroundColor:
LongBreak ? const Color(0xffD94530) : const Color(0xff6351c5),
body: GestureDetector(
onTap: () {
if (controller.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
height: 300,
child: CupertinoTimerPicker(
initialTimerDuration: controller.duration!,
onTimerDurationChanged: (time) {
setState(() {
controller.duration = time;
});
},
),
),
);
}
},
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: const Color(0xffD94530),
height: controller.value *
MediaQuery.of(context).size.height *
0.722,
),
),
Spacer(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Responsive(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Align(
alignment: FractionalOffset.bottomCenter,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 2.0, horizontal: 15.0),
child: Container(
width: MediaQuery.of(context).size.width,
height: 210,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color:
const Color(0xffFAFAFA)
),
child: Container(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
"Hyper-focused on... (+add task)",
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
Text(
countText,
style: const TextStyle(
fontWeight:
FontWeight.w600,
letterSpacing: 4,
fontSize: 65.0,
color:
Color(0xff3B3B3B),
),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment
.center,
children: const [
Text(
' Hours Minutes Seconds ',
style: TextStyle(
fontWeight:
FontWeight.w500,
letterSpacing: 2,
fontSize: 20.0,
color:
Color(0xff3B3B3B),
),
),
],
),
],
),
),
),
],
),
),
),
),
),
),
),
Spacer(),
Responsive(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding:
const EdgeInsets.symmetric(
vertical: 2.0,
horizontal: 15.0),
child:
FloatingActionButton.extended(
backgroundColor:
const Color(
0xffFAFAFA),
onPressed: () {
if (controller
.isAnimating) {
controller.stop();
setState(() {
LongBreak = false;
});
} else {
controller.reverse(
from: controller
.value ==
0
? 1.0
: controller
.value);
setState(() {
LongBreak = false;
});
}
},
icon: Icon(
controller.isAnimating
? Icons.pause
: Icons.play_arrow,
color: const Color(
0xff3B3B3B),
),
label: Text(
controller.isAnimating
? "Pause"
: "Start",
style: const TextStyle(
color: Color(
0xff3B3B3B)),
)),
);
}),
],
),
),
SizedBox(height: 10,),
],
),
),
),
],
);
}),
),
),
);
}
AnimationController _buildClockAnimation(TickerProvider tickerProvider) {
return AnimationController(
vsync: tickerProvider,
duration: const Duration(milliseconds: 750),
);
}
void _animateLeftDigit(
int prev,
int current,
AnimationController controller,
) {
final prevFirstDigit = (prev / 10).floor();
final currentFirstDigit = (current / 10).floor();
if (prevFirstDigit != currentFirstDigit) {
controller.forward();
}
}
}
For some reason, I'm unable to center the container, wrapped with the widget
center
and no luck, how can I center the timer container?
Thank you for any help you can offer
Its difficult to figure out the complete code because of lots of code and incorrect indentation. But as far as I can see, you are facing this problem because of using Spacer,
Spacer is pushing your floating button and the timer Container to its ends.
Try removing the Spacer and wrap the timer Container with Center and wrap it with Flexible this should solve your problem.
Your present code is roughly:
Column
|_ Timer Container
|_ Spacer
|_ Floating Button
Change it to:
Column
|_ Flexible
|_ Center
|_ Timer Container
|_ Floating Button
If you like to use Stack widget, use positional widget like Aling, Positioned on Stack. Based on your UI, using stack widget structure will be
return Stack(
children: <Widget>[
background(context),
Align(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: buildCounter(context),
),
),
Align(
alignment: Alignment.bottomCenter,
child: startPauseButtonBuild(),
),
],
);
Here is the full snippet, I've removed some part for minimal and padding widget to handle some space
void main() => runApp(MaterialApp(home: const StartPomodoro()));
class StartPomodoro extends StatefulWidget {
const StartPomodoro({
super.key,
});
#override
State<StartPomodoro> createState() => _StartPomodoroState();
}
class _StartPomodoroState extends State<StartPomodoro>
with TickerProviderStateMixin {
final now = DateTime.now();
List<bool> isSelected = [true, false];
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
double progress = 1.0;
bool LongBreak = true;
void notify() {
if (countText == '00:00:00') {}
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 0),
);
controller.addListener(() {
notify();
if (controller.isAnimating) {
setState(() {
progress = controller.value;
});
} else {
setState(() {
progress = 1.0;
LongBreak = true;
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return SafeArea(
child: Scaffold(
backgroundColor:
LongBreak ? const Color(0xffD94530) : const Color(0xff6351c5),
body: GestureDetector(
onTap: () {},
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
background(context),
Align(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: buildCounter(context),
),
),
Align(
alignment: Alignment.bottomCenter,
child: startPauseButtonBuild(),
),
],
);
}),
),
),
);
}
Widget startPauseButtonBuild() {
return LayoutBuilder(
builder: (_, constraints) => Padding(
padding: const EdgeInsets.all(8.0),
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding:
const EdgeInsets.symmetric(vertical: 2.0, horizontal: 15.0),
child: SizedBox(
width: constraints.maxWidth,
child: FloatingActionButton.extended(
backgroundColor: const Color(0xffFAFAFA),
onPressed: () {},
icon: Icon(
controller.isAnimating ? Icons.pause : Icons.play_arrow,
color: const Color(0xff3B3B3B),
),
label: Text(
controller.isAnimating ? "Pause" : "Start",
style: const TextStyle(color: Color(0xff3B3B3B)),
),
),
),
);
}),
),
);
}
Column buildCounter(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: MediaQuery.of(context).size.width,
height: 210,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: const Color(0xffFAFAFA)),
child: Container(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Hyper-focused on... (+add task)",
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
countText,
style: const TextStyle(
fontWeight: FontWeight.w600,
letterSpacing: 4,
fontSize: 65.0,
color: Color(0xff3B3B3B),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text(
' Hours Minutes Seconds ',
style: TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 2,
fontSize: 20.0,
color: Color(0xff3B3B3B),
),
),
],
),
],
),
),
),
],
),
),
),
),
),
],
);
}
Align background(BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
color: const Color(0xffD94530),
height: controller.value * MediaQuery.of(context).size.height * 0.722,
),
);
}
AnimationController _buildClockAnimation(TickerProvider tickerProvider) {
return AnimationController(
vsync: tickerProvider,
duration: const Duration(milliseconds: 750),
);
}
void _animateLeftDigit(
int prev,
int current,
AnimationController controller,
) {
final prevFirstDigit = (prev / 10).floor();
final currentFirstDigit = (current / 10).floor();
if (prevFirstDigit != currentFirstDigit) {
controller.forward();
}
}
}
Since i am new to flutter i want the complete implementation of this animation.
/// Flutter code sample for SliverAppBar
// This sample shows a [SliverAppBar] and it's behavior when using the
// [pinned], [snap] and [floating] parameters.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget();
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool _pinned = true;
bool _snap = false;
bool _floating = false;
// [SliverAppBar]s are typically used in [CustomScrollView.slivers], which in
// turn can be placed in a [Scaffold.body].
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: _pinned,
snap: _snap,
floating: _floating,
expandedHeight: 160.0,
flexibleSpace: const FlexibleSpaceBar(
title: Text('SliverAppBar'),
background: FlutterLogo(),
),
),
const SliverToBoxAdapter(
child: SizedBox(
height: 20,
child: Center(
child: Text('Scroll to see the SliverAppBar in effect.'),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
color: index.isOdd ? Colors.white : Colors.black12,
height: 100.0,
child: Center(
child: Text('$index', textScaleFactor: 5),
),
);
},
childCount: 20,
),
),
],
),
bottomNavigationBar: BottomAppBar(
child: ButtonBar(
alignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Row(
children: <Widget>[
const Text('pinned'),
Switch(
onChanged: (bool val) {
setState(() {
_pinned = val;
});
},
value: _pinned,
),
],
),
Row(
children: <Widget>[
const Text('snap'),
Switch(
onChanged: (bool val) {
setState(() {
_snap = val;
// Snapping only applies when the app bar is floating.
_floating = _floating || _snap;
});
},
value: _snap,
),
],
),
Row(
children: <Widget>[
const Text('floating'),
Switch(
onChanged: (bool val) {
setState(() {
_floating = val;
_snap = _snap && _floating;
});
},
value: _floating,
),
],
),
],
),
),
);
}
}
Source - https://api.flutter.dev/flutter/material/SliverAppBar-class.html
This is exact implementation of this AppBar:
class TransitionAppBar extends StatelessWidget {
TransitionAppBar({
required this.avatar,
required this.title,
this.extent = 250,
Key? key,
}) : super(key: key);
final Widget avatar;
final double extent;
final String title;
#override
Widget build(BuildContext context) {
return SliverPersistentHeader(
pinned: true,
delegate: _TransitionAppBarDelegate(
avatar: avatar,
title: title,
extent: extent > 200 ? extent : 200,
),
);
}
}
class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate {
_TransitionAppBarDelegate({required this.avatar, required this.title, this.extent = 250})
: assert(extent >= 200, '');
final Widget avatar;
final double extent;
final String title;
final _avatarAlignTween = AlignmentTween(begin: Alignment.bottomCenter, end: Alignment.topLeft);
final _avatarMarginTween = EdgeInsetsTween(
end: EdgeInsets.only(left: 14, top: 36),
);
final _iconAlignTween = AlignmentTween(begin: Alignment.bottomRight, end: Alignment.topRight);
final _titleMarginTween = EdgeInsetsTween(
begin: EdgeInsets.only(bottom: 20),
end: EdgeInsets.only(left: 64, top: 45),
);
#override
double get maxExtent => extent;
#override
double get minExtent => 80;
#override
bool shouldRebuild(_TransitionAppBarDelegate oldDelegate) {
return avatar != oldDelegate.avatar || title != oldDelegate.title;
}
#override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
final tempVal = maxExtent * 72 / 100;
final progress = shrinkOffset > tempVal ? 1.0 : shrinkOffset / tempVal;
final avatarMargin = _avatarMarginTween.lerp(progress);
final titleMargin = _titleMarginTween.lerp(progress);
final avatarAlign = _avatarAlignTween.lerp(progress);
final iconAlign = _iconAlignTween.lerp(progress);
final avatarSize = (1 - progress) * 200 + 32;
return Stack(
children: <Widget>[
AnimatedContainer(
duration: Duration(milliseconds: 100),
height: 80,
constraints: BoxConstraints(maxHeight: minExtent),
color: Colors.white,
),
Padding(
padding: avatarMargin,
child: Align(
alignment: avatarAlign,
child: SizedBox(
height: avatarSize,
width: avatarSize,
child: avatar,
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: progress < 0.4 ? 100 * (1 - progress) * 1.5 : 0,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.pink[200]!.withOpacity(0.05),
Colors.pink[400]!.withOpacity(0.8),
],
),
),
),
),
Padding(
padding: titleMargin,
child: Align(
alignment: avatarAlign,
child: Text(
title,
),
),
),
Padding(
padding: titleMargin,
child: Align(
alignment: iconAlign,
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: GestureDetector(
onTap: () {},
child: Icon(
Icons.search,
size: 30,
color: progress < 0.4 ? Colors.white : Colors.black,
),
),
),
),
),
],
);
}
}
Also this answer might be helpful