flutter/examples/flutter_gallery/lib/demo/material/cards_demo.dart
Ian Hickson 449f4a6673
License update (#45373)
* Update project.pbxproj files to say Flutter rather than Chromium

Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright.

* Update the copyright notice checker to require a standard notice on all files

* Update copyrights on Dart files. (This was a mechanical commit.)

* Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine.

Some were already marked "The Flutter Authors", not clear why. Their
dates have been normalized. Some were missing the blank line after the
license. Some were randomly different in trivial ways for no apparent
reason (e.g. missing the trailing period).

* Clean up the copyrights in non-Dart files. (Manual edits.)

Also, make sure templates don't have copyrights.

* Fix some more ORGANIZATIONNAMEs
2019-11-27 15:04:02 -08:00

410 lines
13 KiB
Dart

// 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/foundation.dart';
import 'package:flutter/material.dart';
import '../../gallery/demo.dart';
const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
enum CardDemoType {
standard,
tappable,
selectable,
}
class TravelDestination {
const TravelDestination({
@required this.assetName,
@required this.assetPackage,
@required this.title,
@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 assetPackage;
final String title;
final String description;
final String city;
final String location;
final CardDemoType type;
}
const List<TravelDestination> destinations = <TravelDestination>[
TravelDestination(
assetName: 'places/india_thanjavur_market.png',
assetPackage: _kGalleryAssetsPackage,
title: 'Top 10 Cities to Visit in Tamil Nadu',
description: 'Number 10',
city: 'Thanjavur',
location: 'Thanjavur, Tamil Nadu',
),
TravelDestination(
assetName: 'places/india_chettinad_silk_maker.png',
assetPackage: _kGalleryAssetsPackage,
title: 'Artisans of Southern India',
description: 'Silk Spinners',
city: 'Chettinad',
location: '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 {
const TravelDestinationItem({ 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 = 338.0;
final TravelDestination destination;
final ShapeBorder shape;
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
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(
top: false,
bottom: false,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
const SectionTitle(title: 'Tappable'),
SizedBox(
height: height,
child: Card(
// This ensures that the Card's children (including the ink splash) are clipped correctly.
clipBehavior: Clip.antiAlias,
shape: shape,
child: InkWell(
onTap: () {
print('Card was tapped');
},
// Generally, material cards use onSurface with 12% opacity for the pressed state.
splashColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
// Generally, material cards do not have a highlight overlay.
highlightColor: Colors.transparent,
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) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
return SafeArea(
top: false,
bottom: false,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
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;
});
},
// Generally, material cards use onSurface with 12% opacity for the pressed state.
splashColor: colorScheme.onSurface.withOpacity(0.12),
// Generally, material cards do not have a highlight overlay.
highlightColor: Colors.transparent,
child: Stack(
children: <Widget>[
Container(
color: _isSelected
// Generally, material cards use primary with 8% opacity for the selected state.
// See: https://material.io/design/interaction/states.html#anatomy
? colorScheme.primary.withOpacity(0.08)
: Colors.transparent,
),
TravelDestinationContent(destination: widget.destination),
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.check_circle,
color: _isSelected ? colorScheme.primary : 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;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Photo and title.
SizedBox(
height: 184.0,
child: Stack(
children: <Widget>[
Positioned.fill(
// In order to have the ink splash appear above the image, you
// must use Ink.image. This allows the image to be painted as part
// 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,
child: Container(),
),
),
Positioned(
bottom: 16.0,
left: 16.0,
right: 16.0,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
destination.title,
style: titleStyle,
),
),
),
],
),
),
// Description and share/explore buttons.
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
child: DefaultTextStyle(
softWrap: false,
overflow: TextOverflow.ellipsis,
style: descriptionStyle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// three line description
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
destination.description,
style: descriptionStyle.copyWith(color: Colors.black54),
),
),
Text(destination.city),
Text(destination.location),
],
),
),
),
if (destination.type == CardDemoType.standard)
// share, explore buttons
ButtonBar(
alignment: MainAxisAlignment.start,
children: <Widget>[
FlatButton(
child: Text('SHARE', semanticsLabel: 'Share ${destination.title}'),
textColor: Colors.amber.shade500,
onPressed: () { print('pressed'); },
),
FlatButton(
child: Text('EXPLORE', semanticsLabel: 'Explore ${destination.title}'),
textColor: Colors.amber.shade500,
onPressed: () { print('pressed'); },
),
],
),
],
);
}
}
class CardsDemo extends StatefulWidget {
static const String routeName = '/material/cards';
@override
_CardsDemoState createState() => _CardsDemoState();
}
class _CardsDemoState extends State<CardsDemo> {
ShapeBorder _shape;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cards'),
actions: <Widget>[
MaterialDemoDocumentationButton(CardsDemo.routeName),
IconButton(
icon: const Icon(
Icons.sentiment_very_satisfied,
semanticLabel: 'update shape',
),
onPressed: () {
setState(() {
_shape = _shape != null ? null : const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0),
bottomLeft: Radius.circular(2.0),
bottomRight: Radius.circular(2.0),
),
);
});
},
),
],
),
body: Scrollbar(
child: ListView(
padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
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(
margin: const EdgeInsets.only(bottom: 8.0),
child: child,
);
}).toList(),
),
),
);
}
}