| // Copyright 2014 The Flutter Authors. 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'; |
| |
| /// Flutter code sample for [ColorScheme.fromImageProvider] with content-based dynamic color. |
| |
| const Widget divider = SizedBox(height: 10); |
| const double narrowScreenWidthThreshold = 400; |
| |
| void main() => runApp(DynamicColorExample()); |
| |
| class DynamicColorExample extends StatefulWidget { |
| DynamicColorExample({super.key}); |
| |
| final List<ImageProvider> images = <NetworkImage>[ |
| const NetworkImage( |
| 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png'), |
| const NetworkImage( |
| 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_2.png'), |
| const NetworkImage( |
| 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_3.png'), |
| const NetworkImage( |
| 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_4.png'), |
| const NetworkImage( |
| 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_5.png'), |
| const NetworkImage( |
| 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_6.png'), |
| ]; |
| |
| @override |
| State<DynamicColorExample> createState() => _DynamicColorExampleState(); |
| } |
| |
| class _DynamicColorExampleState extends State<DynamicColorExample> { |
| late ColorScheme currentColorScheme; |
| String currentHyperlinkImage = ''; |
| late int selectedImage; |
| late bool isLight; |
| late bool isLoading; |
| |
| @override |
| void initState() { |
| super.initState(); |
| selectedImage = 0; |
| isLight = true; |
| isLoading = true; |
| currentColorScheme = const ColorScheme.light(); |
| WidgetsBinding.instance.addPostFrameCallback((_) { |
| _updateImage(widget.images[selectedImage]); |
| isLoading = false; |
| }); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final ColorScheme colorScheme = currentColorScheme; |
| final Color selectedColor = currentColorScheme.primary; |
| |
| final ThemeData lightTheme = ThemeData( |
| colorSchemeSeed: selectedColor, |
| brightness: Brightness.light, |
| useMaterial3: false, |
| ); |
| final ThemeData darkTheme = ThemeData( |
| colorSchemeSeed: selectedColor, |
| brightness: Brightness.dark, |
| useMaterial3: false, |
| ); |
| |
| Widget schemeLabel(String brightness, ColorScheme colorScheme) { |
| return Padding( |
| padding: const EdgeInsets.symmetric(vertical: 15), |
| child: Text( |
| brightness, |
| style: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.onSecondaryContainer), |
| ), |
| ); |
| } |
| |
| Widget schemeView(ThemeData theme) { |
| return Padding( |
| padding: const EdgeInsets.symmetric(horizontal: 15), |
| child: ColorSchemeView(colorScheme: theme.colorScheme), |
| ); |
| } |
| |
| return MaterialApp( |
| theme: ThemeData(useMaterial3: true, colorScheme: colorScheme), |
| debugShowCheckedModeBanner: false, |
| home: Builder( |
| builder: (BuildContext context) => Scaffold( |
| appBar: AppBar( |
| title: const Text('Content Based Dynamic Color'), |
| backgroundColor: colorScheme.primary, |
| foregroundColor: colorScheme.onPrimary, |
| actions: <Widget>[ |
| const Icon(Icons.light_mode), |
| Switch( |
| activeColor: colorScheme.primary, |
| activeTrackColor: colorScheme.surface, |
| inactiveTrackColor: colorScheme.onSecondary, |
| value: isLight, |
| onChanged: (bool value) { |
| setState(() { |
| isLight = value; |
| _updateImage(widget.images[selectedImage]); |
| }); |
| }) |
| ], |
| ), |
| body: Center( |
| child: isLoading |
| ? const CircularProgressIndicator() |
| : ColoredBox( |
| color: colorScheme.secondaryContainer, |
| child: Column( |
| children: <Widget>[ |
| divider, |
| _imagesRow( |
| context, |
| widget.images, |
| colorScheme, |
| ), |
| divider, |
| Expanded( |
| child: ColoredBox( |
| color: colorScheme.surface, |
| child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { |
| if (constraints.maxWidth < narrowScreenWidthThreshold) { |
| return SingleChildScrollView( |
| child: Column( |
| children: <Widget>[ |
| divider, |
| schemeLabel('Light ColorScheme', colorScheme), |
| schemeView(lightTheme), |
| divider, |
| divider, |
| schemeLabel('Dark ColorScheme', colorScheme), |
| schemeView(darkTheme), |
| ], |
| ), |
| ); |
| } else { |
| return SingleChildScrollView( |
| child: Padding( |
| padding: const EdgeInsets.only(top: 5), |
| child: Column( |
| children: <Widget>[ |
| Row( |
| children: <Widget>[ |
| Expanded( |
| child: Column( |
| children: <Widget>[ |
| schemeLabel('Light ColorScheme', colorScheme), |
| schemeView(lightTheme), |
| ], |
| ), |
| ), |
| Expanded( |
| child: Column( |
| children: <Widget>[ |
| schemeLabel('Dark ColorScheme', colorScheme), |
| schemeView(darkTheme), |
| ], |
| ), |
| ), |
| ], |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| }), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| Future<void> _updateImage(ImageProvider provider) async { |
| final ColorScheme newColorScheme = await ColorScheme.fromImageProvider( |
| provider: provider, brightness: isLight ? Brightness.light : Brightness.dark); |
| setState(() { |
| selectedImage = widget.images.indexOf(provider); |
| currentColorScheme = newColorScheme; |
| }); |
| } |
| |
| // For small screens, have two rows of image selection. For wide screens, |
| // fit them onto one row. |
| Widget _imagesRow(BuildContext context, List<ImageProvider> images, ColorScheme colorScheme) { |
| final double windowHeight = MediaQuery.of(context).size.height; |
| final double windowWidth = MediaQuery.of(context).size.width; |
| return Padding( |
| padding: const EdgeInsets.all(8.0), |
| child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { |
| if (constraints.maxWidth > 800) { |
| return _adaptiveLayoutImagesRow(images, colorScheme, windowHeight); |
| } else { |
| return Column(children: <Widget>[ |
| _adaptiveLayoutImagesRow(images.sublist(0, 3), colorScheme, windowWidth), |
| _adaptiveLayoutImagesRow(images.sublist(3), colorScheme, windowWidth), |
| ]); |
| } |
| }), |
| ); |
| } |
| |
| Widget _adaptiveLayoutImagesRow(List<ImageProvider> images, ColorScheme colorScheme, double windowWidth) { |
| return Row( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: images |
| .map( |
| (ImageProvider image) => Flexible( |
| flex: (images.length / 3).floor(), |
| child: GestureDetector( |
| onTap: () => _updateImage(image), |
| child: Card( |
| color: widget.images.indexOf(image) == selectedImage |
| ? colorScheme.primaryContainer |
| : colorScheme.surface, |
| child: Padding( |
| padding: const EdgeInsets.all(5.0), |
| child: ConstrainedBox( |
| constraints: BoxConstraints(maxWidth: windowWidth * .25), |
| child: ClipRRect( |
| borderRadius: BorderRadius.circular(8.0), |
| child: Image(image: image), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ) |
| .toList(), |
| ); |
| } |
| } |
| |
| class ColorSchemeView extends StatelessWidget { |
| const ColorSchemeView({super.key, required this.colorScheme}); |
| |
| final ColorScheme colorScheme; |
| |
| @override |
| Widget build(BuildContext context) { |
| return Column( |
| children: <Widget>[ |
| ColorGroup(children: <ColorChip>[ |
| 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>[ |
| 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>[ |
| 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>[ |
| 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>[ |
| ColorChip(label: 'surface', color: colorScheme.surface, onColor: colorScheme.onSurface), |
| ColorChip(label: 'onSurface', color: colorScheme.onSurface, onColor: colorScheme.surface), |
| ColorChip( |
| label: 'onSurfaceVariant', color: colorScheme.onSurfaceVariant, onColor: colorScheme.surfaceContainerHighest), |
| ], |
| ), |
| divider, |
| ColorGroup( |
| children: <ColorChip>[ |
| 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 RepaintBoundary( |
| child: 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 brightness = ThemeData.estimateBrightnessForColor(color); |
| return switch (brightness) { |
| Brightness.dark => Colors.white, |
| Brightness.light => Colors.black, |
| }; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final Color labelColor = onColor ?? contrastColor(color); |
| return ColoredBox( |
| color: color, |
| child: Padding( |
| padding: const EdgeInsets.all(16), |
| child: Row( |
| children: <Expanded>[ |
| Expanded(child: Text(label, style: TextStyle(color: labelColor))), |
| ], |
| ), |
| ), |
| ); |
| } |
| } |