Unverified Commit c348be97 authored by rami-a's avatar rami-a Committed by GitHub

[Material] Update the card demo in the Gallery to demonstrate different uses...

[Material] Update the card demo in the Gallery to demonstrate different uses of the Card widget (#27699)

Additionally, this adds a tappable Card example to the Card documentation.
parent f021ee27
...@@ -9,83 +9,265 @@ import '../../gallery/demo.dart'; ...@@ -9,83 +9,265 @@ import '../../gallery/demo.dart';
const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
enum CardDemoType {
standard,
tappable,
selectable,
}
class TravelDestination { class TravelDestination {
const TravelDestination({ const TravelDestination({
this.assetName, @required this.assetName,
this.assetPackage, @required this.assetPackage,
this.title, @required this.title,
this.description, @required this.description,
}); @required this.city,
@required this.location,
this.type = CardDemoType.standard,
}) : assert(assetName != null),
assert(assetPackage != null),
assert(title != null),
assert(description != null),
assert(city != null),
assert(location != null);
final String assetName; final String assetName;
final String assetPackage; final String assetPackage;
final String title; final String title;
final List<String> description; final String description;
final String city;
bool get isValid => assetName != null && title != null && description?.length == 3; final String location;
final CardDemoType type;
} }
final List<TravelDestination> destinations = <TravelDestination>[ const List<TravelDestination> destinations = <TravelDestination>[
const TravelDestination( TravelDestination(
assetName: 'places/india_thanjavur_market.png', assetName: 'places/india_thanjavur_market.png',
assetPackage: _kGalleryAssetsPackage, assetPackage: _kGalleryAssetsPackage,
title: 'Top 10 Cities to Visit in Tamil Nadu', title: 'Top 10 Cities to Visit in Tamil Nadu',
description: <String>[ description: 'Number 10',
'Number 10', city: 'Thanjavur',
'Thanjavur', location: 'Thanjavur, Tamil Nadu',
'Thanjavur, Tamil Nadu',
],
), ),
const TravelDestination( TravelDestination(
assetName: 'places/india_chettinad_silk_maker.png', assetName: 'places/india_chettinad_silk_maker.png',
assetPackage: _kGalleryAssetsPackage, assetPackage: _kGalleryAssetsPackage,
title: 'Artisans of Southern India', title: 'Artisans of Southern India',
description: <String>[ description: 'Silk Spinners',
'Silk Spinners', city: 'Chettinad',
'Chettinad', location: 'Sivaganga, Tamil Nadu',
'Sivaganga, Tamil Nadu', type: CardDemoType.tappable,
], ),
TravelDestination(
assetName: 'places/india_tanjore_thanjavur_temple.png',
assetPackage: _kGalleryAssetsPackage,
title: 'Brihadisvara Temple',
description: 'Temples',
city: 'Thanjavur',
location: 'Thanjavur, Tamil Nadu',
type: CardDemoType.selectable,
) )
]; ];
class TravelDestinationItem extends StatelessWidget { class TravelDestinationItem extends StatelessWidget {
TravelDestinationItem({ Key key, @required this.destination, this.shape }) const TravelDestinationItem({ Key key, @required this.destination, this.shape })
: assert(destination != null && destination.isValid), : assert(destination != null),
super(key: key); super(key: key);
static const double height = 366.0; // This height will allow for all the Card's content to fit comfortably within the card.
static const double height = 338.0;
final TravelDestination destination; final TravelDestination destination;
final ShapeBorder shape; final ShapeBorder shape;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context); return SafeArea(
final TextStyle titleStyle = theme.textTheme.headline.copyWith(color: Colors.white); top: false,
final TextStyle descriptionStyle = theme.textTheme.subhead; bottom: false,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
const SectionTitle(title: 'Normal'),
SizedBox(
height: height,
child: Card(
// This ensures that the Card's children are clipped correctly.
clipBehavior: Clip.antiAlias,
shape: shape,
child: TravelDestinationContent(destination: destination),
),
),
],
),
),
);
}
}
class TappableTravelDestinationItem extends StatelessWidget {
const TappableTravelDestinationItem({ Key key, @required this.destination, this.shape })
: assert(destination != null),
super(key: key);
// This height will allow for all the Card's content to fit comfortably within the card.
static const double height = 298.0;
final TravelDestination destination;
final ShapeBorder shape;
@override
Widget build(BuildContext context) {
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false, bottom: false,
child: Container( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
const SectionTitle(title: 'Tappable'),
SizedBox(
height: height, height: height,
child: Card( child: Card(
// This ensures that the Card's children (including the ink splash) are clipped correctly.
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
shape: shape, shape: shape,
child: InkWell(
onTap: () {
print('Card was tapped');
},
splashColor: Theme.of(context).colorScheme.primary.withAlpha(30),
child: TravelDestinationContent(destination: destination),
),
),
),
],
),
),
);
}
}
class SelectableTravelDestinationItem extends StatefulWidget {
const SelectableTravelDestinationItem({ Key key, @required this.destination, this.shape })
: assert(destination != null),
super(key: key);
final TravelDestination destination;
final ShapeBorder shape;
@override
_SelectableTravelDestinationItemState createState() => _SelectableTravelDestinationItemState();
}
class _SelectableTravelDestinationItemState extends State<SelectableTravelDestinationItem> {
// This height will allow for all the Card's content to fit comfortably within the card.
static const double height = 298.0;
bool _isSelected = false;
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
bottom: false,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
// photo and title const SectionTitle(title: 'Selectable (long press)'),
SizedBox(
height: height,
child: Card(
// This ensures that the Card's children (including the ink splash) are clipped correctly.
clipBehavior: Clip.antiAlias,
shape: widget.shape,
child: InkWell(
onLongPress: () {
print('Selectable card state changed');
setState(() {
_isSelected = !_isSelected;
});
},
splashColor: Theme.of(context).colorScheme.primary.withAlpha(30),
child: Stack(
children: <Widget>[
Container(
color: _isSelected
? Theme.of(context).colorScheme.primary.withAlpha(41)
: Colors.transparent,
),
TravelDestinationContent(destination: widget.destination),
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Icon(
Icons.check_circle,
color: _isSelected ? Colors.white : Colors.transparent,
),
)
),
],
)
),
),
),
],
),
),
);
}
}
class SectionTitle extends StatelessWidget {
const SectionTitle({
Key key,
this.title
}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(4.0, 4.0, 4.0, 12.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(title, style: Theme.of(context).textTheme.subhead),
),
);
}
}
class TravelDestinationContent extends StatelessWidget {
const TravelDestinationContent({ Key key, @required this.destination })
: assert(destination != null),
super(key: key);
final TravelDestination destination;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextStyle titleStyle = theme.textTheme.headline.copyWith(color: Colors.white);
final TextStyle descriptionStyle = theme.textTheme.subhead;
final List<Widget> children = <Widget>[
// Photo and title.
SizedBox( SizedBox(
height: 184.0, height: 184.0,
child: Stack( child: Stack(
children: <Widget>[ children: <Widget>[
Positioned.fill( Positioned.fill(
child: Image.asset( // In order to have the ink splash appear above the image, you
destination.assetName, // must use Ink.image. This allows the image to be painted as part
package: destination.assetPackage, // of the Material and display ink effects above it. Using a
// standard Image will obscure the ink splash.
child: Ink.image(
image: AssetImage(destination.assetName, package: destination.assetPackage),
fit: BoxFit.cover, fit: BoxFit.cover,
), child: Container(),
)
), ),
Positioned( Positioned(
bottom: 16.0, bottom: 16.0,
...@@ -94,7 +276,8 @@ class TravelDestinationItem extends StatelessWidget { ...@@ -94,7 +276,8 @@ class TravelDestinationItem extends StatelessWidget {
child: FittedBox( child: FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(destination.title, child: Text(
destination.title,
style: titleStyle, style: titleStyle,
), ),
), ),
...@@ -102,9 +285,8 @@ class TravelDestinationItem extends StatelessWidget { ...@@ -102,9 +285,8 @@ class TravelDestinationItem extends StatelessWidget {
], ],
), ),
), ),
// description and share/explore buttons // Description and share/explore buttons.
Expanded( Padding(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0), padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
child: DefaultTextStyle( child: DefaultTextStyle(
softWrap: false, softWrap: false,
...@@ -117,17 +299,20 @@ class TravelDestinationItem extends StatelessWidget { ...@@ -117,17 +299,20 @@ class TravelDestinationItem extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(bottom: 8.0), padding: const EdgeInsets.only(bottom: 8.0),
child: Text( child: Text(
destination.description[0], destination.description,
style: descriptionStyle.copyWith(color: Colors.black54), style: descriptionStyle.copyWith(color: Colors.black54),
), ),
), ),
Text(destination.description[1]), Text(destination.city),
Text(destination.description[2]), Text(destination.location),
], ],
), ),
), ),
), ),
), ];
if (destination.type == CardDemoType.standard) {
children.add(
// share, explore buttons // share, explore buttons
ButtonTheme.bar( ButtonTheme.bar(
child: ButtonBar( child: ButtonBar(
...@@ -136,24 +321,25 @@ class TravelDestinationItem extends StatelessWidget { ...@@ -136,24 +321,25 @@ class TravelDestinationItem extends StatelessWidget {
FlatButton( FlatButton(
child: Text('SHARE', semanticsLabel: 'Share ${destination.title}'), child: Text('SHARE', semanticsLabel: 'Share ${destination.title}'),
textColor: Colors.amber.shade500, textColor: Colors.amber.shade500,
onPressed: () { /* do nothing */ }, onPressed: () { print('pressed'); },
), ),
FlatButton( FlatButton(
child: Text('EXPLORE', semanticsLabel: 'Explore ${destination.title}'), child: Text('EXPLORE', semanticsLabel: 'Explore ${destination.title}'),
textColor: Colors.amber.shade500, textColor: Colors.amber.shade500,
onPressed: () { /* do nothing */ }, onPressed: () { print('pressed'); },
), ),
], ],
), ),
), ),
],
),
),
),
); );
} }
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}
}
class CardsDemo extends StatefulWidget { class CardsDemo extends StatefulWidget {
static const String routeName = '/material/cards'; static const String routeName = '/material/cards';
...@@ -169,7 +355,7 @@ class _CardsDemoState extends State<CardsDemo> { ...@@ -169,7 +355,7 @@ class _CardsDemoState extends State<CardsDemo> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Travel stream'), title: const Text('Cards'),
actions: <Widget>[ actions: <Widget>[
MaterialDemoDocumentationButton(CardsDemo.routeName), MaterialDemoDocumentationButton(CardsDemo.routeName),
IconButton( IconButton(
...@@ -193,15 +379,24 @@ class _CardsDemoState extends State<CardsDemo> { ...@@ -193,15 +379,24 @@ class _CardsDemoState extends State<CardsDemo> {
], ],
), ),
body: ListView( body: ListView(
itemExtent: TravelDestinationItem.height,
padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
children: destinations.map<Widget>((TravelDestination destination) { children: destinations.map<Widget>((TravelDestination destination) {
Widget child;
switch (destination.type) {
case CardDemoType.standard:
child = TravelDestinationItem(destination: destination, shape: _shape);
break;
case CardDemoType.tappable:
child = TappableTravelDestinationItem(destination: destination, shape: _shape);
break;
case CardDemoType.selectable:
child = SelectableTravelDestinationItem(destination: destination, shape: _shape);
break;
}
return Container( return Container(
margin: const EdgeInsets.only(bottom: 8.0), margin: const EdgeInsets.only(bottom: 8.0),
child: TravelDestinationItem( child: child,
destination: destination,
shape: _shape,
),
); );
}).toList() }).toList()
) )
......
...@@ -56,6 +56,27 @@ import 'theme.dart'; ...@@ -56,6 +56,27 @@ import 'theme.dart';
/// ``` /// ```
/// {@end-tool} /// {@end-tool}
/// ///
/// Sometimes the primary action area of a card is the card itself. Cards can be
/// one large touch target that shows a detail screen when tapped.
///
/// {@tool snippet --template=stateless_widget}
///
/// This sample shows creation of a [Card] widget that can be tapped. When
/// tapped this [Card]'s [InkWell] displays an "ink splash" that fills the
/// entire card.
///
/// ```dart
/// Card(
/// child: InkWell(
/// splashColor: Colors.blue.withAlpha(30),
/// onTap: () { /* ... */ },
/// child: Text('A card that can be tapped'),
/// ),
/// )
/// ```
///
/// {@end-tool}
///
/// See also: /// See also:
/// ///
/// * [ListTile], to display icons and text in a card. /// * [ListTile], to display icons and text in a card.
......
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