'Flutter custom widget styling
I'm working on a big app tightly with the designer.
I was a part of the style guide and the component design process,
the designer handed off his work and now its my turn to translate some prototypes and styles to real flutter app.
I'm having a really hard time translating the style guide to flutter widgets.
For example, I have a custom chip widget created from scratch, with many style properties.
There are two main different styles for this chip (normal and highlighted),
and the chip is used widely (I believe more than 50 references), excluding other more complex widgets using it.
I tried :
- Creating a style class for each big widget, and created extension on ThemeData.
class MyCustomChip extends StatelessWidget {
Widget build(BuildContext context) {
final style = Theme.of(context).myCustomChip;
/** return the custom chip **/
}
}
class MyCustomChipStyle {
/** many style properties **/
}
extension MyCustomChipThemeData on ThemeData {
MyCustomChipStyle get myCustomChip => ...
}
- Created a static style class, and passed relevant style properties every time the widget used.
class MyCustomChip extends StatelessWidget {
final Color color;
final BorderRadius borderRadius;
/** many more styling properties **/
}
abstract class MyCustomChipValues {
static const BorderRadius kBorder = /** Getting value from a predefined radiuses **/
static const Color kColor = /** Getting value from a predefined palette **/
/** Many more **/
}
class WidgetUsingTheChip extends StatelessWidget {
Widget build(BuildContext context) {
/** those lines will be duplicated alot **/
return MyCustomChip(
color: MyCustomChipValues.kColor,
borderRadius: MyCustomChipValues.kBorder,
/** Many more **/
);
}
}
- Used all the relevant style properties inside the custom widget, and didn't expose them.
abstract class MyCustomChipValues {
static const BorderRadius kBorder = /** Getting value from a predefined radiuses **/
static const Color kColor = /** Getting value from a predefined palette **/
/** Many more **/
}
class MyCustomChip extends StatelessWidget {
Widget build(BuildContext context) {
/** making it virtually impossible to change style depending on state **/
return Container(
color: MyCustomChipValues.kColor,
/** Many more **/
);
}
}
class WidgetUsingTheChip extends StatelessWidget {
Widget build(BuildContext context) {
return MyCustomChip();
}
}
I have defined other style classes :
- Insets (various paddings and margins)
- Colors (our palette, with buttons, chips, input, scaffold, etc..)
- BorderRadiuses
- Shadows
- Animations (duration and curve)
- Guidelines (based on the gutter and the design grid we prototyped with)
- Other classes...
I cant wrap my head on which of the following is more easy to maintain and refactor in the time of need..
Any suggestion on how to manage this whole Style guide -> code will be helpful,
especially on how to manage custom widget styles.
Thanks !
Solution 1:[1]
Basically you need to understand the concept of custom widgets and know the different types of chips that exist.
You can have several types of bullets - choice, filter, action, input - Chips. Chips - Material Design
From the different types of styles that material design offers, you adapt this to the widgets you receive from your design team. For example wrap your bullets in a Column to have an extra Label.
class MyActionChip extends StatelessWidget {
/// Default chip have a string label
const MyActionChip({
required this.label,
required this.onPressed,
Key? key,
this.onTapAvatar,
}) : child = null, super(key: key);
/// Default chip have a widget label
const MyActionChip.child({
required this.child,
required this.onPressed,
Key? key,
this.onTapAvatar,
}) : label = null, super(key: key);
final String? label;
final Widget? child;
final VoidCallback? onTapAvatar;
final Function()? onPressed;
@override
Widget build(BuildContext context) {
return InputChip(
backgroundColor: MyColors.milk,
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(MyConstants.borderRadiusMedium)),
side: BorderSide(color: MyColors.anthracite20),
),
avatar: const Icon(
MyIcons.plus,
size: 18,
color: MyColors.anthracite,
),
labelPadding:
const EdgeInsets.only(right: SdConstants.contentPaddingMedium),
label: child ??
Text(
label!,
style: const TextStyle(
color: MyColors.anthracite,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
onPressed: onPressed,
);
}
}
- In complex cases, you can build a custom chip according to the complexity.
Note: the parameters that vary according to the screens and callbacks are simply exposed outside the constructor. You need several constructors/classes to customize all your chips.
Solution 2:[2]
I would recommend using InheritedWidget
, then you don't have to rely on external packages.
For example:
class CustomStyle extends InheritedWidget {
const CustomStyle({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
final StyleData data;
static CustomStyle of(BuildContext context) {
final CustomStyle? result =
context.dependOnInheritedWidgetOfExactType<CustomStyle>();
assert(result != null, 'No CustomStyle found in context');
return result!;
}
@override
bool updateShouldNotify(CustomStyle oldWidget) => data != oldWidget.data;
}
You could create these and add to your CustomStyle
in whatever way you want, maybe have multiple class depending on the widget, or separate them by colors.
class StyleData {
const StyleData({
required this.color,
required this.padding,
});
final Color color;
final EdgeInsets padding;
}
Wrap your MaterialApp
with your CustomStyle
, pass your data.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomStyle(
data: const StyleData(
color: Colors.blue,
padding: EdgeInsets.zero,
),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
Then you can access you styling in all your custom widgets:
class CustomContainer extends StatelessWidget {
const CustomContainer({Key? key, this.color, this.padding}) : super(key: key);
final Color? color;
final EdgeInsets? padding;
@override
Widget build(BuildContext context) {
return Container(
color: color ?? CustomStyle.of(context).data.color,
padding: padding ?? CustomStyle.of(context).data.padding,
child: const Text('Hello'),
);
}
}
Any questions just let me know.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Tyler2P |
Solution 2 | atruby |