Is there any way to apply custom theme for ExpansionTile. In my case, I want to have the different background colour for Header and children of the expansion tile but When ever the ExpansionTile is expanded, Headers background color changes to that of children?
To apply a custom Theme to any widget, just wrap it with the Theme() widget.
and then specify your custom theme in the data field of the widget.
Theme(
data: ThemeData(/*specify your custom theme here*/),
child: ExpansionTile() /*or any other widget you want to apply the theme to.*/
)
In your case, to customise the header in ExpansionTile,
when ExpansionTile is closed
the style of header text i.e. title depends on
ThemeData.textTheme.subhead (in flutter 2 it's ThemeData.textTheme.subtitle1)
whereas, the style of the arrow icon depends
on ThemeData.unselectedWidget (in flutter 2 it's ThemeData.unselectedWidgetColor)
when ExpansionTile is open
the color of both the widgets depends on ThemeData.accentColor
In a similar fashion you can customise almost any part of the expansion tile by tweaking the theme. For more details check out this link.
Since flutter is built with flexibility in mind. You can do almost anything you want. Create almost any UI you want.
Following the idea of #user3315892, you can implement your own stateful widget for the ExpansionTile, so that you can control what colours you want the header and children to be when the header is expanded or collapsed.
The following example only changes the text foreground and background colours of the header when the expansion tile is expanded or collapsed, but you can do the same for the children widgets.
class CustomExpansionTile extends StatefulWidget {
#override
State createState() => CustomExpansionTileState();
}
class CustomExpansionTileState extends State<CustomExpansionTile> {
bool isExpanded = false;
#override
Widget build(BuildContext context) {
return ExpansionTile(
title: Container(
child: Text(
"HEADER HERE",
style: TextStyle(
color: isExpanded ? Colors.pink : Colors.teal,
),
),
// Change header (which is a Container widget in this case) background colour here.
color: isExpanded ? Colors.orange : Colors.green,
),
leading: Icon(
Icons.face,
size: 36.0,
),
children: <Widget>[
Text("Child Widget One"),
Text("Child Widget Two"),
],
onExpansionChanged: (bool expanding) => setState(() => this.isExpanded = expanding),
);
}
}
I found another way to do it. I don't know if this is the best way but it's simpler for me.
To change the ExpansionTile color, you can wrap it with a Container and give it a color. This will change the entire color of the ExpansionTile, both collapsed and expanded.
But if you want a different color for the children, you can also wrap them with a Container and set another color there. However, keep in mind that you have to "expand" that Container to occupy all the available space (you can do it by setting width and height parameters properly).
Btw, that doesn't change the arrow color.
Widget expansionTileTest(context) {
return Container(
color: Colors.grey,
child: ExpansionTile(
title: Text(
"Title",
style: TextStyle(color: Colors.black),
),
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height * 0.1,
width: MediaQuery.of(context).size.width,
color: Colors.yellow,
child: Center(child: Text("Hi")),
),
],
),
);
}
Collapsed
Expanded
You can set color of header and then body/children wrap in Container and set color to that container.
I used this code to make the trailing ion color Black.
Theme(
data: Theme.of(context).copyWith(accentColor: Colors.black),
child: ExpansionTile(
backgroundColor: Colors.white,
title: Text(
'Title Exmample',
style: TextStyle(color: Colors.black),
),
leading: CircleAvatar(
backgroundColor: Colors.black.withOpacity(.07),
child: Icon(
Icons.apps_outlined,
color: Theme.of(context).primaryColor,
)),
children: _buildTiles(Theme.of(context).primaryColor, Colors.black.withOpacity(.07), context),
),
);
If you want the ExpansionTile title to remain the same color whether closed or expanded, you can set it's style:
ExpansionTile(
title: Text('HEADER HERE', style: TextStyle(color: Colors.red)),
...
),
Additional styling for ExpansionTile is an open feature request:
Feature request: More styling of ExpansionTile #15427
Related
I'm looking to create a simple layout using a scrollable ListView, with an immovable header tile. I've placed the header tile and ListView into a Column, so the Header can be above the ListView without scrolling off the screen. However, when scrolling the ListView, my header tile seems to take on the color of whatever ListView tiles are scrolling "under" it.
On startup, the app looks like this:
However if we scroll half a tile down, the green color of the list tiles appears to push out the red from the header. The text from the green tiles does not have the same problem, and is properly occluded by the header tile
Minimal code for reconstruction
void main() => runApp(MyTestApp2());
class MyTestApp2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("AppBar Header"),
),
body: Column(children: <Widget>[
ListTile(
title: Center(child: Text("This Header Should be Red")),
tileColor: Colors.redAccent,
),
Expanded(child: ListView(
children: List.generate(20, (int index) => "List Item $index")
.map((n) => ListTile(
title: Text(n.toString()),
tileColor: Colors.lightGreen))
.toList(),
)),
])),
);
}
}
What is happening here? I could probably achieve this layout using different Widgets, but I would like to know Why is this color bleed effect occurring? How does it fit with the box layout model Flutter uses?
EDIT: The immediate problem can be solved by wrapping the red ListTile in a Container widget, and setting the color property to be red on that container, like this:
Container(
color: Colors.redAccent,
child: ListTile(....
However I would still like to know what is going on in terms of the layout algorithm in the original code, if anybody knows. Shouldn't the existence of the header tile prevent our listview from pushing its elements into the area owned by the red ListTile?
Please try wrapping listTile with Material widget
I do not know why but the tileColor is keeping alive under the widgets while scrolling down the color of the tile went under the widgets above.
Wrapping the listTile with Material should fix that for some reason.
Material(
child: ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
tileColor:const Color.fromARGB(255, 247, 247, 247),
leading: Icon(
Icons.monetization_on_outlined,
color: Theme.of(context).primaryColor,
size: 50,
),
title: Text(
"Some Strings",
),
subtitle: Text("Some Strings"),
onLongPress: () {},
onTap: () {},
),
)
When I wrote the following code, I expected the text to be red:
Theme(
data: Theme.of(context).copyWith(
textTheme: Theme.of(context).textTheme.copyWith(
bodyText2: TextStyle(color: Colors.red),
),
buttonTheme: Theme.of(context)
.buttonTheme
.copyWith(buttonColor: Colors.yellow),
),
child: Builder(
builder: (context) {
return Column(
children: [
Text('Flutter'),
Text('is awesome!'),
RaisedButton(
onPressed: () {},
child: Text('OK'),
)
],
);
},
),
)
but the text was black while the button was yellow as you can see:
here
As you can see the Text widgets ignored the style defined in the theme while the RaisedButton didn't. Why?
I know that I can use DefaultTextStyle instead but I'm trying to understand why is that not working like I expected.
Text widget has style property. As we can see from docs:
If the [style] argument is null, the text will use the style from the
closest enclosing [DefaultTextStyle].
It is clear, Text uses style from DefaultTextStyle widget (not from Theme) if you didn't specify it as you did. If you want to use style from theme you should specify it explicitly:
Text('Flutter', style: Theme.of(context).textTheme.bodyText2)
As for buttons - any MaterialButton child (RaisedButton too) use ButtonTheme.of(context).textTheme as default value of textTheme property.
I have an ExpansionTile within my Drawer widget. When I expand this item, it automatically adds a divider line above and below. I want these divider lines permanently.
So I'd either like to know how to show the ExpansionTile's divider lines always (expanded and unexpanded), or I can add my own divider lines and tell the ExpansionTile to never show divider lines.
Is this possible? Thanks.
you can hide divider lines by wrapping the ExpansionTile widget in Theme Widget,
your code will look like this after you add Theme widget
Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child:ExpansionTile(
title:...,
children:[],
),
),
check this Github Issue
#RJB, I had the same issue, I resolved wrapping the ExpansionTile with a column, like this:
Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: Column(
children: [
ExpansionTile(
title: Text(
'My title',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
trailing: Icon(
_showContent
? Icons.expand_less_rounded
: Icons.expand_more_rounded,
),
onExpansionChanged: (bool expanded) {
setState(() => _showContent = expanded);
},
children: <Widget>[
Text('My content'),
],
),
const Divider(
color: Colors.amber,
thickness: 1,
height: 0,
)
],
),
);
I know it isn't pretty, but I could't find an Expansion component that I could personalize every aspect of its appearance.
Another option is that instead of wrapping the ExpansionTile widget with Theme, you can pass the shape directly to ExpansionTile as follows:
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.transparent),
),
I don't want to change the text color of the whole app. Just all the text inside a container. Can I wrap it with some other widget or something for this ?
To apply certain TextStyle properties only to a subtree of your app. You can use DefaultTextStyle
DefaultTextStyle(
child: Container(child: /* your subtree */),
style: TextStyle(color: Colors.red),
),
as a comment pointed out, this replaces all defaults, not just the color. This can be mitigated by using the merge constructor:
DefaultTextStyle.merge(
child: Container(child: /* your subtree */),
style: TextStyle(color: Colors.red),
),
flutter's answer is good in my opinion. But the power of ThemeData is more than you think. Here is the official documentation about Themes for part of an application.
You could provide a Theme to wrap your container to provide a new theme. Here is two way to slove it:
1. Creating unique ThemeData
/*Not recommended, this could make a totally different If you just want a little part changed.*/
Theme(
// Create a unique theme with "ThemeData"
data: ThemeData(
textTheme: /* Your Text Theme*/,
),
child: Container(
onPressed: () {},
child: Text("Your Text Here"),
),
);
2. Extending the parent theme
Theme(
// Find and extend the parent theme using "copyWith". See the next
// section for more info on `Theme.of`.
data: Theme.of(context).copyWith(textTheme: /* Provide your theme here! */),
child: Container(
child: Text("your text here"),
),
);
You could also use existed theme with a little changed:
Theme.of(context).textTheme.copyWith(
body1: Theme.of(context).textTheme.body1.copyWith(color: Colors.red),
)
Use DefaultTextStyle.merge to keep your theme and just change the color.
DefaultTextStyle.merge(
style: TextStyle(color: Colors.grey[400]),
child: Column(...),
)
If you are using the MaterialApp widget you could use the theme property of it and set different Text themes and call them anywhere in your app. For example the following code defines 3 different text themes:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Time Tracker",
theme: ThemeData(
textTheme: TextTheme(
headline: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold,color: Colors.blue),
title: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic,color: Colors.red),
body1: TextStyle(fontSize: 14.0, fontFamily: 'Hind',color: Colors.yellow),
),
),
home: LandingPage(),
);
}
}
You can then call a particular theme(headline) anywhere in your app like this:
Text('Home Page',style: Theme.of(context).textTheme.headline,)
Which gives you the headline TextTheme
I have functions for all my styles
TextStyle largeTextStyle() => TextStyle(fontSize: 150);
then I just do
Text("blah", style:largeTextStyle())
Is there a way to set the fontFamily for all buttons in a Flutter app?
I see I can set my fontFamily for my MaterialApp using theme.fontFamily, but I'd like to use a different fontFamily for all my buttons.
I saw there is also a ButtonThemeData, but it seems to be related to colors and shapes only.
I don't want to set my fontFamily explicitly every time I use a button or having to wrap all types of buttons, is there any way to accomplish this?
Thanks!
You should use themes to customize fonts for whole widgets, including buttons : https://flutter.dev/docs/cookbook/design/fonts
Simplest might be to create a helper method that returns a Button configured as you wish.
I recomend creating a custom button that "extends" Flutter MaterialButton or RawMaterialButton. Remember to add buttonText as a paramater too if you want your button to be reusable. Also remember to add TextStyle(fontFamily: 'Raleway') to the Text widget style.
Another option would be to "extend" the Flutter Text widget in the same way as with the button example below, and add your CustomTextWidget as a child to the Flutter MaterialButton widget. I prefer to use both in a combination. CustomButton together with CustomText widget.
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
CustomButton({#required this.onPressed});
final GestureTapCallback onPressed;
#override
Widget build(BuildContext context) {
return RawMaterialButton(
fillColor: Colors.green,
splashColor: Colors.greenAccent,
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Icon(
Icons.face,
color: Colors.amber,
),
SizedBox(
width: 10.0,
),
Text(
"Tap Me",
maxLines: 1,
style: TextStyle(color: Colors.white),
),
],
),
),
onPressed: onPressed,
shape: const StadiumBorder(),
);
}
}
Here is the implementation:
CustomButton(
onPressed: () {
print("Tapped Me");
},
)