I don't know how to change the grey color of overdue dates. For me there is not enough contrast between past date, future date and today's date.
Looking at the source code of of CupertinoDatePicker, it seems that the colors are not easily changeable.
To quote from flutter's source code:
TextStyle _themeTextStyle(BuildContext context, { bool isValid = true }) {
final TextStyle style = CupertinoTheme.of(context).textTheme.dateTimePickerTextStyle;
return isValid
? style.copyWith(color: CupertinoDynamicColor.maybeResolve(style.color, context))
: style.copyWith(color: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context));
}
Therefore, the only direct impact you have on the colors is by wrapping your CupertinoDatePicker inside a CupertinoTheme and providing a value for textTheme.dateTimePickerTextStyle . Further customisation is not supported as of yet
Since there is no option to change the overlay color from the CupertinoDatePicker widget constructor, you will need to change the overlay color from its source code.
First, we will take an example of CupertinoDatePicker like this:
// ...
child: CupertinoDatePicker(
onDateTimeChanged: (date) {},
),
// ...
And it shows this on the screen:
Going inside its source code, you need to search for those lines:
const Widget _startSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(capEndEdge: false);
const Widget _centerSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(capStartEdge: false, capEndEdge: false);
const Widget _endSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(capStartEdge: false);
This represents the implementation of that grey overlay that you see on the screen.
Then, you need to go through the CupertinoPickerDefaultSelectionOverlay source code also, you should find this:
/// default margin and use rounded corners on the left and right side of the
/// rectangular overlay.
/// Default to true and must not be null.
const CupertinoPickerDefaultSelectionOverlay({
super.key,
this.background = CupertinoColors.tertiarySystemFill,
this.capStartEdge = true,
this.capEndEdge = true,
}) : assert(background != null),
assert(capStartEdge != null),
assert(capEndEdge != null);
We want to change the color of that overlay background, so in this line:
this.background = CupertinoColors.tertiarySystemFill,
We will change its value with the Color we want, so as an example we will change it like this:
this.background = const Color.fromARGB(49, 0, 253, 30),
and after a hot restart, the result will be:
Notes:
If you want to use material colors like Colors.red,
Colors.green..., you will need to import the material library on
the top of that file.
You should mark the color with a const as I did in the sample of
code, otherwise, you will get an error.
Related
I am running into a problem where I am wanting to remove the space between expansion panels of an expansion panel list when the panels are expanded.
Images of unwanted behavior, these images are taken from flutter documentation:
List when not expanded, which is fine:
List when expanded:
- You can see the gap between the sections. This is what I do not want for my app.
Any tips are appreciated.
Try Modifying The Actual Implementations
If you have some prior programming experience, this should make sense...
Find these files:
expansion_panel.dart
expansion_title.dart
expand_icon.dart
mergeable_material.dart
These files should be located in the External Library > Dart Packages > Flutter > src > material. Note that the material folder may be expressed as "src.material".
Android Studio should allow you to hover over the ExpansionPanel Widget and right-click it. After right-clicking it you should see a list of shortcuts, one being "Go to". Click on the "Go to" option which should bring up more options, one of which being "Implementation(s)". This should bring you to where you need. According to my version, double clicking the widget name so the entire widget's name is highlighted should allow for a keyboard shortcut Ctrl+Alt+B.
The key file you must go into is "mergeable_material.dart". In "mergeable.dart", go to the MaterialGap constructor:
const MaterialGap({
#required LocalKey key,
this.size = 16.0,
//This is the size of the gap between ExpansionTiles WHEN EXPANDED
}) : assert(key != null),
super(key);
This size variable controls the gap between the Expansion Tiles ONLY when EXPANDED. Setting "this.size = 0.0" should remove the gap.
If you want to know why this is, long story short, when the ExpansionPanels list of widgets property is being defined (which is in expansion_panel.dart) a "MaterialGap" is being added between the children. We modified the material gap to be 0.0 thus effectively removing the gap.
Other things you can do:
Change the Trailing Icon in the Header.
Go to expand.dart file. Go to the bottom of the file where the build method should be. In the return statement, replace the icon button with 'whatever' you want (within reason).
#override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
assert(debugCheckHasMaterialLocalizations(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final String onTapHint = widget.isExpanded ? localizations.expandedIconTapHint :
localizations.collapsedIconTapHint;
return Semantics(
onTapHint: widget.onPressed == null ? null : onTapHint,
child: Container() //Replaced the Icon Button here to remove it
);
}
Keep in mind that without a button, you must make canTapOnHeader: true, or you won't
be able to expand the panel. You can make sure you don't forget to do this by going
to the ExpansionPanel constructor and changing "this.canTapOnHeader = false;" to
"this.canTapOnHeader = true;
ExpansionPanel({
#required this.headerBuilder,
#required this.body,
this.isExpanded = false,
this.canTapOnHeader = true, //Changed this to true from false
}) : assert(headerBuilder != null),
assert(body != null),
assert(isExpanded != null),
assert(canTapOnHeader != null);
Remove the shadow and/or dividers
Go to the bottom of the expansion_panel.dart file. This is where the build method should be. At the bottom of the build method should be the return statement, which returns MergeableMaterial. You can do the following:
return MergeableMaterial(
hasDividers: false, //Change the boolean value of this
children: items,
elevation: 0,
//Add this line to remove shadow from elevation, REQUIRES ANOTHER STEP
);
If add the "elevation: 0" property you MUST go back to mergeable_material.dart and add the following line of code:
void _paintShadows(Canvas canvas, Rect rect) {
if (boxShadows == null) return; //ADD THIS LINE OF CODE
for (BoxShadow boxShadow in boxShadows) {
final Paint paint = boxShadow.toPaint();
// TODO(dragostis): Right now, we are only interpolating the border radii
// of the visible Material slices, not the shadows; they are not getting
// interpolated and always have the same rounded radii. Once shadow
// performance is better, shadows should be redrawn every single time the
// slices' radii get interpolated and use those radii not the defaults.
canvas.drawRRect(kMaterialEdges[MaterialType.card].toRRect(rect), paint);
}
}
I think you get the idea, You can do more to remove padding and whatnot by messing around with constants.
Concerns
Note: I'm relatively new to flutter, (2 months last summer and 2 months now- I'm a college student) in fact. I don't know how modifying these files may impact other widgets but I haven't noticed any issues by doing this.
This is also my first post so take it with a grain of salt. Modifying Material implementations may break some rules or conventions that I would refer to the documentation for.
Instead of changing the source code, you're better off making a copy of the expansion_panel.dart and using this. For the space between the items to disappear, you must comment out on lines 486 and 487.
if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));
And on lines 558 and 559.
if (_isChildExpanded(index) && index != widget.children.length - 1)
items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 + 1)));
Another issue with this component which you might want to fix, is with the canTapOnHeader property. Setting it to true allows you to tap the card and expand, but you're stuck with a bunch of dead space on the right side of your card. To fix this, add a check to only show expandIconContainer (line 526) as follows:
if (!child.canTapOnHeader) expandIconContainer,
The ExpansionPanelList has an elevation property, which is causing the panels to appear separated. If you don't set the elevation, it is automatically set to a value of 2. To remove the space between expanded panels, you can set the elevation to 0.
However, when you do this, you may run into other graphical issues. For example, the divider doesn't appear for an expanded panel. Without the space between panels, it makes it had to see where one panel starts and the next ends. Not a tough fix to custom code your own dividers in, but thought it was worth mentioning.
Inside AlertDialog I have TypeAheadFormField (from flutter_typeahead) and MaterialColorPicker:
MaterialColorPicker(
onMainColorChange: (ColorSwatch color) {
this._myColor = color;
},
selectedColor: _myColor,
)
...
onSuggestionSelected: (validColorInt) {
this._textController.text = 'THE TEXT IS UPDATED';
setState(() {
this._myColor = Color(validColorInt); // BUT NOT THE PICKER
});
}
I want to change the picker's selected color when the user makes a selection. What do I miss?
It seems you aren't sharing all of the code that is involved here.
With what you shared, to get the color change to reflect on your variable, and based on the documentation of the MaterialColorPicker, your code should look like this:
MaterialColorPicker(
onColorChange: (Color color) {
setState(() {
this._myColor = color;
});
},
selectedColor: Colors.red
)
I'm not sure, but you can't use setState in a dialog, because a normal dialog does not update state, you have to use an custom dialog
I have a Flutter app, where on the appBar, I have added a dropdown button. My app supports primary / secondary color changing.
The screenshot below shows an appBar for two different primary colors.
As you can see, the Flutter is able to decide what color to use for app bar text to keep proper readability - white for dark color and black for light color (second app bar).
I would like to set dropdown items' text color identical for the one, used by Flutter app bar text, hence, I would like to retrieve it.
Looking at Theme.of(context) properties, however, didn't give me a clue what color should I use to achieve what I need to.
Below is the code snippet:
final ThemeData _theme = Theme.of(context);
final int index = values.indexWhere((TimeSpan element) => element.value == initialValue.value);
return Theme(
data: _theme.copyWith(
canvasColor: _theme.primaryColor,
brightness: Brightness.dark,
),
child: DropdownButtonHideUnderline(
child: DropdownButton<TimeSpan>(
items: values
.map(
(value) => DropdownMenuItem(
child: Text(
value.toString(),
style: TextStyle(color: _theme.colorScheme.surface),
),
value: value,
),
)
.toList(),
onChanged: callback,
isExpanded: false,
value: values[index],
),
),
);
After a couple of days working with light / dark theme brightness, I figure out, a (possible) solution for the above case.
You can get the desired text color via one of the existing text themes, for instance Theme.of(context).primaryTextTheme.title.color returns color adjusted to current theme brightness.
Flutter renders the widget based on the final theme values it has.
So for example if the child widget has a different theme and the parent widget has a different theme, Flutter's rendering engine will first give preference to the child widget's theme over the parent widget's theme.
So DropdownButton has a theme hardcoded by default which cannot be directly changed as the widget does not accept any parameter to change the theme of the underlying widget(s) and as a result the Theme of the parent widget won't change/alter the theme of DropdownButton. That's the reason why the text Week remains black in both the cases.
If you really want to change the theme you can either alter the source code of DropDownButton or make a custom widget for it. However, simply hardcoding the values for text-color should still do the work.
In order to change the Appbar's text color you will have to manually change the text color as the parent theme suggests that the text color should be white whereas you want it to be black(as per your requirements).
Scaffold(
appBar: AppBar(
title: Text("Weight Overview",color: Colors.black)
)
[...]
)
On OP's request,
You can manually change the Theme of your children at any point of your Widget tree by using the Theme widget and providing it with a theme.
Solution code:
Scaffold(
appBar: AppBar(
title: Theme(
theme: ThemeData(primaryColor: Colors.black),
child: Text("Weight Overview",),
),
),
[...]
)
PopupMenuButton opens a menu when user clicks on it. But how do I do open the same menu at the same place as the PopupMenuButton from the code(e.g. due to another user event)?
I am trying to create a nested menu in the AppBar. Like when user clicks on "Sort by" then menu changes to something else. Is there another way to achieve this? I could not find it on the internet.
If you take a look at the sources of PopupMenuButton you can see that it uses showMenu method which is accessible even out of the button's context (potentially comes along with imported material Dart class).
Define a GlobalKey variable and apply it to your PopupMenuButton to have a reference to the context of your button - that's how we get the position of the button on the screen.
Create an array of items for the menu, later to reuse it in both the programmatic call and the physical button itself.
Calculate the position for the menu to appear at, and make the showMenu call.
showMenu returns a Future - listen to its completion to get the chosen item.
// Define these variables somewhere accessible by both the button on `build` & by the programmatic call.
final popupButtonKey = GlobalKey<State>(); // We use `State` because Flutter libs do not export `PopupMenuButton` state specifically.
final List<PopupMenuEntry> menuEntries = [
PopupMenuItem(
value: 1,
child: Text('One'),
),
PopupMenuItem(
value: 2,
child: Text('Two'),
),
];
In your build:
PopupMenuButton(
key: popupButtonKey,
initialValue: null,
itemBuilder: (_) => menuEntries,
// ...
);
To show the menu programmatically:
// Here we get the render object of our physical button, later to get its size & position
final RenderBox popupButtonObject = popupButtonKey.currentContext.findRenderObject();
// Get the render object of the overlay used in `Navigator` / `MaterialApp`, i.e. screen size reference
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
// Calculate the show-up area for the dropdown using button's size & position based on the `overlay` used as the coordinate space.
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
popupButtonObject.localToGlobal(Offset.zero, ancestor: overlay),
popupButtonObject.localToGlobal(popupButtonObject.size.bottomRight(Offset.zero), ancestor: overlay),
),
Offset.zero & overlay.size, // same as: new Rect.fromLTWH(0.0, 0.0, overlay.size.width, overlay.size.height)
);
// Finally, show the menu.
showMenu(
context: context,
elevation: 8.0, // default value
items: menuEntries,
initialValue: null,
position: position,
).then((res) {
print(res);
});
In Flutter is there a way to generate a material color from it's name, without creating a full map Map<String,MaterialColor>.
In theory, something like this:
String colorName = "deepOrange";
MaterialColor color = Colors(colorName);
According to the comment, the intention is to save and read back from shared_preferences. In that case, it is better to save and retrieve the color by int value, not by string name, to ensure we always get the color.
Save: prefs.setInt("prefered_color", Color.value)
Retrieve: Color c = const Color(prefs.getInt('prefered_color') ?? 0xFF42A5F5);
According to the official doc, there's currently no API to perform the function you described. Although it's easy to implement your methodology, I doubt its usefulness in general cases. Also we have to deal with typos or noSuchColor errors. But using const / enum will offer the advantage of compile time error checking.
I manage to do something like that using the
Colors.primaries list ( found in the colors.dart file):
//colors.dart
/// The material design primary color swatches, excluding grey.
static const List<MaterialColor> primaries = <MaterialColor>[
red,
pink,
purple,
deepPurple,
indigo,
blue,
lightBlue,
cyan,
teal,
green,
lightGreen,
lime,
yellow,
amber,
orange,
deepOrange,
brown,
// The grey swatch is intentionally omitted because when picking a color
// randomly from this list to colorize an application, picking grey suddenly
// makes the app look disabled.
blueGrey,
];
So in order to save the colors:
Color colorToSave = Colors.indigo;
prefs.setInt("colorIndex", Colors.primaries.indexOf(colorToSave));
To retrieve:
MaterialColor colorSaved = Colors.primaries(getInt('colorIndex') ?? 0);
Hope that helps you.
Here is an easy way to do it.
Let's suppose you want to get the material color for Colors.blue
You can use this to get it:-
MaterialStateProperty.all(Colors.blue)
For example, to set the background color of an elevated button, a material color should be provided. You can provide a named color like:-
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
),
onPressed: (){},
child: Text('Press me'),
),
Hope helps.