From cff31a3f6d235d1e5cb2033c9c42f8f1b8ab74c4 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 20 Jun 2016 14:21:28 -0700 Subject: [PATCH] IconThemeData.size (#4633) --- packages/flutter/lib/src/material/icon.dart | 51 +++++++----- .../flutter/lib/src/material/icon_theme.dart | 7 +- .../lib/src/material/icon_theme_data.dart | 35 ++++++--- packages/flutter/test/material/icon_test.dart | 78 +++++++++++++++++++ 4 files changed, 141 insertions(+), 30 deletions(-) create mode 100644 packages/flutter/test/material/icon_test.dart diff --git a/packages/flutter/lib/src/material/icon.dart b/packages/flutter/lib/src/material/icon.dart index bb63b26499..da8f3c6010 100644 --- a/packages/flutter/lib/src/material/icon.dart +++ b/packages/flutter/lib/src/material/icon.dart @@ -26,29 +26,34 @@ import 'theme.dart'; /// /// See also: /// -/// * [IconButton], for interactive icons -/// * [Icons], for the list of available icons for use with this class +/// * [IconButton], for interactive icons. +/// * [Icons], for the list of available icons for use with this class. +/// * [IconTheme], which provides ambient configuration for icons. class Icon extends StatelessWidget { /// Creates an icon. /// - /// The [size] argument most not be null. - Icon({ + /// The [size] and [color] default to the value given by the current [IconTheme]. + const Icon({ Key key, this.icon, - this.size: 24.0, + this.size, this.color - }) : super(key: key) { - assert(size != null); - } + }) : super(key: key); + + /// The icon to display. The available icons are described in [Icons]. + /// + /// If null, no icon is shown. + final IconData icon; /// The size of the icon in logical pixels. /// /// Icons occupy a square with width and height equal to size. + /// + /// Defaults to the current [IconTheme] size, if any. If there is no + /// [IconTheme], or it does not specify an explicit size, then it defaults to + /// 24.0. final double size; - /// The icon to display. The available icons are described in [Icons]. - final IconData icon; - /// The color to use when drawing the icon. /// /// Defaults to the current [IconTheme] color, if any. If there is @@ -56,6 +61,9 @@ class Icon extends StatelessWidget { /// and black if the theme is light. See [Theme] to set the current /// theme and [ThemeData.brightness] for setting the current theme's /// brightness. + /// + /// The given color will be adjusted by the opacity of the current + /// [IconTheme], if any. final Color color; Color _getDefaultColorForBrightness(Brightness brightness) { @@ -75,8 +83,10 @@ class Icon extends StatelessWidget { @override Widget build(BuildContext context) { + final double iconSize = size ?? IconTheme.of(context)?.size ?? 24.0; + if (icon == null) - return new SizedBox(width: size, height: size); + return new SizedBox(width: iconSize, height: iconSize); final double iconOpacity = IconTheme.of(context)?.opacity ?? 1.0; Color iconColor = color ?? _getDefaultColor(context); @@ -85,15 +95,15 @@ class Icon extends StatelessWidget { return new ExcludeSemantics( child: new SizedBox( - width: size, - height: size, + width: iconSize, + height: iconSize, child: new Center( child: new Text( new String.fromCharCode(icon.codePoint), style: new TextStyle( inherit: false, color: iconColor, - fontSize: size, + fontSize: iconSize, fontFamily: 'MaterialIcons' ) ) @@ -105,9 +115,14 @@ class Icon extends StatelessWidget { @override void debugFillDescription(List description) { super.debugFillDescription(description); - description.add('$icon'); - description.add('size: $size'); - if (this.color != null) + if (icon != null) { + description.add('$icon'); + } else { + description.add(''); + } + if (size != null) + description.add('size: $size'); + if (color != null) description.add('color: $color'); } } diff --git a/packages/flutter/lib/src/material/icon_theme.dart b/packages/flutter/lib/src/material/icon_theme.dart index a4871a879c..3aba060dee 100644 --- a/packages/flutter/lib/src/material/icon_theme.dart +++ b/packages/flutter/lib/src/material/icon_theme.dart @@ -7,9 +7,10 @@ import 'package:meta/meta.dart'; import 'icon_theme_data.dart'; -/// Controls the color and opacity of icons in a widget subtree. +/// Controls the default color, opacity, and size of icons in a widget subtree. class IconTheme extends InheritedWidget { - /// Creates an icon theme that controls the color and opacity of descendant widgets. + /// Creates an icon theme that controls the color, opacity, and size of + /// descendant widgets. /// /// Both [data] and [child] arguments must not be null. IconTheme({ @@ -21,7 +22,7 @@ class IconTheme extends InheritedWidget { assert(child != null); } - /// The color and opacity to use for icons in this subtree. + /// The color, opacity, and size to use for icons in this subtree. final IconThemeData data; /// The data from the closest instance of this class that encloses the given context. diff --git a/packages/flutter/lib/src/material/icon_theme_data.dart b/packages/flutter/lib/src/material/icon_theme_data.dart index 3a5b34414f..7abbe1acc1 100644 --- a/packages/flutter/lib/src/material/icon_theme_data.dart +++ b/packages/flutter/lib/src/material/icon_theme_data.dart @@ -5,16 +5,16 @@ import 'dart:ui' as ui show lerpDouble; import 'dart:ui' show Color, hashValues; -/// Defines the color and opacity of icons. +/// Defines the color, opacity, and size of icons. /// -/// Used by [IconTheme] to control the color and opacity of icons in a widget -/// subtree. +/// Used by [IconTheme] to control the color, opacity, and size of icons in a +/// widget subtree. class IconThemeData { /// Creates an icon theme data. /// /// The opacity applies to both explicit and default icon colors. The value /// is clamped between 0.0 and 1.0. - const IconThemeData({ this.color, double opacity: 1.0 }) : _opacity = opacity; + const IconThemeData({ this.color, double opacity: 1.0, this.size }) : _opacity = opacity; /// The default color for icons. final Color color; @@ -23,25 +23,42 @@ class IconThemeData { double get opacity => (_opacity ?? 1.0).clamp(0.0, 1.0); final double _opacity; + /// The default size for icons. + final double size; + /// Linearly interpolate between two icon theme data objects. static IconThemeData lerp(IconThemeData begin, IconThemeData end, double t) { return new IconThemeData( color: Color.lerp(begin.color, end.color, t), - opacity: ui.lerpDouble(begin.opacity, end.opacity, t) + opacity: ui.lerpDouble(begin.opacity, end.opacity, t), + size: ui.lerpDouble(begin.size, end.size, t) ); } @override bool operator ==(dynamic other) { - if (other is! IconThemeData) + if (other.runtimeType != runtimeType) return false; final IconThemeData typedOther = other; - return color == typedOther.color && opacity == typedOther.opacity; + return color == typedOther.color + && opacity == typedOther.opacity + && size == typedOther.size; } @override - int get hashCode => hashValues(color, opacity); + int get hashCode => hashValues(color, opacity, size); @override - String toString() => '$color'; + String toString() { + List result = []; + if (color != null) + result.add('color: $color'); + if (opacity != 1.0) + result.add('opacity: $opacity'); + if (size != null) + result.add('size: $size'); + if (result.length == 0) + return ''; + return result.join(', '); + } } diff --git a/packages/flutter/test/material/icon_test.dart b/packages/flutter/test/material/icon_test.dart new file mode 100644 index 0000000000..73eee1ba62 --- /dev/null +++ b/packages/flutter/test/material/icon_test.dart @@ -0,0 +1,78 @@ +// Copyright 2015 The Chromium 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'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Icon sizing - no theme, default size', (WidgetTester tester) async { + await tester.pumpWidget( + new Center( + child: new Icon() + ) + ); + + RenderBox renderObject = tester.renderObject(find.byType(Icon)); + expect(renderObject.size, equals(const Size.square(24.0))); + }); + + testWidgets('Icon sizing - no theme, explicit size', (WidgetTester tester) async { + await tester.pumpWidget( + new Center( + child: new Icon( + size: 96.0 + ) + ) + ); + + RenderBox renderObject = tester.renderObject(find.byType(Icon)); + expect(renderObject.size, equals(const Size.square(96.0))); + }); + + testWidgets('Icon sizing - sized theme', (WidgetTester tester) async { + await tester.pumpWidget( + new Center( + child: new IconTheme( + data: new IconThemeData(size: 36.0), + child: new Icon() + ) + ) + ); + + RenderBox renderObject = tester.renderObject(find.byType(Icon)); + expect(renderObject.size, equals(const Size.square(36.0))); + }); + + testWidgets('Icon sizing - sized theme, explicit size', (WidgetTester tester) async { + await tester.pumpWidget( + new Center( + child: new IconTheme( + data: new IconThemeData(size: 36.0), + child: new Icon( + size: 48.0 + ) + ) + ) + ); + + RenderBox renderObject = tester.renderObject(find.byType(Icon)); + expect(renderObject.size, equals(const Size.square(48.0))); + }); + + testWidgets('Icon sizing - sizeless theme, default size', (WidgetTester tester) async { + await tester.pumpWidget( + new Center( + child: new IconTheme( + data: new IconThemeData(), + child: new Icon() + ) + ) + ); + + RenderBox renderObject = tester.renderObject(find.byType(Icon)); + expect(renderObject.size, equals(const Size.square(24.0))); + }); +}