mirror of
https://github.com/flutter/samples.git
synced 2025-11-10 14:58:34 +00:00
New M3 Demo App for Master Channel (#1378)
* Added components * Added test * Added README * Run flutter format * Added all the updated components and tests * Added README file * Update README.md * Add textfields to M3 demo app * move textfield to exprerimental/ * Update component_screen_test.dart * Fixed comment: changed sdk version * Changed all double quotes to single * Added trailing comma, changed all private fields to public except private state classes * Added enum for screen selection * Removed comment for material_3_demo * Added experimental m3 demo app to master CI pipeline * fixed failing tests * Modified the analysis_options.yaml * Fixed warning * Ran flutter format command Co-authored-by: Qun Cheng <quncheng@google.com> Co-authored-by: hangyu <108393416+hangyujin@users.noreply.github.com>
This commit is contained in:
302
experimental/material_3_demo/lib/color_palettes_screen.dart
Normal file
302
experimental/material_3_demo/lib/color_palettes_screen.dart
Normal file
@@ -0,0 +1,302 @@
|
||||
// Copyright 2021 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
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({super.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({super.key, required this.colorScheme});
|
||||
|
||||
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({super.key, required this.children});
|
||||
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorChip extends StatelessWidget {
|
||||
const ColorChip({
|
||||
super.key,
|
||||
required this.color,
|
||||
required this.label,
|
||||
this.onColor,
|
||||
});
|
||||
|
||||
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))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
947
experimental/material_3_demo/lib/component_screen.dart
Normal file
947
experimental/material_3_demo/lib/component_screen.dart
Normal file
@@ -0,0 +1,947 @@
|
||||
// Copyright 2021 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ComponentScreen extends StatelessWidget {
|
||||
const ComponentScreen({super.key, required this.showNavBottomBar});
|
||||
|
||||
final bool showNavBottomBar;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SizedBox(
|
||||
width: maxWidthConstraint,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
colDivider,
|
||||
colDivider,
|
||||
const Buttons(),
|
||||
colDivider,
|
||||
colDivider,
|
||||
const IconToggleButtons(),
|
||||
colDivider,
|
||||
const FloatingActionButtons(),
|
||||
colDivider,
|
||||
const Chips(),
|
||||
colDivider,
|
||||
const Cards(),
|
||||
colDivider,
|
||||
const TextFields(),
|
||||
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;
|
||||
const double maxWidthConstraint = 400;
|
||||
|
||||
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({super.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({super.key, required this.isDisabled});
|
||||
|
||||
@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(
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: 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(
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
backgroundColor: 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({super.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(
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: 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(
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
backgroundColor: 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({super.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({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: cardWidth,
|
||||
child: Tooltip(
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
message: 'Elevated Card',
|
||||
child: Card(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: const [
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Icon(Icons.more_vert),
|
||||
),
|
||||
SizedBox(height: 35),
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text('Elevated'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: cardWidth,
|
||||
child: Tooltip(
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
message: 'Filled Card',
|
||||
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),
|
||||
),
|
||||
SizedBox(height: 35),
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text('Filled'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: cardWidth,
|
||||
child: Tooltip(
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
message: 'Outlined Card',
|
||||
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),
|
||||
),
|
||||
SizedBox(height: 35),
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text('Outlined'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TextFields extends StatelessWidget {
|
||||
const TextFields({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search),
|
||||
suffixIcon: Icon(Icons.clear),
|
||||
labelText: 'Filled',
|
||||
hintText: 'hint text',
|
||||
helperText: 'supporting text',
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: const [
|
||||
SizedBox(
|
||||
width: 170,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search),
|
||||
suffixIcon: Icon(Icons.clear),
|
||||
labelText: 'Filled',
|
||||
hintText: 'hint text',
|
||||
helperText: 'supporting text',
|
||||
filled: true,
|
||||
errorText: 'error text',
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 170,
|
||||
child: TextField(
|
||||
enabled: false,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search),
|
||||
suffixIcon: Icon(Icons.clear),
|
||||
labelText: 'Disabled',
|
||||
hintText: 'hint text',
|
||||
helperText: 'supporting text',
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search),
|
||||
suffixIcon: Icon(Icons.clear),
|
||||
labelText: 'Outlined',
|
||||
hintText: 'hint text',
|
||||
helperText: 'supporting text',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: const [
|
||||
SizedBox(
|
||||
width: 170,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search),
|
||||
suffixIcon: Icon(Icons.clear),
|
||||
labelText: 'Outlined',
|
||||
hintText: 'hint text',
|
||||
helperText: 'supporting text',
|
||||
errorText: 'error text',
|
||||
border: OutlineInputBorder(),
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 170,
|
||||
child: TextField(
|
||||
enabled: false,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search),
|
||||
suffixIcon: Icon(Icons.clear),
|
||||
labelText: 'Disabled',
|
||||
hintText: 'hint text',
|
||||
helperText: 'supporting text',
|
||||
border: OutlineInputBorder(),
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
])),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Dialogs extends StatefulWidget {
|
||||
const Dialogs({super.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: 'Updated component list',
|
||||
icon: Icon(Icons.widgets_outlined),
|
||||
label: 'Components',
|
||||
selectedIcon: Icon(Icons.widgets),
|
||||
),
|
||||
NavigationDestination(
|
||||
tooltip: 'Color palettes in light and dark modes',
|
||||
icon: Icon(Icons.format_paint_outlined),
|
||||
label: 'Color',
|
||||
selectedIcon: Icon(Icons.format_paint),
|
||||
),
|
||||
NavigationDestination(
|
||||
tooltip: 'Different text styles for the default TextTheme',
|
||||
icon: Icon(Icons.text_snippet_outlined),
|
||||
label: 'Typography',
|
||||
selectedIcon: Icon(Icons.text_snippet),
|
||||
),
|
||||
NavigationDestination(
|
||||
tooltip:
|
||||
'Different ways of elevation with a new supported feature "surfaceTintColor"',
|
||||
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({
|
||||
super.key,
|
||||
this.onSelectItem,
|
||||
required this.selectedIndex,
|
||||
required this.isExampleBar,
|
||||
});
|
||||
|
||||
@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(
|
||||
{super.key, required this.onSelectItem, required this.selectedIndex});
|
||||
|
||||
@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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IconToggleButtons extends StatefulWidget {
|
||||
const IconToggleButtons({super.key});
|
||||
|
||||
@override
|
||||
State<IconToggleButtons> createState() => _IconToggleButtonsState();
|
||||
}
|
||||
|
||||
class _IconToggleButtonsState extends State<IconToggleButtons> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
// Standard IconButton
|
||||
children: const <Widget>[
|
||||
IconToggleButton(isEnabled: true),
|
||||
colDivider,
|
||||
IconToggleButton(isEnabled: false),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const <Widget>[
|
||||
// Filled IconButton
|
||||
IconToggleButton(
|
||||
isEnabled: true,
|
||||
getDefaultStyle: enabledFilledButtonStyle,
|
||||
),
|
||||
colDivider,
|
||||
IconToggleButton(
|
||||
isEnabled: false,
|
||||
getDefaultStyle: disabledFilledButtonStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const <Widget>[
|
||||
// Filled Tonal IconButton
|
||||
IconToggleButton(
|
||||
isEnabled: true,
|
||||
getDefaultStyle: enabledFilledTonalButtonStyle,
|
||||
),
|
||||
colDivider,
|
||||
IconToggleButton(
|
||||
isEnabled: false,
|
||||
getDefaultStyle: disabledFilledTonalButtonStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const <Widget>[
|
||||
// Outlined IconButton
|
||||
IconToggleButton(
|
||||
isEnabled: true,
|
||||
getDefaultStyle: enabledOutlinedButtonStyle,
|
||||
),
|
||||
colDivider,
|
||||
IconToggleButton(
|
||||
isEnabled: false,
|
||||
getDefaultStyle: disabledOutlinedButtonStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IconToggleButton extends StatefulWidget {
|
||||
const IconToggleButton({
|
||||
required this.isEnabled,
|
||||
this.getDefaultStyle,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool isEnabled;
|
||||
final ButtonStyle? Function(bool, ColorScheme)? getDefaultStyle;
|
||||
|
||||
@override
|
||||
State<IconToggleButton> createState() => _IconToggleButtonState();
|
||||
}
|
||||
|
||||
class _IconToggleButtonState extends State<IconToggleButton> {
|
||||
bool selected = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ColorScheme colors = Theme.of(context).colorScheme;
|
||||
final VoidCallback? onPressed = widget.isEnabled
|
||||
? () {
|
||||
setState(() {
|
||||
selected = !selected;
|
||||
});
|
||||
}
|
||||
: null;
|
||||
ButtonStyle? style = widget.getDefaultStyle?.call(selected, colors);
|
||||
|
||||
return IconButton(
|
||||
visualDensity: VisualDensity.standard,
|
||||
isSelected: selected,
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
selectedIcon: const Icon(Icons.settings),
|
||||
onPressed: onPressed,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ButtonStyle enabledFilledButtonStyle(bool selected, ColorScheme colors) {
|
||||
return IconButton.styleFrom(
|
||||
foregroundColor: selected ? colors.onPrimary : colors.primary,
|
||||
backgroundColor: selected ? colors.primary : colors.surfaceVariant,
|
||||
disabledForegroundColor: colors.onSurface.withOpacity(0.38),
|
||||
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
|
||||
hoverColor: selected
|
||||
? colors.onPrimary.withOpacity(0.08)
|
||||
: colors.primary.withOpacity(0.08),
|
||||
focusColor: selected
|
||||
? colors.onPrimary.withOpacity(0.12)
|
||||
: colors.primary.withOpacity(0.12),
|
||||
highlightColor: selected
|
||||
? colors.onPrimary.withOpacity(0.12)
|
||||
: colors.primary.withOpacity(0.12),
|
||||
);
|
||||
}
|
||||
|
||||
ButtonStyle disabledFilledButtonStyle(bool selected, ColorScheme colors) {
|
||||
return IconButton.styleFrom(
|
||||
disabledForegroundColor: colors.onSurface.withOpacity(0.38),
|
||||
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
|
||||
);
|
||||
}
|
||||
|
||||
ButtonStyle enabledFilledTonalButtonStyle(bool selected, ColorScheme colors) {
|
||||
return IconButton.styleFrom(
|
||||
foregroundColor:
|
||||
selected ? colors.onSecondaryContainer : colors.onSurfaceVariant,
|
||||
backgroundColor:
|
||||
selected ? colors.secondaryContainer : colors.surfaceVariant,
|
||||
hoverColor: selected
|
||||
? colors.onSecondaryContainer.withOpacity(0.08)
|
||||
: colors.onSurfaceVariant.withOpacity(0.08),
|
||||
focusColor: selected
|
||||
? colors.onSecondaryContainer.withOpacity(0.12)
|
||||
: colors.onSurfaceVariant.withOpacity(0.12),
|
||||
highlightColor: selected
|
||||
? colors.onSecondaryContainer.withOpacity(0.12)
|
||||
: colors.onSurfaceVariant.withOpacity(0.12),
|
||||
);
|
||||
}
|
||||
|
||||
ButtonStyle disabledFilledTonalButtonStyle(bool selected, ColorScheme colors) {
|
||||
return IconButton.styleFrom(
|
||||
disabledForegroundColor: colors.onSurface.withOpacity(0.38),
|
||||
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
|
||||
);
|
||||
}
|
||||
|
||||
ButtonStyle enabledOutlinedButtonStyle(bool selected, ColorScheme colors) {
|
||||
return IconButton.styleFrom(
|
||||
backgroundColor: selected ? colors.inverseSurface : null,
|
||||
hoverColor: selected
|
||||
? colors.onInverseSurface.withOpacity(0.08)
|
||||
: colors.onSurfaceVariant.withOpacity(0.08),
|
||||
focusColor: selected
|
||||
? colors.onInverseSurface.withOpacity(0.12)
|
||||
: colors.onSurfaceVariant.withOpacity(0.12),
|
||||
highlightColor: selected
|
||||
? colors.onInverseSurface.withOpacity(0.12)
|
||||
: colors.onSurface.withOpacity(0.12),
|
||||
side: BorderSide(color: colors.outline),
|
||||
).copyWith(
|
||||
foregroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return colors.onInverseSurface;
|
||||
}
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return colors.onSurface;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ButtonStyle disabledOutlinedButtonStyle(bool selected, ColorScheme colors) {
|
||||
return IconButton.styleFrom(
|
||||
disabledForegroundColor: colors.onSurface.withOpacity(0.38),
|
||||
disabledBackgroundColor:
|
||||
selected ? colors.onSurface.withOpacity(0.12) : null,
|
||||
side: selected ? null : BorderSide(color: colors.outline.withOpacity(0.12)),
|
||||
);
|
||||
}
|
||||
|
||||
class Chips extends StatelessWidget {
|
||||
const Chips({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
runSpacing: 5,
|
||||
children: <Widget>[
|
||||
ActionChip(
|
||||
label: const Text('Assist'),
|
||||
avatar: const Icon(Icons.chat_outlined),
|
||||
onPressed: () {}),
|
||||
ActionChip(
|
||||
label: const Text('Set alarm'),
|
||||
avatar: const Icon(Icons.alarm_add_outlined),
|
||||
onPressed: () {}),
|
||||
const ActionChip(
|
||||
label: Text('No Action'),
|
||||
avatar: Icon(Icons.indeterminate_check_box_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
colDivider,
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
runSpacing: 5,
|
||||
children: <Widget>[
|
||||
FilterChip(
|
||||
label: const Text('Filter'),
|
||||
onSelected: (isSelected) {},
|
||||
),
|
||||
FilterChip(
|
||||
label: const Text('OK'),
|
||||
selected: true,
|
||||
onSelected: (isSelected) {},
|
||||
),
|
||||
const FilterChip(
|
||||
label: Text('Disabled'),
|
||||
selected: true,
|
||||
onSelected: null,
|
||||
),
|
||||
const FilterChip(
|
||||
label: Text('Disabled'),
|
||||
onSelected: null,
|
||||
)
|
||||
],
|
||||
),
|
||||
colDivider,
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
runSpacing: 5,
|
||||
children: <Widget>[
|
||||
InputChip(
|
||||
label: const Text('Input'),
|
||||
onDeleted: () {},
|
||||
),
|
||||
InputChip(
|
||||
label: const Text('Egg'),
|
||||
onDeleted: () {},
|
||||
),
|
||||
InputChip(
|
||||
label: const Text('Lettuce'),
|
||||
showCheckmark: false,
|
||||
selected: true,
|
||||
onDeleted: () {},
|
||||
),
|
||||
const InputChip(
|
||||
label: Text('No'),
|
||||
isEnabled: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
colDivider,
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
runSpacing: 5,
|
||||
children: <Widget>[
|
||||
ActionChip(
|
||||
label: const Text('Suggestion'),
|
||||
onPressed: () {},
|
||||
),
|
||||
ActionChip(
|
||||
label: const Text('I agree'),
|
||||
onPressed: () {},
|
||||
),
|
||||
ActionChip(
|
||||
label: const Text('LGTM'),
|
||||
onPressed: () {},
|
||||
),
|
||||
const ActionChip(label: Text('Nope')),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
177
experimental/material_3_demo/lib/elevation_screen.dart
Normal file
177
experimental/material_3_demo/lib/elevation_screen.dart
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2021 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ElevationScreen extends StatelessWidget {
|
||||
const ElevationScreen({super.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({super.key, this.shadowColor, this.surfaceTintColor});
|
||||
|
||||
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(
|
||||
{super.key, required this.info, this.shadowColor, this.surfaceTint});
|
||||
|
||||
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),
|
||||
];
|
||||
215
experimental/material_3_demo/lib/main.dart
Normal file
215
experimental/material_3_demo/lib/main.dart
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright 2021 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
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({super.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',
|
||||
];
|
||||
|
||||
enum ScreenSelected {
|
||||
component(0),
|
||||
color(1),
|
||||
typography(2),
|
||||
elevation(3);
|
||||
|
||||
const ScreenSelected(this.value);
|
||||
final int value;
|
||||
}
|
||||
|
||||
class _Material3DemoState extends State<Material3Demo> {
|
||||
bool useMaterial3 = true;
|
||||
bool useLightMode = true;
|
||||
int colorSelected = 0;
|
||||
int screenIndex = ScreenSelected.component.value;
|
||||
|
||||
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 screenSelected) {
|
||||
setState(() {
|
||||
screenIndex = screenSelected;
|
||||
});
|
||||
}
|
||||
|
||||
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(
|
||||
ScreenSelected screenSelected, bool showNavBarExample) {
|
||||
switch (screenSelected) {
|
||||
case ScreenSelected.component:
|
||||
return ComponentScreen(showNavBottomBar: showNavBarExample);
|
||||
case ScreenSelected.color:
|
||||
return const ColorPalettesScreen();
|
||||
case ScreenSelected.typography:
|
||||
return const TypographyScreen();
|
||||
case ScreenSelected.elevation:
|
||||
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(ScreenSelected.values[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(ScreenSelected.values[screenIndex], true),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
63
experimental/material_3_demo/lib/typography_screen.dart
Normal file
63
experimental/material_3_demo/lib/typography_screen.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2021 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TypographyScreen extends StatelessWidget {
|
||||
const TypographyScreen({super.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({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.style,
|
||||
});
|
||||
|
||||
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