mirror of https://github.com/flutter/samples.git
Added Material Banner demo and Material Card demo (#238)
* Fix apk release issue * Removed TODO * Removed TODO * Added chip demo * Added card demo * Added Material Card demo and Material Banner Demo * Updated license for demos * Merge with master * Fixing issues * Banner demo issues fixed * Fixed all issues * Updated code samples * Added images to card demo * Fixed issues * Updated code segment * Fixed issues * Removed unused variable * Formatted * Updated code segmentspull/248/head
parent
9630222230
commit
c2f639c519
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,123 @@
|
|||||||
|
// 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/material.dart';
|
||||||
|
import 'package:gallery/l10n/gallery_localizations.dart';
|
||||||
|
|
||||||
|
// BEGIN bannerDemo
|
||||||
|
|
||||||
|
enum BannerDemoAction {
|
||||||
|
reset,
|
||||||
|
showMultipleActions,
|
||||||
|
showLeading,
|
||||||
|
}
|
||||||
|
|
||||||
|
class BannerDemo extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_BannerDemoState createState() => _BannerDemoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BannerDemoState extends State<BannerDemo> {
|
||||||
|
static const _itemCount = 20;
|
||||||
|
var _displayBanner = true;
|
||||||
|
var _showMultipleActions = true;
|
||||||
|
var _showLeading = true;
|
||||||
|
|
||||||
|
void handleDemoAction(BannerDemoAction action) {
|
||||||
|
setState(() {
|
||||||
|
switch (action) {
|
||||||
|
case BannerDemoAction.reset:
|
||||||
|
_displayBanner = true;
|
||||||
|
_showMultipleActions = true;
|
||||||
|
_showLeading = true;
|
||||||
|
break;
|
||||||
|
case BannerDemoAction.showMultipleActions:
|
||||||
|
_showMultipleActions = !_showMultipleActions;
|
||||||
|
break;
|
||||||
|
case BannerDemoAction.showLeading:
|
||||||
|
_showLeading = !_showLeading;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final banner = MaterialBanner(
|
||||||
|
content: Text(GalleryLocalizations.of(context).bannerDemoText),
|
||||||
|
leading: _showLeading
|
||||||
|
? CircleAvatar(
|
||||||
|
child: Icon(Icons.access_alarm),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
actions: [
|
||||||
|
FlatButton(
|
||||||
|
child: Text(GalleryLocalizations.of(context).signIn),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_displayBanner = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (_showMultipleActions)
|
||||||
|
FlatButton(
|
||||||
|
child: Text(GalleryLocalizations.of(context).dismiss),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_displayBanner = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
title: Text(GalleryLocalizations.of(context).demoBannerTitle),
|
||||||
|
actions: [
|
||||||
|
PopupMenuButton<BannerDemoAction>(
|
||||||
|
onSelected: handleDemoAction,
|
||||||
|
itemBuilder: (context) => <PopupMenuEntry<BannerDemoAction>>[
|
||||||
|
PopupMenuItem<BannerDemoAction>(
|
||||||
|
value: BannerDemoAction.reset,
|
||||||
|
child:
|
||||||
|
Text(GalleryLocalizations.of(context).bannerDemoResetText),
|
||||||
|
),
|
||||||
|
const PopupMenuDivider(),
|
||||||
|
CheckedPopupMenuItem<BannerDemoAction>(
|
||||||
|
value: BannerDemoAction.showMultipleActions,
|
||||||
|
checked: _showMultipleActions,
|
||||||
|
child: Text(
|
||||||
|
GalleryLocalizations.of(context).bannerDemoMultipleText),
|
||||||
|
),
|
||||||
|
CheckedPopupMenuItem<BannerDemoAction>(
|
||||||
|
value: BannerDemoAction.showLeading,
|
||||||
|
checked: _showLeading,
|
||||||
|
child: Text(
|
||||||
|
GalleryLocalizations.of(context).bannerDemoLeadingText),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: ListView.builder(
|
||||||
|
itemCount: _displayBanner ? _itemCount + 1 : _itemCount,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == 0 && _displayBanner) {
|
||||||
|
return banner;
|
||||||
|
}
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
GalleryLocalizations.of(context)
|
||||||
|
.starterAppDrawerItem(_displayBanner ? index : index + 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// END
|
@ -0,0 +1,411 @@
|
|||||||
|
// 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
|
Loading…
Reference in new issue