mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Added M3 demo app (#1189)
* Added M3 demo app * Changed to use the centralized lint configuration. * Moved the app at the root level * Added to CI * Added app to beta CI and commented app on stable CI file * Update README.md * Addressed comments * fixed comments * Reverted app name in CI files. * Added project to the Pub Dependabot config * Linting pass for current beta channel standards Co-authored-by: Qun Cheng <quncheng@google.com> Co-authored-by: Craig Labenz <craig.labenz@gmail.com>
This commit is contained in:
295
material_3_demo/lib/color_palettes_screen.dart
Normal file
295
material_3_demo/lib/color_palettes_screen.dart
Normal file
@@ -0,0 +1,295 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const Widget divider = SizedBox(height: 10);
|
||||
|
||||
// If screen content width is greater or equal to this value, the light and dark
|
||||
// color schemes will be displayed in a column. Otherwise, they will
|
||||
// be displayed in a row.
|
||||
const double narrowScreenWidthThreshold = 400;
|
||||
|
||||
class ColorPalettesScreen extends StatelessWidget {
|
||||
const ColorPalettesScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color selectedColor = Theme.of(context).primaryColor;
|
||||
ThemeData lightTheme =
|
||||
ThemeData(colorSchemeSeed: selectedColor, brightness: Brightness.light);
|
||||
ThemeData darkTheme =
|
||||
ThemeData(colorSchemeSeed: selectedColor, brightness: Brightness.dark);
|
||||
|
||||
Widget schemeLabel(String brightness) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||
child: Text(
|
||||
brightness,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget schemeView(ThemeData theme) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: ColorSchemeView(
|
||||
colorScheme: theme.colorScheme,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: LayoutBuilder(builder: (context, constraints) {
|
||||
if (constraints.maxWidth < narrowScreenWidthThreshold) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
divider,
|
||||
schemeLabel("Light Theme"),
|
||||
schemeView(lightTheme),
|
||||
divider,
|
||||
divider,
|
||||
schemeLabel("Dark Theme"),
|
||||
schemeView(darkTheme)
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
schemeLabel("Light Theme"),
|
||||
schemeView(lightTheme)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
schemeLabel("Dark Theme"),
|
||||
schemeView(darkTheme)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorSchemeView extends StatelessWidget {
|
||||
const ColorSchemeView({Key? key, required this.colorScheme})
|
||||
: super(key: key);
|
||||
|
||||
final ColorScheme colorScheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
ColorGroup(children: [
|
||||
ColorChip(
|
||||
label: "primary",
|
||||
color: colorScheme.primary,
|
||||
onColor: colorScheme.onPrimary,
|
||||
),
|
||||
ColorChip(
|
||||
label: "onPrimary",
|
||||
color: colorScheme.onPrimary,
|
||||
onColor: colorScheme.primary),
|
||||
ColorChip(
|
||||
label: "primaryContainer",
|
||||
color: colorScheme.primaryContainer,
|
||||
onColor: colorScheme.onPrimaryContainer,
|
||||
),
|
||||
ColorChip(
|
||||
label: "onPrimaryContainer",
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
onColor: colorScheme.primaryContainer,
|
||||
)
|
||||
]),
|
||||
divider,
|
||||
ColorGroup(children: [
|
||||
ColorChip(
|
||||
label: "secondary",
|
||||
color: colorScheme.secondary,
|
||||
onColor: colorScheme.onSecondary,
|
||||
),
|
||||
ColorChip(
|
||||
label: "onSecondary",
|
||||
color: colorScheme.onSecondary,
|
||||
onColor: colorScheme.secondary,
|
||||
),
|
||||
ColorChip(
|
||||
label: "secondaryContainer",
|
||||
color: colorScheme.secondaryContainer,
|
||||
onColor: colorScheme.onSecondaryContainer,
|
||||
),
|
||||
ColorChip(
|
||||
label: "onSecondaryContainer",
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
onColor: colorScheme.secondaryContainer)
|
||||
]),
|
||||
divider,
|
||||
ColorGroup(
|
||||
children: [
|
||||
ColorChip(
|
||||
label: 'tertiary',
|
||||
color: colorScheme.tertiary,
|
||||
onColor: colorScheme.onTertiary),
|
||||
ColorChip(
|
||||
label: 'onTertiary',
|
||||
color: colorScheme.onTertiary,
|
||||
onColor: colorScheme.tertiary),
|
||||
ColorChip(
|
||||
label: 'tertiaryContainer',
|
||||
color: colorScheme.tertiaryContainer,
|
||||
onColor: colorScheme.onTertiaryContainer),
|
||||
ColorChip(
|
||||
label: 'onTertiaryContainer',
|
||||
color: colorScheme.onTertiaryContainer,
|
||||
onColor: colorScheme.tertiaryContainer),
|
||||
],
|
||||
),
|
||||
divider,
|
||||
ColorGroup(
|
||||
children: [
|
||||
ColorChip(
|
||||
label: 'error',
|
||||
color: colorScheme.error,
|
||||
onColor: colorScheme.onError),
|
||||
ColorChip(
|
||||
label: 'onError',
|
||||
color: colorScheme.onError,
|
||||
onColor: colorScheme.error),
|
||||
ColorChip(
|
||||
label: 'errorContainer',
|
||||
color: colorScheme.errorContainer,
|
||||
onColor: colorScheme.onErrorContainer),
|
||||
ColorChip(
|
||||
label: 'onErrorContainer',
|
||||
color: colorScheme.onErrorContainer,
|
||||
onColor: colorScheme.errorContainer),
|
||||
],
|
||||
),
|
||||
divider,
|
||||
ColorGroup(
|
||||
children: [
|
||||
ColorChip(
|
||||
label: 'background',
|
||||
color: colorScheme.background,
|
||||
onColor: colorScheme.onBackground),
|
||||
ColorChip(
|
||||
label: 'onBackground',
|
||||
color: colorScheme.onBackground,
|
||||
onColor: colorScheme.background),
|
||||
],
|
||||
),
|
||||
divider,
|
||||
ColorGroup(
|
||||
children: [
|
||||
ColorChip(
|
||||
label: 'surface',
|
||||
color: colorScheme.surface,
|
||||
onColor: colorScheme.onSurface),
|
||||
ColorChip(
|
||||
label: 'onSurface',
|
||||
color: colorScheme.onSurface,
|
||||
onColor: colorScheme.surface),
|
||||
ColorChip(
|
||||
label: 'surfaceVariant',
|
||||
color: colorScheme.surfaceVariant,
|
||||
onColor: colorScheme.onSurfaceVariant),
|
||||
ColorChip(
|
||||
label: 'onSurfaceVariant',
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
onColor: colorScheme.surfaceVariant),
|
||||
],
|
||||
),
|
||||
divider,
|
||||
ColorGroup(
|
||||
children: [
|
||||
ColorChip(label: 'outline', color: colorScheme.outline),
|
||||
ColorChip(label: 'shadow', color: colorScheme.shadow),
|
||||
ColorChip(
|
||||
label: 'inverseSurface',
|
||||
color: colorScheme.inverseSurface,
|
||||
onColor: colorScheme.onInverseSurface),
|
||||
ColorChip(
|
||||
label: 'onInverseSurface',
|
||||
color: colorScheme.onInverseSurface,
|
||||
onColor: colorScheme.inverseSurface),
|
||||
ColorChip(
|
||||
label: 'inversePrimary',
|
||||
color: colorScheme.inversePrimary,
|
||||
onColor: colorScheme.primary),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorGroup extends StatelessWidget {
|
||||
const ColorGroup({Key? key, required this.children}) : super(key: key);
|
||||
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorChip extends StatelessWidget {
|
||||
const ColorChip({
|
||||
Key? key,
|
||||
required this.color,
|
||||
required this.label,
|
||||
this.onColor,
|
||||
}) : super(key: key);
|
||||
|
||||
final Color color;
|
||||
final Color? onColor;
|
||||
final String label;
|
||||
|
||||
static Color contrastColor(Color color) {
|
||||
final brightness = ThemeData.estimateBrightnessForColor(color);
|
||||
switch (brightness) {
|
||||
case Brightness.dark:
|
||||
return Colors.white;
|
||||
case Brightness.light:
|
||||
return Colors.black;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color labelColor = onColor ?? contrastColor(color);
|
||||
|
||||
return Container(
|
||||
color: color,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(label, style: TextStyle(color: labelColor))),
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
509
material_3_demo/lib/component_screen.dart
Normal file
509
material_3_demo/lib/component_screen.dart
Normal file
@@ -0,0 +1,509 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ComponentScreen extends StatelessWidget {
|
||||
const ComponentScreen({Key? key, required this.showNavBottomBar})
|
||||
: super(key: key);
|
||||
|
||||
final bool showNavBottomBar;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
_colDivider,
|
||||
_colDivider,
|
||||
const Buttons(),
|
||||
_colDivider,
|
||||
const FloatingActionButtons(),
|
||||
_colDivider,
|
||||
const Cards(),
|
||||
_colDivider,
|
||||
const Dialogs(),
|
||||
_colDivider,
|
||||
showNavBottomBar
|
||||
? const NavigationBars(
|
||||
selectedIndex: 0,
|
||||
isExampleBar: true,
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const _rowDivider = SizedBox(width: 10);
|
||||
const _colDivider = SizedBox(height: 10);
|
||||
const double _cardWidth = 115;
|
||||
|
||||
void Function()? handlePressed(BuildContext context, bool isDisabled, String buttonName) {
|
||||
return isDisabled
|
||||
? null
|
||||
: () {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(
|
||||
'Yay! $buttonName is clicked!',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.surface),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
textColor: Theme.of(context).colorScheme.surface,
|
||||
label: 'Close',
|
||||
onPressed: () {},
|
||||
),
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
};
|
||||
}
|
||||
|
||||
class Buttons extends StatefulWidget {
|
||||
const Buttons({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<Buttons> createState() => _ButtonsState();
|
||||
}
|
||||
|
||||
class _ButtonsState extends State<Buttons> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
children: const <Widget>[
|
||||
ButtonsWithoutIcon(isDisabled: false),
|
||||
_rowDivider,
|
||||
ButtonsWithIcon(),
|
||||
_rowDivider,
|
||||
ButtonsWithoutIcon(isDisabled: true),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonsWithoutIcon extends StatelessWidget {
|
||||
final bool isDisabled;
|
||||
|
||||
const ButtonsWithoutIcon({Key? key, required this.isDisabled})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IntrinsicWidth(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
ElevatedButton(
|
||||
onPressed: handlePressed(context, isDisabled, "ElevatedButton"),
|
||||
child: const Text("Elevated"),
|
||||
),
|
||||
_colDivider,
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
// Foreground color
|
||||
onPrimary: Theme.of(context).colorScheme.onPrimary,
|
||||
// Background color
|
||||
primary: Theme.of(context).colorScheme.primary,
|
||||
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
|
||||
onPressed: handlePressed(context, isDisabled, "FilledButton"),
|
||||
child: const Text('Filled'),
|
||||
),
|
||||
_colDivider,
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
// Foreground color
|
||||
onPrimary: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
// Background color
|
||||
primary: Theme.of(context).colorScheme.secondaryContainer,
|
||||
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
|
||||
onPressed: handlePressed(context, isDisabled, "FilledTonalButton"),
|
||||
child: const Text('Filled Tonal'),
|
||||
),
|
||||
_colDivider,
|
||||
OutlinedButton(
|
||||
onPressed: handlePressed(context, isDisabled, "OutlinedButton"),
|
||||
child: const Text("Outlined"),
|
||||
),
|
||||
_colDivider,
|
||||
TextButton(
|
||||
onPressed: handlePressed(context, isDisabled, "TextButton"),
|
||||
child: const Text("Text")),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonsWithIcon extends StatelessWidget {
|
||||
const ButtonsWithIcon({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IntrinsicWidth(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
ElevatedButton.icon(
|
||||
onPressed:
|
||||
handlePressed(context, false, "ElevatedButton with Icon"),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Icon"),
|
||||
),
|
||||
_colDivider,
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
// Foreground color
|
||||
onPrimary: Theme.of(context).colorScheme.onPrimary,
|
||||
// Background color
|
||||
primary: Theme.of(context).colorScheme.primary,
|
||||
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
|
||||
onPressed: handlePressed(context, false, "FilledButton with Icon"),
|
||||
label: const Text('Icon'),
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
_colDivider,
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
// Foreground color
|
||||
onPrimary: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
// Background color
|
||||
primary: Theme.of(context).colorScheme.secondaryContainer,
|
||||
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
|
||||
onPressed:
|
||||
handlePressed(context, false, "FilledTonalButton with Icon"),
|
||||
label: const Text('Icon'),
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
_colDivider,
|
||||
OutlinedButton.icon(
|
||||
onPressed:
|
||||
handlePressed(context, false, "OutlinedButton with Icon"),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Icon"),
|
||||
),
|
||||
_colDivider,
|
||||
TextButton.icon(
|
||||
onPressed: handlePressed(context, false, "TextButton with Icon"),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Icon"),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingActionButtons extends StatelessWidget {
|
||||
const FloatingActionButtons({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
FloatingActionButton.small(
|
||||
onPressed: () {},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
_rowDivider,
|
||||
FloatingActionButton(
|
||||
onPressed: () {},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
_rowDivider,
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Create"),
|
||||
),
|
||||
_rowDivider,
|
||||
FloatingActionButton.large(
|
||||
onPressed: () {},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Cards extends StatelessWidget {
|
||||
const Cards({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: _cardWidth,
|
||||
child: Card(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: const [
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Icon(Icons.more_vert),
|
||||
),
|
||||
_colDivider,
|
||||
_colDivider,
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text("Elevated"),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: _cardWidth,
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
elevation: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: const [
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Icon(Icons.more_vert),
|
||||
),
|
||||
_colDivider,
|
||||
_colDivider,
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text("Filled"),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: _cardWidth,
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: const [
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Icon(Icons.more_vert),
|
||||
),
|
||||
_colDivider,
|
||||
_colDivider,
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text("Outlined"),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Dialogs extends StatefulWidget {
|
||||
const Dialogs({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<Dialogs> createState() => _DialogsState();
|
||||
}
|
||||
|
||||
class _DialogsState extends State<Dialogs> {
|
||||
void openDialog(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text("Basic Dialog Title"),
|
||||
content: const Text(
|
||||
"A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made."),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('Dismiss'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Action'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextButton(
|
||||
child: const Text(
|
||||
"Open Dialog",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
onPressed: () => openDialog(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const List<NavigationDestination> appBarDestinations = [
|
||||
NavigationDestination(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.widgets_outlined),
|
||||
label: 'Components',
|
||||
selectedIcon: Icon(Icons.widgets),
|
||||
),
|
||||
NavigationDestination(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.format_paint_outlined),
|
||||
label: 'Color',
|
||||
selectedIcon: Icon(Icons.format_paint),
|
||||
),
|
||||
NavigationDestination(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.text_snippet_outlined),
|
||||
label: 'Typography',
|
||||
selectedIcon: Icon(Icons.text_snippet),
|
||||
),
|
||||
NavigationDestination(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.invert_colors_on_outlined),
|
||||
label: 'Elevation',
|
||||
selectedIcon: Icon(Icons.opacity),
|
||||
)
|
||||
];
|
||||
|
||||
final List<NavigationRailDestination> navRailDestinations = appBarDestinations
|
||||
.map((destination) => NavigationRailDestination(
|
||||
icon: Tooltip(
|
||||
message: destination.label,
|
||||
child: destination.icon,
|
||||
),
|
||||
selectedIcon: Tooltip(
|
||||
message: destination.label,
|
||||
child: destination.selectedIcon,
|
||||
),
|
||||
label: Text(destination.label)
|
||||
))
|
||||
.toList();
|
||||
|
||||
const List<Widget> exampleBarDestinations = [
|
||||
NavigationDestination(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.explore_outlined),
|
||||
label: 'Explore',
|
||||
selectedIcon: Icon(Icons.explore),
|
||||
),
|
||||
NavigationDestination(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.pets_outlined),
|
||||
label: 'Pets',
|
||||
selectedIcon: Icon(Icons.pets),
|
||||
),
|
||||
NavigationDestination(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.account_box_outlined),
|
||||
label: 'Account',
|
||||
selectedIcon: Icon(Icons.account_box),
|
||||
)
|
||||
];
|
||||
|
||||
class NavigationBars extends StatefulWidget {
|
||||
final void Function(int)? onSelectItem;
|
||||
final int selectedIndex;
|
||||
final bool isExampleBar;
|
||||
|
||||
const NavigationBars(
|
||||
{Key? key,
|
||||
this.onSelectItem,
|
||||
required this.selectedIndex,
|
||||
required this.isExampleBar})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<NavigationBars> createState() => _NavigationBarsState();
|
||||
}
|
||||
|
||||
class _NavigationBarsState extends State<NavigationBars> {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedIndex = widget.selectedIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NavigationBar(
|
||||
selectedIndex: _selectedIndex,
|
||||
onDestinationSelected: (index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
if (!widget.isExampleBar) widget.onSelectItem!(index);
|
||||
},
|
||||
destinations:
|
||||
widget.isExampleBar ? exampleBarDestinations : appBarDestinations,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NavigationRailSection extends StatefulWidget {
|
||||
final void Function(int) onSelectItem;
|
||||
final int selectedIndex;
|
||||
|
||||
const NavigationRailSection(
|
||||
{Key? key, required this.onSelectItem, required this.selectedIndex})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<NavigationRailSection> createState() => _NavigationRailSectionState();
|
||||
}
|
||||
|
||||
class _NavigationRailSectionState extends State<NavigationRailSection> {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedIndex = widget.selectedIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NavigationRail(
|
||||
minWidth: 50,
|
||||
destinations: navRailDestinations,
|
||||
selectedIndex: _selectedIndex,
|
||||
useIndicator: true,
|
||||
onDestinationSelected: (index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
widget.onSelectItem(index);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
175
material_3_demo/lib/elevation_screen.dart
Normal file
175
material_3_demo/lib/elevation_screen.dart
Normal file
@@ -0,0 +1,175 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ElevationScreen extends StatelessWidget {
|
||||
const ElevationScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color shadowColor = Theme.of(context).colorScheme.shadow;
|
||||
Color surfaceTint = Theme.of(context).colorScheme.primary;
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 20, 16.0, 0),
|
||||
child: Text(
|
||||
'Surface Tint only',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
ElevationGrid(surfaceTintColor: surfaceTint),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0),
|
||||
child: Text(
|
||||
'Surface Tint and Shadow',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
ElevationGrid(
|
||||
shadowColor: shadowColor,
|
||||
surfaceTintColor: surfaceTint,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0),
|
||||
child: Text(
|
||||
'Shadow only',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
ElevationGrid(shadowColor: shadowColor)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const double narrowScreenWidthThreshold = 450;
|
||||
|
||||
class ElevationGrid extends StatelessWidget {
|
||||
const ElevationGrid({Key? key, this.shadowColor, this.surfaceTintColor})
|
||||
: super(key: key);
|
||||
|
||||
final Color? shadowColor;
|
||||
final Color? surfaceTintColor;
|
||||
|
||||
List<ElevationCard> elevationCards(
|
||||
Color? shadowColor, Color? surfaceTintColor) {
|
||||
return elevations
|
||||
.map(
|
||||
(elevationInfo) => ElevationCard(
|
||||
info: elevationInfo,
|
||||
shadowColor: shadowColor,
|
||||
surfaceTint: surfaceTintColor,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: LayoutBuilder(builder: (context, constraints) {
|
||||
if (constraints.maxWidth < narrowScreenWidthThreshold) {
|
||||
return GridView.count(
|
||||
shrinkWrap: true,
|
||||
crossAxisCount: 3,
|
||||
children: elevationCards(shadowColor, surfaceTintColor),
|
||||
);
|
||||
} else {
|
||||
return GridView.count(
|
||||
shrinkWrap: true,
|
||||
crossAxisCount: 6,
|
||||
children: elevationCards(shadowColor, surfaceTintColor),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ElevationCard extends StatefulWidget {
|
||||
const ElevationCard(
|
||||
{Key? key, required this.info, this.shadowColor, this.surfaceTint})
|
||||
: super(key: key);
|
||||
|
||||
final ElevationInfo info;
|
||||
final Color? shadowColor;
|
||||
final Color? surfaceTint;
|
||||
|
||||
@override
|
||||
State<ElevationCard> createState() => _ElevationCardState();
|
||||
}
|
||||
|
||||
class _ElevationCardState extends State<ElevationCard> {
|
||||
late double _elevation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_elevation = widget.info.elevation;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const BorderRadius borderRadius = BorderRadius.all(Radius.circular(4.0));
|
||||
final bool showOpacity = _elevation == widget.info.elevation;
|
||||
final Color color = Theme.of(context).colorScheme.surface;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Material(
|
||||
borderRadius: borderRadius,
|
||||
elevation: _elevation,
|
||||
color: color,
|
||||
shadowColor: widget.shadowColor,
|
||||
surfaceTintColor: widget.surfaceTint,
|
||||
type: MaterialType.card,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Level ${widget.info.level}',
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
Text(
|
||||
'${widget.info.level.toInt()} dp',
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
if (showOpacity)
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Text(
|
||||
'${widget.info.overlayPercent}%',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ElevationInfo {
|
||||
const ElevationInfo(this.level, this.elevation, this.overlayPercent);
|
||||
final int level;
|
||||
final double elevation;
|
||||
final int overlayPercent;
|
||||
}
|
||||
|
||||
const List<ElevationInfo> elevations = <ElevationInfo>[
|
||||
ElevationInfo(0, 0.0, 0),
|
||||
ElevationInfo(1, 1.0, 5),
|
||||
ElevationInfo(2, 3.0, 8),
|
||||
ElevationInfo(3, 6.0, 11),
|
||||
ElevationInfo(4, 8.0, 12),
|
||||
ElevationInfo(5, 12.0, 14),
|
||||
];
|
||||
197
material_3_demo/lib/main.dart
Normal file
197
material_3_demo/lib/main.dart
Normal file
@@ -0,0 +1,197 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'color_palettes_screen.dart';
|
||||
import 'component_screen.dart';
|
||||
import 'elevation_screen.dart';
|
||||
import 'typography_screen.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const Material3Demo());
|
||||
}
|
||||
|
||||
class Material3Demo extends StatefulWidget {
|
||||
const Material3Demo({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<Material3Demo> createState() => _Material3DemoState();
|
||||
}
|
||||
|
||||
// NavigationRail shows if the screen width is greater or equal to
|
||||
// screenWidthThreshold; otherwise, NavigationBar is used for navigation.
|
||||
const double narrowScreenWidthThreshold = 450;
|
||||
|
||||
const Color m3BaseColor = Color(0xff6750a4);
|
||||
const List<Color> colorOptions = [
|
||||
m3BaseColor,
|
||||
Colors.blue,
|
||||
Colors.teal,
|
||||
Colors.green,
|
||||
Colors.yellow,
|
||||
Colors.orange,
|
||||
Colors.pink
|
||||
];
|
||||
const List<String> colorText = <String>[
|
||||
"M3 Baseline",
|
||||
"Blue",
|
||||
"Teal",
|
||||
"Green",
|
||||
"Yellow",
|
||||
"Orange",
|
||||
"Pink",
|
||||
];
|
||||
|
||||
class _Material3DemoState extends State<Material3Demo> {
|
||||
bool useMaterial3 = true;
|
||||
bool useLightMode = true;
|
||||
int colorSelected = 0;
|
||||
int screenIndex = 0;
|
||||
|
||||
late ThemeData themeData;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
|
||||
}
|
||||
|
||||
ThemeData updateThemes(int colorIndex, bool useMaterial3, bool useLightMode) {
|
||||
return ThemeData(
|
||||
colorSchemeSeed: colorOptions[colorSelected],
|
||||
useMaterial3: useMaterial3,
|
||||
brightness: useLightMode ? Brightness.light : Brightness.dark);
|
||||
}
|
||||
|
||||
void handleScreenChanged(int selectedScreen) {
|
||||
setState(() {
|
||||
screenIndex = selectedScreen;
|
||||
});
|
||||
}
|
||||
|
||||
void handleBrightnessChange() {
|
||||
setState(() {
|
||||
useLightMode = !useLightMode;
|
||||
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
|
||||
});
|
||||
}
|
||||
|
||||
void handleMaterialVersionChange() {
|
||||
setState(() {
|
||||
useMaterial3 = !useMaterial3;
|
||||
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
|
||||
});
|
||||
}
|
||||
|
||||
void handleColorSelect(int value) {
|
||||
setState(() {
|
||||
colorSelected = value;
|
||||
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
|
||||
});
|
||||
}
|
||||
|
||||
Widget createScreenFor(int screenIndex, bool showNavBarExample) {
|
||||
switch (screenIndex) {
|
||||
case 0:
|
||||
return ComponentScreen(showNavBottomBar: showNavBarExample);
|
||||
case 1:
|
||||
return const ColorPalettesScreen();
|
||||
case 2:
|
||||
return const TypographyScreen();
|
||||
case 3:
|
||||
return const ElevationScreen();
|
||||
default:
|
||||
return ComponentScreen(showNavBottomBar: showNavBarExample);
|
||||
}
|
||||
}
|
||||
|
||||
PreferredSizeWidget createAppBar() {
|
||||
return AppBar(
|
||||
title: useMaterial3 ? const Text("Material 3") : const Text("Material 2"),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: useLightMode
|
||||
? const Icon(Icons.wb_sunny_outlined)
|
||||
: const Icon(Icons.wb_sunny),
|
||||
onPressed: handleBrightnessChange,
|
||||
tooltip: "Toggle brightness",
|
||||
),
|
||||
IconButton(
|
||||
icon: useMaterial3
|
||||
? const Icon(Icons.filter_3)
|
||||
: const Icon(Icons.filter_2),
|
||||
onPressed: handleMaterialVersionChange,
|
||||
tooltip: "Switch to Material ${useMaterial3 ? 2 : 3}",
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
itemBuilder: (context) {
|
||||
return List.generate(colorOptions.length, (index) {
|
||||
return PopupMenuItem(
|
||||
value: index,
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Icon(
|
||||
index == colorSelected
|
||||
? Icons.color_lens
|
||||
: Icons.color_lens_outlined,
|
||||
color: colorOptions[index],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
child: Text(colorText[index]))
|
||||
],
|
||||
));
|
||||
});
|
||||
},
|
||||
onSelected: handleColorSelect,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Material 3',
|
||||
themeMode: useLightMode ? ThemeMode.light : ThemeMode.dark,
|
||||
theme: themeData,
|
||||
home: LayoutBuilder(builder: (context, constraints) {
|
||||
if (constraints.maxWidth < narrowScreenWidthThreshold) {
|
||||
return Scaffold(
|
||||
appBar: createAppBar(),
|
||||
body: Row(children: <Widget>[
|
||||
createScreenFor(screenIndex, false),
|
||||
]),
|
||||
bottomNavigationBar: NavigationBars(
|
||||
onSelectItem: handleScreenChanged,
|
||||
selectedIndex: screenIndex,
|
||||
isExampleBar: false,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
appBar: createAppBar(),
|
||||
body: SafeArea(
|
||||
bottom: false,
|
||||
top: false,
|
||||
child: Row(children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: NavigationRailSection(
|
||||
onSelectItem: handleScreenChanged,
|
||||
selectedIndex: screenIndex)),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
createScreenFor(screenIndex, true),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
59
material_3_demo/lib/typography_screen.dart
Normal file
59
material_3_demo/lib/typography_screen.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TypographyScreen extends StatelessWidget {
|
||||
const TypographyScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 7),
|
||||
TextStyleExample(
|
||||
name: "Display Large", style: textTheme.displayLarge!),
|
||||
TextStyleExample(
|
||||
name: "Display Medium", style: textTheme.displayMedium!),
|
||||
TextStyleExample(
|
||||
name: "Display Small", style: textTheme.displaySmall!),
|
||||
TextStyleExample(
|
||||
name: "Headline Large", style: textTheme.headlineLarge!),
|
||||
TextStyleExample(
|
||||
name: "Headline Medium", style: textTheme.headlineMedium!),
|
||||
TextStyleExample(
|
||||
name: "Headline Small", style: textTheme.headlineSmall!),
|
||||
TextStyleExample(name: "Title Large", style: textTheme.titleLarge!),
|
||||
TextStyleExample(name: "Title Medium", style: textTheme.titleMedium!),
|
||||
TextStyleExample(name: "Title Small", style: textTheme.titleSmall!),
|
||||
TextStyleExample(name: "Label Large", style: textTheme.labelLarge!),
|
||||
TextStyleExample(name: "Label Medium", style: textTheme.labelMedium!),
|
||||
TextStyleExample(name: "Label Small", style: textTheme.labelSmall!),
|
||||
TextStyleExample(name: "Body Large", style: textTheme.bodyLarge!),
|
||||
TextStyleExample(name: "Body Medium", style: textTheme.bodyMedium!),
|
||||
TextStyleExample(name: "Body Small", style: textTheme.bodySmall!),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TextStyleExample extends StatelessWidget {
|
||||
const TextStyleExample({
|
||||
Key? key,
|
||||
required this.name,
|
||||
required this.style,
|
||||
}) : super(key: key);
|
||||
|
||||
final String name;
|
||||
final TextStyle style;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(name, style: style),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user