Welcome to Day 13 of the "Hundred Days of Flutter" course! Today, we'll explore styling and theming in Flutter, which is essential for creating visually consistent and appealing applications.
MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
scaffoldBackgroundColor: Colors.white,
),
home: MyHomePage(),
)
ThemeData(
// Colors
primaryColor: Colors.blue,
accentColor: Colors.orange,
backgroundColor: Colors.white,
// Typography
textTheme: TextTheme(
headline1: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
bodyText1: TextStyle(fontSize: 16),
caption: TextStyle(fontSize: 12),
),
// Component Themes
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
// Other Properties
cardTheme: CardTheme(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
)
ThemeData(
colorScheme: ColorScheme.light(
primary: Colors.blue,
secondary: Colors.orange,
surface: Colors.white,
background: Colors.grey[100],
error: Colors.red,
onPrimary: Colors.white,
onSecondary: Colors.white,
onSurface: Colors.black,
onBackground: Colors.black,
onError: Colors.white,
),
)
ThemeData(
textTheme: TextTheme(
// Headlines
headline1: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
letterSpacing: -1.5,
),
headline2: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
letterSpacing: -0.5,
),
// Body
bodyText1: TextStyle(
fontSize: 16,
letterSpacing: 0.15,
),
bodyText2: TextStyle(
fontSize: 14,
letterSpacing: 0.25,
),
// Captions
caption: TextStyle(
fontSize: 12,
letterSpacing: 0.4,
),
),
)
class CustomTheme extends ThemeExtension<CustomTheme> {
final Color customColor;
final double customSpacing;
final TextStyle customTextStyle;
CustomTheme({
required this.customColor,
required this.customSpacing,
required this.customTextStyle,
});
@override
ThemeExtension<CustomTheme> copyWith({
Color? customColor,
double? customSpacing,
TextStyle? customTextStyle,
}) {
return CustomTheme(
customColor: customColor ?? this.customColor,
customSpacing: customSpacing ?? this.customSpacing,
customTextStyle: customTextStyle ?? this.customTextStyle,
);
}
@override
ThemeExtension<CustomTheme> lerp(
ThemeExtension<CustomTheme>? other,
double t,
) {
if (other is! CustomTheme) {
return this;
}
return CustomTheme(
customColor: Color.lerp(customColor, other.customColor, t)!,
customSpacing: lerpDouble(customSpacing, other.customSpacing, t)!,
customTextStyle: TextStyle.lerp(customTextStyle, other.customTextStyle, t)!,
);
}
}
// Usage
ThemeData(
extensions: [
CustomTheme(
customColor: Colors.purple,
customSpacing: 16.0,
customTextStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
)
MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system, // or ThemeMode.light or ThemeMode.dark
home: MyHomePage(),
)
class ThemeSwitcher extends StatefulWidget {
@override
_ThemeSwitcherState createState() => _ThemeSwitcherState();
}
class _ThemeSwitcherState extends State<ThemeSwitcher> {
bool _isDarkMode = false;
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: _isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
appBar: AppBar(
title: Text('Theme Switcher'),
actions: [
IconButton(
icon: Icon(_isDarkMode ? Icons.light_mode : Icons.dark_mode),
onPressed: _toggleTheme,
),
],
),
body: Center(
child: Text('Theme Switcher Example'),
),
),
);
}
}
class ResponsiveLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return MobileLayout();
} else if (constraints.maxWidth < 900) {
return TabletLayout();
} else {
return DesktopLayout();
}
},
);
}
}
class ResponsiveWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final orientation = MediaQuery.of(context).orientation;
return Container(
width: size.width * 0.8,
height: orientation == Orientation.portrait
? size.height * 0.3
: size.height * 0.5,
child: Center(
child: Text('Responsive Widget'),
),
);
}
}
Let's test your understanding of today's concepts:
What is the purpose of ThemeData in Flutter?
Which property is used to switch between light and dark themes?
What is the purpose of LayoutBuilder in responsive design?
Create a themed todo app that:
Here's a starting point:
class ThemedTodoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light().copyWith(
primaryColor: Colors.blue,
colorScheme: ColorScheme.light(
primary: Colors.blue,
secondary: Colors.orange,
surface: Colors.white,
background: Colors.grey[100],
error: Colors.red,
),
textTheme: TextTheme(
headline1: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.black,
),
bodyText1: TextStyle(
fontSize: 16,
color: Colors.black87,
),
),
cardTheme: CardTheme(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
darkTheme: ThemeData.dark().copyWith(
primaryColor: Colors.blue,
colorScheme: ColorScheme.dark(
primary: Colors.blue,
secondary: Colors.orange,
surface: Colors.grey[900],
background: Colors.black,
error: Colors.red,
),
textTheme: TextTheme(
headline1: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
bodyText1: TextStyle(
fontSize: 16,
color: Colors.white70,
),
),
cardTheme: CardTheme(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
themeMode: ThemeMode.system,
home: TodoScreen(),
);
}
}
class TodoScreen extends StatefulWidget {
@override
_TodoScreenState createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final List<String> _todos = [];
final _controller = TextEditingController();
bool _isDarkMode = false;
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
void _addTodo() {
if (_controller.text.isNotEmpty) {
setState(() {
_todos.add(_controller.text);
_controller.clear();
});
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Themed Todo App'),
actions: [
IconButton(
icon: Icon(_isDarkMode ? Icons.light_mode : Icons.dark_mode),
onPressed: _toggleTheme,
),
],
),
body: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'My Todos',
style: theme.textTheme.headline1,
),
SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Add a new todo',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: _addTodo,
child: Text('Add'),
),
],
),
SizedBox(height: 24),
if (constraints.maxWidth > 600)
GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: _todos.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(_todos[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
_todos.removeAt(index);
});
},
),
),
);
},
)
else
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: _todos.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(_todos[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
_todos.removeAt(index);
});
},
),
),
);
},
),
],
),
);
},
),
);
}
}
Tomorrow, we'll explore gestures and animations in Flutter!
Learn how to work with assets and resources in Flutter, including adding images, custom fonts, loading assets, asset bundling, and platform-specific assets.
Start Previous DayLearn about gesture detection, basic animations, and animation controllers in Flutter, including GestureDetector, InkWell, AnimatedContainer, and custom animations.
Start Next Day