Commit c362d8da authored by Will Larche's avatar Will Larche Committed by rami-a
parent f8e5b9c4
24e9ab38cea4fd85f11ee5a3a491576037294bb9
6fc7ec65d51116c3f83acb5251e57e779af2ebbb
......@@ -29,6 +29,7 @@ export 'src/material/button.dart';
export 'src/material/button_bar.dart';
export 'src/material/button_theme.dart';
export 'src/material/card.dart';
export 'src/material/card_theme.dart';
export 'src/material/checkbox.dart';
export 'src/material/checkbox_list_tile.dart';
export 'src/material/chip.dart';
......
......@@ -4,6 +4,7 @@
import 'package:flutter/widgets.dart';
import 'card_theme.dart';
import 'material.dart';
import 'theme.dart';
......@@ -65,25 +66,25 @@ import 'theme.dart';
class Card extends StatelessWidget {
/// Creates a material design card.
///
/// The [clipBehavior] and [elevation] arguments must not be null.
/// Additionally, the [elevation] must be non-negative.
/// The [elevation] must be null or non-negative.
const Card({
Key key,
this.color,
this.elevation = 1.0,
this.elevation,
this.shape,
this.margin = const EdgeInsets.all(4.0),
this.clipBehavior = Clip.none,
this.margin,
this.clipBehavior,
this.child,
this.semanticContainer = true,
}) : assert(elevation != null && elevation >= 0.0),
}) : assert(elevation == null || elevation >= 0.0),
super(key: key);
/// The card's background color.
///
/// Defines the card's [Material.color].
///
/// The default color is defined by the ambient [Theme]: [ThemeData.cardColor].
/// If this property is null then [ThemeData.cardTheme.color] is used,
/// if that's null then [ThemeData.cardColor] is used.
final Color color;
/// The z-coordinate at which to place this card. This controls the size of
......@@ -91,25 +92,30 @@ class Card extends StatelessWidget {
///
/// Defines the card's [Material.elevation].
///
/// The default elevation is 1.0. The value is always non-negative.
/// If this property is null then [ThemeData.cardTheme.elevation] is used,
/// if that's null, the default value is 1.0.
final double elevation;
/// The shape of the card's [Material].
///
/// Defines the card's [Material.shape].
///
/// The default shape is a [RoundedRectangleBorder] with a circular corner
/// radius of 4.0.
/// If this property is null then [ThemeData.cardTheme.shape] is used.
/// If that's null then the shape will be a [RoundedRectangleBorder] with a
/// circular corner radius of 4.0.
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
/// If this property is null then [ThemeData.cardTheme.clipBehavior] is used.
/// If that's null then the behavior will be [Clip.none].
final Clip clipBehavior;
/// The empty space that surrounds the card.
///
/// Defines the card's outer [Container.margin].
///
/// The default margin is 4.0 logical pixels on all sides:
/// If this property is null then [ThemeData.cardTheme.margin] is used,
/// if that's null, the default margin is 4.0 logical pixels on all sides:
/// `EdgeInsets.all(4.0)`.
final EdgeInsetsGeometry margin;
......@@ -131,20 +137,25 @@ class Card extends StatelessWidget {
/// {@macro flutter.widgets.child}
final Widget child;
static const double _defaultElevation = 1.0;
static const Clip _defaultClipBehavior = Clip.none;
@override
Widget build(BuildContext context) {
final CardTheme cardTheme = CardTheme.of(context);
return Semantics(
container: semanticContainer,
child: Container(
margin: margin ?? const EdgeInsets.all(4.0),
margin: margin ?? cardTheme.margin ?? const EdgeInsets.all(4.0),
child: Material(
type: MaterialType.card,
color: color ?? Theme.of(context).cardColor,
elevation: elevation,
shape: shape ?? const RoundedRectangleBorder(
color: color ?? cardTheme.color ?? Theme.of(context).cardColor,
elevation: elevation ?? cardTheme.elevation ?? _defaultElevation,
shape: shape ?? cardTheme.shape ?? const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
clipBehavior: clipBehavior,
clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? _defaultClipBehavior,
child: Semantics(
explicitChildNodes: !semanticContainer,
child: child,
......
// Copyright 2019 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 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
/// Defines default property values for descendant [Card] widgets.
///
/// Descendant widgets obtain the current [CardTheme] object using
/// `CardTheme.of(context)`. Instances of [CardTheme] can be
/// customized with [CardTheme.copyWith].
///
/// Typically a [CardTheme] is specified as part of the overall [Theme]
/// with [ThemeData.cardTheme].
///
/// All [CardTheme] properties are `null` by default. When null, the [Card]
/// will use the values from [ThemeData] if they exist, otherwise it will
/// provide its own defaults.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
class CardTheme extends Diagnosticable {
/// Creates a theme that can be used for [ThemeData.cardTheme].
///
/// The [elevation] must be null or non-negative.
const CardTheme({
this.clipBehavior,
this.color,
this.elevation,
this.margin,
this.shape,
}) : assert(elevation == null || elevation >= 0.0);
/// Default value for [Card.clipBehavior].
///
/// If null, [Card] uses [Clip.none].
final Clip clipBehavior;
/// Default value for [Card.color].
///
/// If null, [Card] uses [ThemeData.cardColor].
final Color color;
/// Default value for [Card.elevation].
///
/// If null, [Card] uses a default of 1.0.
final double elevation;
/// Default value for [Card.margin].
///
/// If null, [Card] uses a default margin of 4.0 logical pixels on all sides:
/// `EdgeInsets.all(4.0)`.
final EdgeInsetsGeometry margin;
/// Default value for [Card.shape].
///
/// If null, [Card] then uses a [RoundedRectangleBorder] with a circular
/// corner radius of 4.0.
final ShapeBorder shape;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
CardTheme copyWith({
Clip clipBehavior,
Color color,
double elevation,
EdgeInsetsGeometry margin,
ShapeBorder shape,
}) {
return CardTheme(
clipBehavior: clipBehavior ?? this.clipBehavior,
color: color ?? this.color,
elevation: elevation ?? this.elevation,
margin: margin ?? this.margin,
shape: shape ?? this.shape,
);
}
/// The [ThemeData.cardTheme] property of the ambient [Theme].
static CardTheme of(BuildContext context) {
return Theme.of(context).cardTheme;
}
/// Linearly interpolate between two Card themes.
///
/// The argument `t` must not be null.
///
/// {@macro dart.ui.shadow.lerp}
static CardTheme lerp(CardTheme a, CardTheme b, double t) {
assert(t != null);
return CardTheme(
clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
color: Color.lerp(a?.color, b?.color, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t),
margin: EdgeInsetsGeometry.lerp(a?.margin, b?.margin, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
);
}
@override
int get hashCode {
return hashValues(
clipBehavior,
color,
elevation,
margin,
shape,
);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final CardTheme typedOther = other;
return typedOther.clipBehavior == clipBehavior
&& typedOther.color == color
&& typedOther.elevation == elevation
&& typedOther.margin == margin
&& typedOther.shape == shape;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
}
}
......@@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart';
import 'app_bar_theme.dart';
import 'bottom_app_bar_theme.dart';
import 'button_theme.dart';
import 'card_theme.dart';
import 'chip_theme.dart';
import 'color_scheme.dart';
import 'colors.dart';
......@@ -149,6 +150,7 @@ class ThemeData extends Diagnosticable {
IconThemeData accentIconTheme,
SliderThemeData sliderTheme,
TabBarTheme tabBarTheme,
CardTheme cardTheme,
ChipThemeData chipTheme,
TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize,
......@@ -248,6 +250,7 @@ class ThemeData extends Diagnosticable {
tabBarTheme ??= const TabBarTheme();
appBarTheme ??= const AppBarTheme();
bottomAppBarTheme ??= const BottomAppBarTheme();
cardTheme ??= const CardTheme();
chipTheme ??= ChipThemeData.fromDefaults(
secondaryColor: primaryColor,
brightness: brightness,
......@@ -296,6 +299,7 @@ class ThemeData extends Diagnosticable {
accentIconTheme: accentIconTheme,
sliderTheme: sliderTheme,
tabBarTheme: tabBarTheme,
cardTheme: cardTheme,
chipTheme: chipTheme,
platform: platform,
materialTapTargetSize: materialTapTargetSize,
......@@ -359,6 +363,7 @@ class ThemeData extends Diagnosticable {
@required this.accentIconTheme,
@required this.sliderTheme,
@required this.tabBarTheme,
@required this.cardTheme,
@required this.chipTheme,
@required this.platform,
@required this.materialTapTargetSize,
......@@ -407,6 +412,7 @@ class ThemeData extends Diagnosticable {
assert(accentIconTheme != null),
assert(sliderTheme != null),
assert(tabBarTheme != null),
assert(cardTheme != null),
assert(chipTheme != null),
assert(platform != null),
assert(materialTapTargetSize != null),
......@@ -604,6 +610,11 @@ class ThemeData extends Diagnosticable {
/// A theme for customizing the size, shape, and color of the tab bar indicator.
final TabBarTheme tabBarTheme;
/// The colors and styles used to render [Card].
///
/// This is the value returned from [CardTheme.of].
final CardTheme cardTheme;
/// The colors and styles used to render [Chip], [
///
/// This is the value returned from [ChipTheme.of].
......@@ -708,6 +719,7 @@ class ThemeData extends Diagnosticable {
IconThemeData accentIconTheme,
SliderThemeData sliderTheme,
TabBarTheme tabBarTheme,
CardTheme cardTheme,
ChipThemeData chipTheme,
TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize,
......@@ -760,6 +772,7 @@ class ThemeData extends Diagnosticable {
accentIconTheme: accentIconTheme ?? this.accentIconTheme,
sliderTheme: sliderTheme ?? this.sliderTheme,
tabBarTheme: tabBarTheme ?? this.tabBarTheme,
cardTheme: cardTheme ?? this.cardTheme,
chipTheme: chipTheme ?? this.chipTheme,
platform: platform ?? this.platform,
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
......@@ -890,6 +903,7 @@ class ThemeData extends Diagnosticable {
accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t),
sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t),
tabBarTheme: TabBarTheme.lerp(a.tabBarTheme, b.tabBarTheme, t),
cardTheme: CardTheme.lerp(a.cardTheme, b.cardTheme, t),
chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t),
platform: t < 0.5 ? a.platform : b.platform,
materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize,
......@@ -950,6 +964,7 @@ class ThemeData extends Diagnosticable {
(otherData.accentIconTheme == accentIconTheme) &&
(otherData.sliderTheme == sliderTheme) &&
(otherData.tabBarTheme == tabBarTheme) &&
(otherData.cardTheme == cardTheme) &&
(otherData.chipTheme == chipTheme) &&
(otherData.platform == platform) &&
(otherData.materialTapTargetSize == materialTapTargetSize) &&
......@@ -1010,6 +1025,7 @@ class ThemeData extends Diagnosticable {
sliderTheme,
hashValues(
tabBarTheme,
cardTheme,
chipTheme,
platform,
materialTapTargetSize,
......@@ -1066,6 +1082,7 @@ class ThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme));
properties.add(DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme));
properties.add(DiagnosticsProperty<TabBarTheme>('tabBarTheme', tabBarTheme));
properties.add(DiagnosticsProperty<CardTheme>('cardTheme', cardTheme));
properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize));
properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme));
......
// Copyright 2019 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 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('CardTheme copyWith, ==, hashCode basics', () {
expect(const CardTheme(), const CardTheme().copyWith());
expect(const CardTheme().hashCode, const CardTheme().copyWith().hashCode);
});
testWidgets('Passing no CardTheme returns defaults', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Scaffold(
body: Card()
),
));
final Container container = _getCardContainer(tester);
final Material material = _getCardMaterial(tester);
expect(material.clipBehavior, Clip.none);
expect(material.color, Colors.white);
expect(material.elevation, 1.0);
expect(container.margin, const EdgeInsets.all(4.0));
expect(material.shape, const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
));
});
testWidgets('Card uses values from CardTheme', (WidgetTester tester) async {
final CardTheme cardTheme = _cardTheme();
await tester.pumpWidget(MaterialApp(
theme: ThemeData(cardTheme: cardTheme),
home: const Scaffold(
body: Card()
),
));
final Container container = _getCardContainer(tester);
final Material material = _getCardMaterial(tester);
expect(material.clipBehavior, cardTheme.clipBehavior);
expect(material.color, cardTheme.color);
expect(material.elevation, cardTheme.elevation);
expect(container.margin, cardTheme.margin);
expect(material.shape, cardTheme.shape);
});
testWidgets('Card widget properties take priority over theme', (WidgetTester tester) async {
const Clip clip = Clip.hardEdge;
const Color color = Colors.orange;
const double elevation = 7.0;
const EdgeInsets margin = EdgeInsets.all(3.0);
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(9.0)),
);
await tester.pumpWidget(MaterialApp(
theme: _themeData().copyWith(cardTheme: _cardTheme()),
home: const Scaffold(
body: Card(
clipBehavior: clip,
color: color,
elevation: elevation,
margin: margin,
shape: shape,
)
),
));
final Container container = _getCardContainer(tester);
final Material material = _getCardMaterial(tester);
expect(material.clipBehavior, clip);
expect(material.color, color);
expect(material.elevation, elevation);
expect(container.margin, margin);
expect(material.shape, shape);
});
testWidgets('CardTheme properties take priority over ThemeData properties', (WidgetTester tester) async {
final CardTheme cardTheme = _cardTheme();
final ThemeData themeData = _themeData().copyWith(cardTheme: cardTheme);
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: const Scaffold(
body: Card()
),
));
final Material material = _getCardMaterial(tester);
expect(material.color, cardTheme.color);
});
testWidgets('ThemeData properties are used when no CardTheme is set', (WidgetTester tester) async {
final ThemeData themeData = _themeData();
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: const Scaffold(
body: Card()
),
));
final Material material = _getCardMaterial(tester);
expect(material.color, themeData.cardColor);
});
testWidgets('CardTheme customizes shape', (WidgetTester tester) async {
const CardTheme cardTheme = CardTheme(
color: Colors.white,
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))),
elevation: 1.0,
);
final Key painterKey = UniqueKey();
await tester.pumpWidget(MaterialApp(
theme: ThemeData(cardTheme: cardTheme),
home: Scaffold(
body: RepaintBoundary(
key: painterKey,
child: Center(
child: Card(
child: SizedBox.fromSize(size: const Size(200, 300),),
)
)
)
),
));
await expectLater(
find.byKey(painterKey),
matchesGoldenFile('card_theme.custom_shape.png'),
skip: !Platform.isLinux,
);
});
}
CardTheme _cardTheme() {
return const CardTheme(
clipBehavior: Clip.antiAlias,
color: Colors.green,
elevation: 6.0,
margin: EdgeInsets.all(7.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
)
);
}
ThemeData _themeData() {
return ThemeData(
cardColor: Colors.pink,
);
}
Material _getCardMaterial(WidgetTester tester) {
return tester.widget<Material>(
find.descendant(
of: find.byType(Card),
matching: find.byType(Material),
),
);
}
Container _getCardContainer(WidgetTester tester) {
return tester.widget<Container>(
find.descendant(
of: find.byType(Card),
matching: find.byType(Container),
),
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment