I still new with Dart language.
I want to create a custom Stepper class with extends current Stepper class. The reason why I need to create custom Stepper because I need to override function _buildHorizontal
Current workaround:
import 'package:flutter/material.dart';
class CustomStepper extends Stepper {
CustomStepper({
Key key,
#required Step steps,
ScrollPhysics physics,
StepperType type = StepperType.vertical,
int currentStep = 0,
ValueChanged<int> onStepTapped,
VoidCallback onStepContinue,
VoidCallback onStepCancel,
ControlsWidgetBuilder controlsBuilder,
}) : assert(steps != null),
assert(type != null),
assert(currentStep != null),
super(key: key);
}
My question is, how to override _buildHorizontal in a right way?
You can see here the colors that are being used by default by the Stepper that are being get from the Theme, such as primary color, accent color and so on.
If you want to change it only for that stepper, a more convenient approach is by wrapping it in a Theme and override those properties only for that widget, such as:
Theme(
data: ThemeData(
accentColor: Colors.blueAccent
),
child: Stepper()
)
Otherwise, the best way is to copy the source of the Stepper for your own custom widget and tweak it for your needs.
Related
I've got a simple AnimatedWidget with one child widget.
AnimatedContainer(
duration: Duration(milliseconds: 2000),
curve: Curves.bounceOut,
decoration: BoxDecoration(
color: Colors.purple,
),
child: FlutterLogo(
size: _boxSize,
),
),
where _boxSize is being animated like so:
void _startAnimation() => setState(() {
_boxSize *= 1.7;
});
AnimatedContainer is not working for child widgets, however. You need to change direct properties of AnimatedContainer for the animation to work.
This is in compliance with documentation:
The [AnimatedContainer] will automatically animate between the old
and new values of properties when they change using the provided curve
and duration. Properties that are null are not animated.
Its child and descendants are not animated.
What is the equivalent of AnimatedContainer which is ALSO ABLE to animate its children?
There are few widgets which will animate the child. You can swap the new flutter logo widget with preferred size using AnimatedSwitcher Widget.
AnimatedSwitcher - This widget will swap the child widget with a new widget.
AnimatedPositioned - It'll change the position of the child from the stack widget whenever the given position changes.
AnimatedAlign - Animated version of align which will change the alignment of the child whenever the given alignment changes.
AnimatedCrossFade - It fades between two children and animate itself between their sizes.
There is no magic widget which would simply recursively animate all children. But I think what you want is an implicitly animated widget. ie. you change the constructor parameters of a widget, and as it changes it animates from one value to the next.
The easiest way is probably the ImplicitlyAnimatedWidget with a AnimatedWidgetBaseState. So for your example to animate a boxSize attribute this could look like:
class AnimatedFlutterLogo extends ImplicitlyAnimatedWidget {
const AnimatedFlutterLogo({Key key, #required this.boxSize, #required Duration duration})
: super(key: key, duration: duration);
final double boxSize;
#override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() => _AnimatedFlutterLogoState();
}
class _AnimatedFlutterLogoState extends AnimatedWidgetBaseState<AnimatedFlutterLogo> {
Tween<double> _boxSize;
#override
void forEachTween(visitor) {
_boxSize = visitor(_boxSize, widget.boxSize, (dynamic value) => Tween<double>(begin: value));
}
#override
Widget build(BuildContext context) {
return Container(
child: FlutterLogo(
size: _boxSize?.evaluate(animation),
),
);
}
}
which is imho already pretty concise, the only real boilerplate is basically the forEachTween(visitor) method which has to create Tween objects for all properties you'd like to animate.
I tried cursorColor and wrapping the TextField in a Theme where textSelectionHandleColor and textSelectionColor are set to whatever colors, however, the text cursor stays blue.
To be clear, I am talking about the handle. None of the following adjust it for me:
https://github.com/flutter/flutter/issues/14598
https://github.com/flutter/flutter/issues/15571
Sadly, it is currently not possible to change the textSelectionHandleColor of a TextField by modifying its parent Theme. The only Theme that changes the textSelectionHandleColor is the Theme directly inside the MaterialApp (source).
Issue on GitHub: textSelectionHandleColor is not working/changing. #20219
The reason this problem exists is that the handles are rendered inside an Overlay. The Overlay is not a child from the TextField, but instead always a child of the MaterialApp. Here is a failed try from another developer to solve the problem: textSelectionHandleColor taken from parent's context. Fixes #20219
Therefore you can currently only adjust the MaterialApp inside your application:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: Theme.of(context).copyWith(textSelectionHandleColor: Colors.red),
home: Scaffold(
body: Center(
child: TextField(
autofocus: true,
),
),
),
);
}
}
In Flutter we can use Themes to share colors and font styles.
https://flutter.io/docs/cookbook/design/themes
Is there an existing best practice that we can use in a similar manner share values such as margins, paddings and widths or heights?
Preferably something that helps stick to the material design guidelines.
Defining custom widgets
The easiest and probably most elegant approach is to define custom widgets, like a MyRaisedButton that internally uses the RaisedButton with the right dimensions.
class MyRaisedButton extends StatelessWidget {
MyRaisedButton({
this.child,
this.onPressed,
});
final Widget child;
final VoidCallback onPressed;
#override
Widget build(BuildContext context) {
return RaisedButton(
padding: ...,
onPressed: onPressed,
child: child
);
}
}
This works surprisingly well in most cases.
However, if you still want to keep your widgets flexible (being able to pass a lot of customization options to the constructor), your overall widget definition quickly gets very long, because you need to forward all the options to the RaisedButton.
In that case, it makes sense to actually share the values throughout the app.
Actually sharing values throughout the app
Of course, this approach is possible too.
Due to Flutter's openness, we can just look at how the Theme is implemented and copy that code to create a custom widget that functions just like a Theme.
Here's a boiled-down version:
#immutable
class MyThemeData {
MyThemeData({
this.myPadding,
this.myColor,
this.myString
});
final Padding myPadding;
final Color myColor;
final String myString;
}
class MyTheme extends StatelessWidget {
MyTheme({
Key key,
#required this.data,
#required this.child
}) : super(key: key);
final MyThemeData data;
final Widget child;
static MyThemeData of(BuildContext context) {
return (context.ancestorWidgetOfExactType(MyTheme) as MyTheme)?.data;
}
#override
Widget build(BuildContext context) => child;
}
Now, you can just wrap the MaterialApp in a MyTheme widget:
MyTheme(
data: MyThemeData(
myPadding: ...,
myColor: ...,
...
),
child: ... (here goes the MaterialApp)
)
Then anywhere in your app, you can write MyTheme.of(context).myPadding.
You can adapt the MyThemeData class to your needs, storing anything you want.
I'm currently learning Flutter and I'm having some trouble showing a Snackbar after the interaction with the slider has ended (in other words, the final value was set when the user lifts their finger off the slider). I can't call my _showSnackBar() method in onChange because the snackbar is created and shown many times, one after the other.
Is there something I can do to call a method only after the interaction has finished? I was thinking of making a pull request and add something like onInteractionEnded callback property, but I would like to find out of there is another way first.
Here is my code for reference.
class _MySliderState extends State<MySlider> {
int _value = 2;
#override
Widget build(BuildContext context) {
return Slider(
min: 0.0,
max: 4.0,
divisions: 4,
value: (_value * 1.0),
onChanged: (double value) {
setState(() {
_value = value ~/ 1;
});
_showSnackBar();
},
);
}
void _showSnackBar() {
var snackbar = SnackBar(content: const Text('Slider value changed'));
Scaffold.of(context).showSnackBar(snackbar);
}
}
Thanks.
onChangeStart and onChangeEnd was added to Slider very recently
https://github.com/flutter/flutter/pull/17298
The change should available in master already.
How do i implement the swipe from the left to go back gesture in flutter? Not sure if it was already implemented automatically for iOS, but I wanted it for Android as well (as things are becoming gesture based).
Use CupertinoPageRoute to make it work on Android;
import 'package:flutter/cupertino.dart';
(as answered on How to implement swipe to previous page in Flutter?)
You could set your Theme.platform to TargetPlatform.ios. This will make use that the swipe back gesture is used on every device.
You can use CupertinoPageRoute() as Tom O'Sullivan said above.
However, if you want to customize it (eg. using custom transition duration) using PageRouteBuilders and get the same swipe to go back gesture, then you can override buildTransitions().
For iOS, the default PageTransitionBuilder is CupertinoPageTransitionsBuilder(). So we can use that in buildTransitions(). This automatically give us the swipe right to go back gesture.
Here's some sample code for the CustomPageRouteBuilder:
class CustomPageRouteBuilder<T> extends PageRoute<T> {
final RoutePageBuilder pageBuilder;
final PageTransitionsBuilder matchingBuilder = const CupertinoPageTransitionsBuilder(); // Default iOS/macOS (to get the swipe right to go back gesture)
// final PageTransitionsBuilder matchingBuilder = const FadeUpwardsPageTransitionsBuilder(); // Default Android/Linux/Windows
CustomPageRouteBuilder({this.pageBuilder});
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return pageBuilder(context, animation, secondaryAnimation);
}
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 900); // Can give custom Duration, unlike in MaterialPageRoute
#override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return matchingBuilder.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
}
}
Then to go to a new page:
GestureDetector(
onTap: () => Navigator.push(
context,
CustomPageRouteBuilder(pageBuilder: (context, animation, secondaryAnimation) => NewScreen()),
),
child: ...,
)
You can set the platform of your theme (and darkTheme) to TargetPlatform.iOS, you can set the pageTransitionsTheme of your themes to,
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
),
and you can load the new page using CupertinoPageRoute ... and none of that will work until you make sure to use Navigator.push (instead of Navigator.pushReplacement) to get to that new screen! I hope this helps anyone out there who was working with existing transitions and didn't notice this crucial detail. :)
Use this plugin:
https://pub.dev/packages/cupertino_back_gesture
A Flutter package to set custom width of iOS back swipe gesture area.
For basic use:
import 'package:cupertino_back_gesture/cupertino_back_gesture.dart';
BackGestureWidthTheme(
backGestureWidth: BackGestureWidth.fraction(1 / 2),
child: MaterialApp(
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: {
//this is default transition
//TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
//You can set iOS transition on Andoroid
TargetPlatform.android: CupertinoPageTransitionsBuilderCustomBackGestureWidth(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilderCustomBackGestureWidth(),
},
),
),
home: MainPage(),
),
)
More details on plugin's page
in my case, the solution turned out to be very simple. I just used context.push('screen') instead of context.go('/screen')
This should not be implemented on Android since it makes interactions inconsistent across the OS.
Swiping from the screens edge to go back is not something that Android wants you to implement, so you should better don't do it.