1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-10 23:08:59 +00:00
Files
samples/gallery/lib/demos/material/cards_demo.dart
2020-02-05 14:11:54 -05:00

412 lines
14 KiB
Dart

// Copyright 2020 The Flutter team. 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 'package:gallery/l10n/gallery_localizations.dart';
const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
// BEGIN cardsDemo
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;
}
List<TravelDestination> destinations(BuildContext context) => [
TravelDestination(
assetName: 'places/india_thanjavur_market.png',
assetPackage: _kGalleryAssetsPackage,
title:
GalleryLocalizations.of(context).cardsDemoTravelDestinationTitle1,
description: GalleryLocalizations.of(context)
.cardsDemoTravelDestinationDescription1,
city: GalleryLocalizations.of(context).cardsDemoTravelDestinationCity1,
location: GalleryLocalizations.of(context)
.cardsDemoTravelDestinationLocation1,
),
TravelDestination(
assetName: 'places/india_chettinad_silk_maker.png',
assetPackage: _kGalleryAssetsPackage,
title:
GalleryLocalizations.of(context).cardsDemoTravelDestinationTitle2,
description: GalleryLocalizations.of(context)
.cardsDemoTravelDestinationDescription2,
city: GalleryLocalizations.of(context).cardsDemoTravelDestinationCity2,
location: GalleryLocalizations.of(context)
.cardsDemoTravelDestinationLocation2,
type: CardDemoType.tappable,
),
TravelDestination(
assetName: 'places/india_tanjore_thanjavur_temple.png',
assetPackage: _kGalleryAssetsPackage,
title:
GalleryLocalizations.of(context).cardsDemoTravelDestinationTitle3,
description: GalleryLocalizations.of(context)
.cardsDemoTravelDestinationDescription3,
city: GalleryLocalizations.of(context).cardsDemoTravelDestinationCity1,
location: GalleryLocalizations.of(context)
.cardsDemoTravelDestinationLocation1,
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 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),
child: Column(
children: [
SectionTitle(
title:
GalleryLocalizations.of(context).settingsTextScalingNormal),
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 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),
child: Column(
children: [
SectionTitle(
title: GalleryLocalizations.of(context).cardsDemoTappable),
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 height = 298.0;
var _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),
child: Column(
children: [
SectionTitle(
title: GalleryLocalizations.of(context).cardsDemoSelectable),
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: [
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),
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, 4, 4, 12),
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: [
SizedBox(
height: 184,
child: Stack(
children: [
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,
left: 16,
right: 16,
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, 16, 16, 0),
child: DefaultTextStyle(
softWrap: false,
overflow: TextOverflow.ellipsis,
style: descriptionStyle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// This array contains the three line description on each card
// demo.
Padding(
padding: const EdgeInsets.only(bottom: 8),
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: [
FlatButton(
child: Text(GalleryLocalizations.of(context).demoMenuShare,
semanticsLabel: GalleryLocalizations.of(context)
.cardsDemoShareSemantics(destination.title)),
textColor: Colors.amber.shade500,
onPressed: () {
print('pressed');
},
),
FlatButton(
child: Text(GalleryLocalizations.of(context).cardsDemoExplore,
semanticsLabel: GalleryLocalizations.of(context)
.cardsDemoExploreSemantics(destination.title)),
textColor: Colors.amber.shade500,
onPressed: () {
print('pressed');
},
),
],
),
],
);
}
}
class CardsDemo extends StatefulWidget {
@override
_CardsDemoState createState() => _CardsDemoState();
}
class _CardsDemoState extends State<CardsDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoCardTitle),
),
body: Scrollbar(
child: ListView(
padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
children: [
for (final destination in destinations(context))
Container(
margin: const EdgeInsets.only(bottom: 8),
child: (destination.type == CardDemoType.standard)
? TravelDestinationItem(destination: destination)
: destination.type == CardDemoType.tappable
? TappableTravelDestinationItem(
destination: destination)
: SelectableTravelDestinationItem(
destination: destination),
),
],
),
),
);
}
}
// END