1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-10 23:08:59 +00:00

[Gallery] Fix directory structure (#312)

This commit is contained in:
Pierre-Louis
2020-02-05 20:11:54 +01:00
committed by GitHub
parent 082592e9a9
commit cee267cf88
762 changed files with 12 additions and 12 deletions

View File

@@ -0,0 +1,27 @@
// Copyright 2019 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/cupertino.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoActivityIndicatorDemo
class CupertinoProgressIndicatorDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticallyImplyLeading: false,
middle: Text(
GalleryLocalizations.of(context).demoCupertinoActivityIndicatorTitle,
),
),
child: const Center(
child: CupertinoActivityIndicator(),
),
);
}
}
// END

View File

@@ -0,0 +1,342 @@
// Copyright 2019 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/cupertino.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoAlertDemo
enum AlertDemoType {
alert,
alertTitle,
alertButtons,
alertButtonsOnly,
actionSheet,
}
class CupertinoAlertDemo extends StatefulWidget {
const CupertinoAlertDemo({
Key key,
@required this.type,
}) : super(key: key);
final AlertDemoType type;
@override
_CupertinoAlertDemoState createState() => _CupertinoAlertDemoState();
}
class _CupertinoAlertDemoState extends State<CupertinoAlertDemo> {
String lastSelectedValue;
String _title(BuildContext context) {
switch (widget.type) {
case AlertDemoType.alert:
return GalleryLocalizations.of(context).demoCupertinoAlertTitle;
case AlertDemoType.alertTitle:
return GalleryLocalizations.of(context)
.demoCupertinoAlertWithTitleTitle;
case AlertDemoType.alertButtons:
return GalleryLocalizations.of(context).demoCupertinoAlertButtonsTitle;
case AlertDemoType.alertButtonsOnly:
return GalleryLocalizations.of(context)
.demoCupertinoAlertButtonsOnlyTitle;
case AlertDemoType.actionSheet:
return GalleryLocalizations.of(context).demoCupertinoActionSheetTitle;
}
return '';
}
void _showDemoDialog({BuildContext context, Widget child}) {
showCupertinoDialog<String>(
context: context,
builder: (context) => ApplyTextOptions(child: child),
).then((value) {
if (value != null) {
setState(() {
lastSelectedValue = value;
});
}
});
}
void _showDemoActionSheet({BuildContext context, Widget child}) {
child = ApplyTextOptions(
child: CupertinoTheme(
data: CupertinoTheme.of(context),
child: child,
),
);
showCupertinoModalPopup<String>(
context: context,
builder: (context) => child,
).then((value) {
if (value != null) {
setState(() {
lastSelectedValue = value;
});
}
});
}
void _onAlertPress(BuildContext context) {
_showDemoDialog(
context: context,
child: CupertinoAlertDialog(
title: Text(GalleryLocalizations.of(context).dialogDiscardTitle),
actions: [
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertDiscard,
),
isDestructiveAction: true,
onPressed: () => Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertDiscard,
),
),
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertCancel,
),
isDefaultAction: true,
onPressed: () => Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertCancel,
),
),
],
),
);
}
void _onAlertWithTitlePress(BuildContext context) {
_showDemoDialog(
context: context,
child: CupertinoAlertDialog(
title: Text(
GalleryLocalizations.of(context).cupertinoAlertLocationTitle,
),
content: Text(
GalleryLocalizations.of(context).cupertinoAlertLocationDescription,
),
actions: [
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertDontAllow,
),
onPressed: () => Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertDontAllow,
),
),
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertAllow,
),
onPressed: () => Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertAllow,
),
),
],
),
);
}
void _onAlertWithButtonsPress(BuildContext context) {
_showDemoDialog(
context: context,
child: CupertinoDessertDialog(
title: Text(
GalleryLocalizations.of(context).cupertinoAlertFavoriteDessert,
),
content: Text(
GalleryLocalizations.of(context).cupertinoAlertDessertDescription,
),
),
);
}
void _onAlertButtonsOnlyPress(BuildContext context) {
_showDemoDialog(
context: context,
child: const CupertinoDessertDialog(),
);
}
void _onActionSheetPress(BuildContext context) {
_showDemoActionSheet(
context: context,
child: CupertinoActionSheet(
title: Text(
GalleryLocalizations.of(context).cupertinoAlertFavoriteDessert,
),
message: Text(
GalleryLocalizations.of(context).cupertinoAlertDessertDescription,
),
actions: [
CupertinoActionSheetAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertCheesecake,
),
onPressed: () => Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertCheesecake,
),
),
CupertinoActionSheetAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertTiramisu,
),
onPressed: () => Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertTiramisu,
),
),
CupertinoActionSheetAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertApplePie,
),
onPressed: () => Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertApplePie,
),
),
],
cancelButton: CupertinoActionSheetAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertCancel,
),
isDefaultAction: true,
onPressed: () => Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertCancel,
),
),
),
);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticallyImplyLeading: false,
middle: Text(_title(context)),
),
child: Builder(
builder: (context) {
return Column(
children: [
Expanded(
child: Center(
child: CupertinoButton.filled(
child: Text(
GalleryLocalizations.of(context).cupertinoShowAlert,
),
onPressed: () {
switch (widget.type) {
case AlertDemoType.alert:
_onAlertPress(context);
break;
case AlertDemoType.alertTitle:
_onAlertWithTitlePress(context);
break;
case AlertDemoType.alertButtons:
_onAlertWithButtonsPress(context);
break;
case AlertDemoType.alertButtonsOnly:
_onAlertButtonsOnlyPress(context);
break;
case AlertDemoType.actionSheet:
_onActionSheetPress(context);
break;
}
},
),
),
),
if (lastSelectedValue != null)
Padding(
padding: const EdgeInsets.all(16),
child: Text(
GalleryLocalizations.of(context)
.dialogSelectedOption(lastSelectedValue),
style: CupertinoTheme.of(context).textTheme.textStyle,
textAlign: TextAlign.center,
),
),
],
);
},
),
);
}
}
class CupertinoDessertDialog extends StatelessWidget {
const CupertinoDessertDialog({Key key, this.title, this.content})
: super(key: key);
final Widget title;
final Widget content;
@override
Widget build(BuildContext context) {
return CupertinoAlertDialog(
title: title,
content: content,
actions: [
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertCheesecake,
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertCheesecake,
);
},
),
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertTiramisu,
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertTiramisu,
);
},
),
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertApplePie,
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertApplePie,
);
},
),
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertChocolateBrownie,
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertChocolateBrownie,
);
},
),
CupertinoDialogAction(
child: Text(
GalleryLocalizations.of(context).cupertinoAlertCancel,
),
isDestructiveAction: true,
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(
GalleryLocalizations.of(context).cupertinoAlertCancel,
);
},
),
],
);
}
}
// END

View File

@@ -0,0 +1,44 @@
// Copyright 2019 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/cupertino.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoButtonDemo
class CupertinoButtonDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticallyImplyLeading: false,
middle:
Text(GalleryLocalizations.of(context).demoCupertinoButtonsTitle),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CupertinoButton(
child: Text(
GalleryLocalizations.of(context).cupertinoButton,
),
onPressed: () {},
),
SizedBox(height: 16),
CupertinoButton.filled(
child: Text(
GalleryLocalizations.of(context).cupertinoButtonWithBackground,
),
onPressed: () {},
),
],
),
),
);
}
}
// END

View File

@@ -0,0 +1,82 @@
// Copyright 2019 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/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoNavigationBarDemo
class CupertinoNavigationBarDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Navigator(onGenerateRoute: (settings) {
return _NoAnimationCupertinoPageRoute<void>(
title: GalleryLocalizations.of(context).demoCupertinoNavigationBarTitle,
builder: (context) => CupertinoPageScaffold(
child: CustomScrollView(
slivers: [
CupertinoSliverNavigationBar(
automaticallyImplyLeading: false,
),
SliverPadding(
padding: MediaQuery.of(context)
.removePadding(removeTop: true)
.padding,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final title = GalleryLocalizations.of(context)
.starterAppDrawerItem(index + 1);
return ListTile(
onTap: () {
Navigator.of(context).push(CupertinoPageRoute<void>(
title: title,
builder: (context) => _SecondPage(),
));
},
title: Text(title),
);
},
childCount: 20,
),
),
),
],
),
),
);
});
}
}
class _SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(),
child: Container(),
);
}
}
/// A CupertinoPageRoute without any transition animations.
class _NoAnimationCupertinoPageRoute<T> extends CupertinoPageRoute<T> {
_NoAnimationCupertinoPageRoute({
@required WidgetBuilder builder,
String title,
}) : super(builder: builder, title: title);
@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return child;
}
}
// END

View File

@@ -0,0 +1,241 @@
// Copyright 2019 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/cupertino.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:intl/intl.dart';
// BEGIN cupertinoPickersDemo
class CupertinoPickerDemo extends StatefulWidget {
@override
_CupertinoPickerDemoState createState() => _CupertinoPickerDemoState();
}
class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
Duration timer = const Duration();
// Value that is shown in the date picker in date mode.
DateTime date = DateTime.now();
// Value that is shown in the date picker in time mode.
DateTime time = DateTime.now();
// Value that is shown in the date picker in dateAndTime mode.
DateTime dateTime = DateTime.now();
Widget _buildDatePicker(BuildContext context) {
return GestureDetector(
onTap: () {
showCupertinoModalPopup<void>(
context: context,
builder: (context) {
return _BottomPicker(
child: CupertinoDatePicker(
backgroundColor:
CupertinoColors.systemBackground.resolveFrom(context),
mode: CupertinoDatePickerMode.date,
initialDateTime: date,
onDateTimeChanged: (newDateTime) {
setState(() => date = newDateTime);
},
),
);
},
);
},
child: _Menu(children: [
Text(GalleryLocalizations.of(context).demoCupertinoPickerDate),
Text(
DateFormat.yMMMMd().format(date),
style: TextStyle(color: CupertinoColors.inactiveGray),
),
]),
);
}
Widget _buildTimePicker(BuildContext context) {
return GestureDetector(
onTap: () {
showCupertinoModalPopup<void>(
context: context,
builder: (context) {
return _BottomPicker(
child: CupertinoDatePicker(
backgroundColor:
CupertinoColors.systemBackground.resolveFrom(context),
mode: CupertinoDatePickerMode.time,
initialDateTime: time,
onDateTimeChanged: (newDateTime) {
setState(() => time = newDateTime);
},
),
);
},
);
},
child: _Menu(
children: [
Text(GalleryLocalizations.of(context).demoCupertinoPickerTime),
Text(
DateFormat.jm().format(time),
style: TextStyle(color: CupertinoColors.inactiveGray),
),
],
),
);
}
Widget _buildDateAndTimePicker(BuildContext context) {
return GestureDetector(
onTap: () {
showCupertinoModalPopup<void>(
context: context,
builder: (context) {
return _BottomPicker(
child: CupertinoDatePicker(
backgroundColor:
CupertinoColors.systemBackground.resolveFrom(context),
mode: CupertinoDatePickerMode.dateAndTime,
initialDateTime: dateTime,
onDateTimeChanged: (newDateTime) {
setState(() => dateTime = newDateTime);
},
),
);
},
);
},
child: _Menu(
children: [
Text(GalleryLocalizations.of(context).demoCupertinoPickerDateTime),
Text(
DateFormat.yMMMd().add_jm().format(dateTime),
style: TextStyle(color: CupertinoColors.inactiveGray),
),
],
),
);
}
Widget _buildCountdownTimerPicker(BuildContext context) {
return GestureDetector(
onTap: () {
showCupertinoModalPopup<void>(
context: context,
builder: (context) {
return _BottomPicker(
child: CupertinoTimerPicker(
backgroundColor:
CupertinoColors.systemBackground.resolveFrom(context),
initialTimerDuration: timer,
onTimerDurationChanged: (newTimer) {
setState(() => timer = newTimer);
},
),
);
},
);
},
child: _Menu(
children: [
Text(GalleryLocalizations.of(context).demoCupertinoPickerTimer),
Text(
'${timer.inHours}:'
'${(timer.inMinutes % 60).toString().padLeft(2, '0')}:'
'${(timer.inSeconds % 60).toString().padLeft(2, '0')}',
style: TextStyle(color: CupertinoColors.inactiveGray),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticallyImplyLeading: false,
middle: Text(GalleryLocalizations.of(context).demoCupertinoPickerTitle),
),
child: DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: ListView(
children: [
const SizedBox(height: 32),
_buildDatePicker(context),
_buildTimePicker(context),
_buildDateAndTimePicker(context),
_buildCountdownTimerPicker(context),
],
),
),
);
}
}
class _BottomPicker extends StatelessWidget {
const _BottomPicker({
Key key,
@required this.child,
}) : assert(child != null),
super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
return Container(
height: 216,
padding: const EdgeInsets.only(top: 6),
color: CupertinoColors.systemBackground.resolveFrom(context),
child: DefaultTextStyle(
style: TextStyle(
color: CupertinoColors.label.resolveFrom(context),
fontSize: 22,
),
child: GestureDetector(
// Blocks taps from propagating to the modal sheet and popping.
onTap: () {},
child: SafeArea(
top: false,
child: child,
),
),
),
);
}
}
class _Menu extends StatelessWidget {
const _Menu({
Key key,
@required this.children,
}) : assert(children != null),
super(key: key);
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: const Border(
top: BorderSide(color: CupertinoColors.inactiveGray, width: 0),
bottom: BorderSide(color: CupertinoColors.inactiveGray, width: 0),
),
),
height: 44,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children,
),
),
);
}
}
// END

View File

@@ -0,0 +1,74 @@
// Copyright 2019 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 'dart:math' show Random;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoRefreshDemo
class CupertinoRefreshControlDemo extends StatefulWidget {
@override
_CupertinoRefreshControlDemoState createState() =>
_CupertinoRefreshControlDemoState();
}
class _CupertinoRefreshControlDemoState
extends State<CupertinoRefreshControlDemo> {
static const listCount = 20;
var randomList = List<int>.generate(listCount, (i) => i + 1);
void _shuffleList() => randomList.shuffle(Random());
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: CustomScrollView(
// If left unspecified, the [CustomScrollView] appends an
// [AlwaysScrollableScrollPhysics]. Behind the scene, the ScrollableState
// will attach that [AlwaysScrollableScrollPhysics] to the output of
// [ScrollConfiguration.of] which will be a [ClampingScrollPhysics]
// on Android.
// To demonstrate the iOS behavior in this demo and to ensure that the list
// always scrolls, we specifically use a [BouncingScrollPhysics] combined
// with a [AlwaysScrollableScrollPhysics]
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
slivers: [
CupertinoSliverNavigationBar(
automaticallyImplyLeading: false,
largeTitle: Text(
GalleryLocalizations.of(context).demoCupertinoPullToRefreshTitle,
),
),
CupertinoSliverRefreshControl(
onRefresh: () {
return Future<void>.delayed(const Duration(seconds: 1))
..then<void>((_) {
if (mounted) {
setState(() => _shuffleList());
}
});
},
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final title = GalleryLocalizations.of(context)
.starterAppDrawerItem(randomList[index]);
return ListTile(title: Text(title));
},
childCount: listCount,
),
),
],
),
);
}
}
// END

View File

@@ -0,0 +1,87 @@
// Copyright 2019 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/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoSegmentedControlDemo
class CupertinoSegmentedControlDemo extends StatefulWidget {
@override
_CupertinoSegmentedControlDemoState createState() =>
_CupertinoSegmentedControlDemoState();
}
class _CupertinoSegmentedControlDemoState
extends State<CupertinoSegmentedControlDemo> {
int currentSegment = 0;
void onValueChanged(int newValue) {
setState(() {
currentSegment = newValue;
});
}
@override
Widget build(BuildContext context) {
final localizations = GalleryLocalizations.of(context);
final segmentedControlMaxWidth = 500.0;
final children = <int, Widget>{
0: Text(localizations.colorsIndigo),
1: Text(localizations.colorsTeal),
2: Text(localizations.colorsCyan),
};
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticallyImplyLeading: false,
middle: Text(
localizations.demoCupertinoSegmentedControlTitle,
),
),
child: DefaultTextStyle(
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(fontSize: 13),
child: SafeArea(
child: ListView(
children: [
const SizedBox(height: 16),
SizedBox(
width: segmentedControlMaxWidth,
child: CupertinoSegmentedControl<int>(
children: children,
onValueChanged: onValueChanged,
groupValue: currentSegment,
),
),
SizedBox(
width: segmentedControlMaxWidth,
child: Padding(
padding: const EdgeInsets.all(16),
child: CupertinoSlidingSegmentedControl<int>(
children: children,
onValueChanged: onValueChanged,
groupValue: currentSegment,
),
),
),
Container(
padding: const EdgeInsets.all(16),
height: 300,
alignment: Alignment.center,
child: children[currentSegment],
),
],
),
),
),
);
}
}
// END

View File

@@ -0,0 +1,94 @@
// Copyright 2019 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/cupertino.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoSliderDemo
class CupertinoSliderDemo extends StatefulWidget {
@override
_CupertinoSliderDemoState createState() => _CupertinoSliderDemoState();
}
class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> {
double _value = 25.0;
double _discreteValue = 20.0;
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticallyImplyLeading: false,
middle: Text(GalleryLocalizations.of(context).demoCupertinoSliderTitle),
),
child: DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: double.infinity,
child: CupertinoSlider(
value: _value,
min: 0.0,
max: 100.0,
onChanged: (value) {
setState(() {
_value = value;
});
},
),
),
MergeSemantics(
child: Text(
GalleryLocalizations.of(context)
.demoCupertinoSliderContinuous(
_value.toStringAsFixed(1),
),
),
),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: double.infinity,
child: CupertinoSlider(
value: _discreteValue,
min: 0.0,
max: 100.0,
divisions: 5,
onChanged: (value) {
setState(() {
_discreteValue = value;
});
},
),
),
MergeSemantics(
child: Text(
GalleryLocalizations.of(context)
.demoCupertinoSliderDiscrete(
_discreteValue.toStringAsFixed(1),
),
),
),
],
),
],
),
),
),
);
}
}
// END

View File

@@ -0,0 +1,47 @@
// Copyright 2019 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/cupertino.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoSwitchDemo
class CupertinoSwitchDemo extends StatefulWidget {
@override
_CupertinoSwitchDemoState createState() => _CupertinoSwitchDemoState();
}
class _CupertinoSwitchDemoState extends State<CupertinoSwitchDemo> {
bool _switchValue = false;
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticallyImplyLeading: false,
middle: Text(
GalleryLocalizations.of(context).demoSelectionControlsSwitchTitle,
),
),
child: Center(
child: Semantics(
container: true,
label:
GalleryLocalizations.of(context).demoSelectionControlsSwitchTitle,
child: CupertinoSwitch(
value: _switchValue,
onChanged: (value) {
setState(() {
_switchValue = value;
});
},
),
),
),
);
}
}
// END

View File

@@ -0,0 +1,88 @@
// Copyright 2019 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/cupertino.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoNavigationDemo
class _TabInfo {
const _TabInfo(this.title, this.icon);
final String title;
final IconData icon;
}
class CupertinoTabBarDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _tabInfo = [
_TabInfo(
GalleryLocalizations.of(context).cupertinoTabBarHomeTab,
CupertinoIcons.home,
),
_TabInfo(
GalleryLocalizations.of(context).cupertinoTabBarChatTab,
CupertinoIcons.conversation_bubble,
),
_TabInfo(
GalleryLocalizations.of(context).cupertinoTabBarProfileTab,
CupertinoIcons.profile_circled,
),
];
return DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
for (final tabInfo in _tabInfo)
BottomNavigationBarItem(
title: Text(tabInfo.title),
icon: Icon(tabInfo.icon),
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) => _CupertinoDemoTab(
title: _tabInfo[index].title,
icon: _tabInfo[index].icon,
),
defaultTitle: _tabInfo[index].title,
);
},
),
);
}
}
class _CupertinoDemoTab extends StatelessWidget {
const _CupertinoDemoTab({
Key key,
@required this.title,
@required this.icon,
}) : super(key: key);
final String title;
final IconData icon;
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
backgroundColor: CupertinoColors.systemBackground,
child: Center(
child: Icon(
icon,
semanticLabel: title,
size: 100,
),
),
);
}
}
// END

View File

@@ -0,0 +1,66 @@
// Copyright 2019 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/cupertino.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN cupertinoTextFieldDemo
class CupertinoTextFieldDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final localizations = GalleryLocalizations.of(context);
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticallyImplyLeading: false,
middle: Text(localizations.demoCupertinoTextFieldTitle),
),
child: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: CupertinoTextField(
placeholder: localizations.demoTextFieldEmail,
keyboardType: TextInputType.emailAddress,
clearButtonMode: OverlayVisibilityMode.editing,
autocorrect: false,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: CupertinoTextField(
placeholder: localizations.rallyLoginPassword,
clearButtonMode: OverlayVisibilityMode.editing,
obscureText: true,
autocorrect: false,
),
),
CupertinoTextField(
prefix: Icon(
CupertinoIcons.padlock_solid,
size: 28,
),
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 12),
clearButtonMode: OverlayVisibilityMode.editing,
keyboardType: TextInputType.number,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0,
color: CupertinoColors.inactiveGray,
),
),
),
placeholder: localizations.demoCupertinoTextFieldPIN,
),
],
),
),
);
}
}
// END

View File

@@ -0,0 +1,125 @@
// 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 colorScheme = Theme.of(context).colorScheme;
final banner = MaterialBanner(
content: Text(GalleryLocalizations.of(context).bannerDemoText),
leading: _showLeading
? CircleAvatar(
child: Icon(Icons.access_alarm, color: colorScheme.onPrimary),
backgroundColor: 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;
});
},
),
],
backgroundColor: colorScheme.background,
);
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

View File

@@ -0,0 +1,169 @@
// Copyright 2019 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 bottomAppBarDemo
class BottomAppBarDemo extends StatefulWidget {
@override
State createState() => _BottomAppBarDemoState();
}
class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
var _showFab = true;
var _showNotch = true;
var _fabLocation = FloatingActionButtonLocation.endDocked;
void _onShowNotchChanged(bool value) {
setState(() {
_showNotch = value;
});
}
void _onShowFabChanged(bool value) {
setState(() {
_showFab = value;
});
}
void _onFabLocationChanged(FloatingActionButtonLocation value) {
setState(() {
_fabLocation = value;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoBottomAppBarTitle),
),
body: ListView(
padding: const EdgeInsets.only(bottom: 88),
children: [
SwitchListTile(
title: Text(
GalleryLocalizations.of(context).demoFloatingButtonTitle,
),
value: _showFab,
onChanged: _onShowFabChanged,
),
SwitchListTile(
title: Text(GalleryLocalizations.of(context).bottomAppBarNotch),
value: _showNotch,
onChanged: _onShowNotchChanged,
),
Padding(
padding: const EdgeInsets.all(16),
child: Text(GalleryLocalizations.of(context).bottomAppBarPosition),
),
RadioListTile<FloatingActionButtonLocation>(
title: Text(
GalleryLocalizations.of(context).bottomAppBarPositionDockedEnd,
),
value: FloatingActionButtonLocation.endDocked,
groupValue: _fabLocation,
onChanged: _onFabLocationChanged,
),
RadioListTile<FloatingActionButtonLocation>(
title: Text(
GalleryLocalizations.of(context).bottomAppBarPositionDockedCenter,
),
value: FloatingActionButtonLocation.centerDocked,
groupValue: _fabLocation,
onChanged: _onFabLocationChanged,
),
RadioListTile<FloatingActionButtonLocation>(
title: Text(
GalleryLocalizations.of(context).bottomAppBarPositionFloatingEnd,
),
value: FloatingActionButtonLocation.endFloat,
groupValue: _fabLocation,
onChanged: _onFabLocationChanged,
),
RadioListTile<FloatingActionButtonLocation>(
title: Text(
GalleryLocalizations.of(context)
.bottomAppBarPositionFloatingCenter,
),
value: FloatingActionButtonLocation.centerFloat,
groupValue: _fabLocation,
onChanged: _onFabLocationChanged,
),
],
),
floatingActionButton: _showFab
? FloatingActionButton(
onPressed: () {
print('Floating action button pressed');
},
child: Icon(Icons.add),
tooltip: GalleryLocalizations.of(context).buttonTextCreate,
)
: null,
floatingActionButtonLocation: _fabLocation,
bottomNavigationBar: _DemoBottomAppBar(
fabLocation: _fabLocation,
shape: _showNotch ? CircularNotchedRectangle() : null,
),
);
}
}
class _DemoBottomAppBar extends StatelessWidget {
const _DemoBottomAppBar({
this.fabLocation,
this.shape,
});
final FloatingActionButtonLocation fabLocation;
final NotchedShape shape;
static final centerLocations = <FloatingActionButtonLocation>[
FloatingActionButtonLocation.centerDocked,
FloatingActionButtonLocation.centerFloat,
];
@override
Widget build(BuildContext context) {
return BottomAppBar(
shape: shape,
child: IconTheme(
data: IconThemeData(color: Theme.of(context).colorScheme.onPrimary),
child: Row(
children: [
IconButton(
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
icon: const Icon(Icons.menu),
onPressed: () {
print('Menu button pressed');
},
),
if (centerLocations.contains(fabLocation)) const Spacer(),
IconButton(
tooltip: GalleryLocalizations.of(context).starterAppTooltipSearch,
icon: const Icon(Icons.search),
onPressed: () {
print('Search button pressed');
},
),
IconButton(
tooltip:
GalleryLocalizations.of(context).starterAppTooltipFavorite,
icon: const Icon(Icons.favorite),
onPressed: () {
print('Favorite button pressed');
},
),
],
),
),
);
}
}
// END

View File

@@ -0,0 +1,212 @@
// Copyright 2019 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 bottomNavigationDemo
enum BottomNavigationDemoType {
withLabels,
withoutLabels,
}
class BottomNavigationDemo extends StatefulWidget {
BottomNavigationDemo({Key key, @required this.type}) : super(key: key);
final BottomNavigationDemoType type;
@override
_BottomNavigationDemoState createState() => _BottomNavigationDemoState();
}
class _BottomNavigationDemoState extends State<BottomNavigationDemo>
with TickerProviderStateMixin {
int _currentIndex = 0;
List<_NavigationIconView> _navigationViews;
String _title(BuildContext context) {
switch (widget.type) {
case BottomNavigationDemoType.withLabels:
return GalleryLocalizations.of(context)
.demoBottomNavigationPersistentLabels;
case BottomNavigationDemoType.withoutLabels:
return GalleryLocalizations.of(context)
.demoBottomNavigationSelectedLabel;
}
return '';
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_navigationViews == null) {
_navigationViews = <_NavigationIconView>[
_NavigationIconView(
icon: const Icon(Icons.add_comment),
title: GalleryLocalizations.of(context).bottomNavigationCommentsTab,
vsync: this,
),
_NavigationIconView(
icon: const Icon(Icons.calendar_today),
title: GalleryLocalizations.of(context).bottomNavigationCalendarTab,
vsync: this,
),
_NavigationIconView(
icon: const Icon(Icons.account_circle),
title: GalleryLocalizations.of(context).bottomNavigationAccountTab,
vsync: this,
),
_NavigationIconView(
icon: const Icon(Icons.alarm_on),
title: GalleryLocalizations.of(context).bottomNavigationAlarmTab,
vsync: this,
),
_NavigationIconView(
icon: const Icon(Icons.camera_enhance),
title: GalleryLocalizations.of(context).bottomNavigationCameraTab,
vsync: this,
),
];
_navigationViews[_currentIndex].controller.value = 1;
}
}
@override
void dispose() {
for (_NavigationIconView view in _navigationViews) {
view.controller.dispose();
}
super.dispose();
}
Widget _buildTransitionsStack() {
final List<FadeTransition> transitions = <FadeTransition>[];
for (_NavigationIconView view in _navigationViews) {
transitions.add(view.transition(context));
}
// We want to have the newly animating (fading in) views on top.
transitions.sort((a, b) {
final aAnimation = a.opacity;
final bAnimation = b.opacity;
final aValue = aAnimation.value;
final bValue = bAnimation.value;
return aValue.compareTo(bValue);
});
return Stack(children: transitions);
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
var bottomNavigationBarItems = _navigationViews
.map<BottomNavigationBarItem>((navigationView) => navigationView.item)
.toList();
if (widget.type == BottomNavigationDemoType.withLabels) {
bottomNavigationBarItems =
bottomNavigationBarItems.sublist(0, _navigationViews.length - 2);
_currentIndex =
_currentIndex.clamp(0, bottomNavigationBarItems.length - 1).toInt();
}
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title(context)),
),
body: Center(
child: _buildTransitionsStack(),
),
bottomNavigationBar: BottomNavigationBar(
showUnselectedLabels:
widget.type == BottomNavigationDemoType.withLabels,
items: bottomNavigationBarItems,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
selectedFontSize: textTheme.caption.fontSize,
unselectedFontSize: textTheme.caption.fontSize,
onTap: (index) {
setState(() {
_navigationViews[_currentIndex].controller.reverse();
_currentIndex = index;
_navigationViews[_currentIndex].controller.forward();
});
},
selectedItemColor: colorScheme.onPrimary,
unselectedItemColor: colorScheme.onPrimary.withOpacity(0.38),
backgroundColor: colorScheme.primary,
),
);
}
}
class _NavigationIconView {
_NavigationIconView({
this.title,
this.icon,
TickerProvider vsync,
}) : item = BottomNavigationBarItem(
icon: icon,
title: Text(title),
),
controller = AnimationController(
duration: kThemeAnimationDuration,
vsync: vsync,
) {
_animation = controller.drive(CurveTween(
curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
));
}
final String title;
final Widget icon;
final BottomNavigationBarItem item;
final AnimationController controller;
Animation<double> _animation;
FadeTransition transition(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: Stack(
children: [
ExcludeSemantics(
child: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.asset(
'assets/demos/bottom_navigation_background.png',
),
),
),
),
),
Center(
child: IconTheme(
data: IconThemeData(
color: Colors.white,
size: 80,
),
child: Semantics(
label: GalleryLocalizations.of(context)
.bottomNavigationContentPlaceholder(title),
child: icon,
),
),
),
],
),
);
}
}
// END

View File

@@ -0,0 +1,192 @@
// Copyright 2019 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';
enum BottomSheetDemoType {
persistent,
modal,
}
class BottomSheetDemo extends StatelessWidget {
BottomSheetDemo({Key key, @required this.type}) : super(key: key);
final BottomSheetDemoType type;
String _title(BuildContext context) {
switch (type) {
case BottomSheetDemoType.persistent:
return GalleryLocalizations.of(context).demoBottomSheetPersistentTitle;
case BottomSheetDemoType.modal:
return GalleryLocalizations.of(context).demoBottomSheetModalTitle;
}
return '';
}
Widget _bottomSheetDemo(BuildContext context) {
switch (type) {
case BottomSheetDemoType.persistent:
return _PersistentBottomSheetDemo();
break;
case BottomSheetDemoType.modal:
default:
return _ModalBottomSheetDemo();
break;
}
}
@override
Widget build(BuildContext context) {
// We wrap the demo in a [Navigator] to make sure that the modal bottom
// sheets gets dismissed when changing demo.
return Navigator(
// Adding [ValueKey] to make sure that the widget gets rebuilt when
// changing type.
key: ValueKey(type),
onGenerateRoute: (settings) {
return MaterialPageRoute<Widget>(
builder: (context) => Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title(context)),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: Theme.of(context).colorScheme.secondary,
child: Icon(
Icons.add,
semanticLabel:
GalleryLocalizations.of(context).demoBottomSheetAddLabel,
),
),
body: _bottomSheetDemo(context),
),
settings: settings,
);
},
);
}
}
// BEGIN bottomSheetDemoModal#1 bottomSheetDemoPersistent#1
class _BottomSheetContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 300,
child: Column(
children: [
Container(
height: 70,
child: Center(
child: Text(
GalleryLocalizations.of(context).demoBottomSheetHeader,
textAlign: TextAlign.center,
),
),
),
Divider(thickness: 1),
Expanded(
child: ListView.builder(
itemCount: 21,
itemBuilder: (context, index) {
return ListTile(
title: Text(GalleryLocalizations.of(context)
.demoBottomSheetItem(index)),
);
},
),
),
],
),
);
}
}
// END bottomSheetDemoModal#1 bottomSheetDemoPersistent#1
// BEGIN bottomSheetDemoModal#2
class _ModalBottomSheetDemo extends StatelessWidget {
void _showModalBottomSheet(BuildContext context) {
showModalBottomSheet<void>(
context: context,
builder: (context) {
return _BottomSheetContent();
},
);
}
@override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
onPressed: () {
_showModalBottomSheet(context);
},
child: Text(GalleryLocalizations.of(context).demoBottomSheetButtonText),
),
);
}
}
// END
// BEGIN bottomSheetDemoPersistent#2
class _PersistentBottomSheetDemo extends StatefulWidget {
@override
_PersistentBottomSheetDemoState createState() =>
_PersistentBottomSheetDemoState();
}
class _PersistentBottomSheetDemoState
extends State<_PersistentBottomSheetDemo> {
VoidCallback _showBottomSheetCallback;
@override
void initState() {
super.initState();
_showBottomSheetCallback = _showPersistentBottomSheet;
}
void _showPersistentBottomSheet() {
setState(() {
// Disable the show bottom sheet button.
_showBottomSheetCallback = null;
});
Scaffold.of(context)
.showBottomSheet<void>(
(context) {
return _BottomSheetContent();
},
elevation: 25,
)
.closed
.whenComplete(() {
if (mounted) {
setState(() {
// Re-enable the bottom sheet button.
_showBottomSheetCallback = _showPersistentBottomSheet;
});
}
});
}
@override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
onPressed: _showBottomSheetCallback,
child: Text(GalleryLocalizations.of(context).demoBottomSheetButtonText),
),
);
}
}
// END

View File

@@ -0,0 +1,214 @@
// Copyright 2019 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';
enum ButtonDemoType {
flat,
raised,
outline,
toggle,
floating,
}
class ButtonDemo extends StatelessWidget {
const ButtonDemo({Key key, this.type}) : super(key: key);
final ButtonDemoType type;
String _title(BuildContext context) {
switch (type) {
case ButtonDemoType.flat:
return GalleryLocalizations.of(context).demoFlatButtonTitle;
case ButtonDemoType.raised:
return GalleryLocalizations.of(context).demoRaisedButtonTitle;
case ButtonDemoType.outline:
return GalleryLocalizations.of(context).demoOutlineButtonTitle;
case ButtonDemoType.toggle:
return GalleryLocalizations.of(context).demoToggleButtonTitle;
case ButtonDemoType.floating:
return GalleryLocalizations.of(context).demoFloatingButtonTitle;
}
return '';
}
@override
Widget build(BuildContext context) {
Widget buttons;
switch (type) {
case ButtonDemoType.flat:
buttons = _FlatButtonDemo();
break;
case ButtonDemoType.raised:
buttons = _RaisedButtonDemo();
break;
case ButtonDemoType.outline:
buttons = _OutlineButtonDemo();
break;
case ButtonDemoType.toggle:
buttons = _ToggleButtonsDemo();
break;
case ButtonDemoType.floating:
buttons = _FloatingActionButtonDemo();
break;
}
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title(context)),
),
body: buttons,
);
}
}
// BEGIN buttonDemoFlat
class _FlatButtonDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FlatButton(
child: Text(GalleryLocalizations.of(context).buttonText),
onPressed: () {},
),
SizedBox(height: 12),
FlatButton.icon(
icon: const Icon(Icons.add, size: 18),
label: Text(GalleryLocalizations.of(context).buttonText),
onPressed: () {},
),
],
),
);
}
}
// END
// BEGIN buttonDemoRaised
class _RaisedButtonDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
child: Text(GalleryLocalizations.of(context).buttonText),
onPressed: () {},
),
SizedBox(height: 12),
RaisedButton.icon(
icon: const Icon(Icons.add, size: 18),
label: Text(GalleryLocalizations.of(context).buttonText),
onPressed: () {},
),
],
),
);
}
}
// END
// BEGIN buttonDemoOutline
class _OutlineButtonDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
OutlineButton(
// TODO: Should update to OutlineButton follow material spec.
highlightedBorderColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
child: Text(GalleryLocalizations.of(context).buttonText),
onPressed: () {},
),
SizedBox(height: 12),
OutlineButton.icon(
// TODO: Should update to OutlineButton follow material spec.
highlightedBorderColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
icon: const Icon(Icons.add, size: 18),
label: Text(GalleryLocalizations.of(context).buttonText),
onPressed: () {},
),
],
),
);
}
}
// END
// BEGIN buttonDemoToggle
class _ToggleButtonsDemo extends StatefulWidget {
@override
_ToggleButtonsDemoState createState() => _ToggleButtonsDemoState();
}
class _ToggleButtonsDemoState extends State<_ToggleButtonsDemo> {
final isSelected = <bool>[false, false, false];
@override
Widget build(BuildContext context) {
return Center(
child: ToggleButtons(
children: [
Icon(Icons.ac_unit),
Icon(Icons.call),
Icon(Icons.cake),
],
onPressed: (index) {
setState(() {
isSelected[index] = !isSelected[index];
});
},
isSelected: isSelected,
),
);
}
}
// END
// BEGIN buttonDemoFloating
class _FloatingActionButtonDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {},
tooltip: GalleryLocalizations.of(context).buttonTextCreate,
),
SizedBox(height: 20),
FloatingActionButton.extended(
icon: const Icon(Icons.add),
label: Text(GalleryLocalizations.of(context).buttonTextCreate),
onPressed: () {},
),
],
),
);
}
}
// END

View File

@@ -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

View File

@@ -0,0 +1,215 @@
// Copyright 2019 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';
enum ChipDemoType {
action,
choice,
filter,
input,
}
class ChipDemo extends StatelessWidget {
const ChipDemo({Key key, this.type}) : super(key: key);
final ChipDemoType type;
String _title(BuildContext context) {
switch (type) {
case ChipDemoType.action:
return GalleryLocalizations.of(context).demoActionChipTitle;
case ChipDemoType.choice:
return GalleryLocalizations.of(context).demoChoiceChipTitle;
case ChipDemoType.filter:
return GalleryLocalizations.of(context).demoFilterChipTitle;
case ChipDemoType.input:
return GalleryLocalizations.of(context).demoInputChipTitle;
}
return '';
}
@override
Widget build(BuildContext context) {
Widget buttons;
switch (type) {
case ChipDemoType.action:
buttons = _ActionChipDemo();
break;
case ChipDemoType.choice:
buttons = _ChoiceChipDemo();
break;
case ChipDemoType.filter:
buttons = _FilterChipDemo();
break;
case ChipDemoType.input:
buttons = _InputChipDemo();
break;
}
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title(context)),
),
body: buttons,
);
}
}
// BEGIN chipDemoAction
class _ActionChipDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: ActionChip(
onPressed: () {},
avatar: Icon(
Icons.brightness_5,
color: Colors.black54,
),
label: Text(GalleryLocalizations.of(context).chipTurnOnLights),
),
);
}
}
// END
// BEGIN chipDemoChoice
class _ChoiceChipDemo extends StatefulWidget {
@override
_ChoiceChipDemoState createState() => _ChoiceChipDemoState();
}
class _ChoiceChipDemoState extends State<_ChoiceChipDemo> {
int indexSelected = -1;
@override
Widget build(BuildContext context) {
return Center(
child: Wrap(
children: [
ChoiceChip(
label: Text(GalleryLocalizations.of(context).chipSmall),
selected: indexSelected == 0,
onSelected: (value) {
setState(() {
indexSelected = value ? 0 : -1;
});
},
),
SizedBox(width: 8),
ChoiceChip(
label: Text(GalleryLocalizations.of(context).chipMedium),
selected: indexSelected == 1,
onSelected: (value) {
setState(() {
indexSelected = value ? 1 : -1;
});
},
),
SizedBox(width: 8),
ChoiceChip(
label: Text(GalleryLocalizations.of(context).chipLarge),
selected: indexSelected == 2,
onSelected: (value) {
setState(() {
indexSelected = value ? 2 : -1;
});
},
),
],
),
);
}
}
// END
// BEGIN chipDemoFilter
class _FilterChipDemo extends StatefulWidget {
@override
_FilterChipDemoState createState() => _FilterChipDemoState();
}
class _FilterChipDemoState extends State<_FilterChipDemo> {
bool isSelectedElevator = false;
bool isSelectedWasher = false;
bool isSelectedFireplace = false;
@override
Widget build(BuildContext context) {
final chips = [
FilterChip(
label: Text(GalleryLocalizations.of(context).chipElevator),
selected: isSelectedElevator,
onSelected: (value) {
setState(() {
isSelectedElevator = !isSelectedElevator;
});
},
),
FilterChip(
label: Text(GalleryLocalizations.of(context).chipWasher),
selected: isSelectedWasher,
onSelected: (value) {
setState(() {
isSelectedWasher = !isSelectedWasher;
});
},
),
FilterChip(
label: Text(GalleryLocalizations.of(context).chipFireplace),
selected: isSelectedFireplace,
onSelected: (value) {
setState(() {
isSelectedFireplace = !isSelectedFireplace;
});
},
),
];
return Center(
child: Wrap(
children: [
for (final chip in chips)
Padding(
padding: const EdgeInsets.all(4),
child: chip,
)
],
),
);
}
}
// END
// BEGIN chipDemoInput
class _InputChipDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: InputChip(
onPressed: () {},
onDeleted: () {},
avatar: Icon(
Icons.directions_bike,
size: 20,
color: Colors.black54,
),
deleteIconColor: Colors.black54,
label: Text(GalleryLocalizations.of(context).chipBiking),
),
);
}
}
// END

View File

@@ -0,0 +1,551 @@
// Copyright 2019 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:flutter/rendering.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:intl/intl.dart';
// BEGIN dataTableDemo
class DataTableDemo extends StatefulWidget {
@override
_DataTableDemoState createState() => _DataTableDemoState();
}
class _DataTableDemoState extends State<DataTableDemo> {
int _rowsPerPage = PaginatedDataTable.defaultRowsPerPage;
int _sortColumnIndex;
bool _sortAscending = true;
_DessertDataSource _dessertsDataSource;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_dessertsDataSource == null) {
_dessertsDataSource = _DessertDataSource(context);
}
}
void _sort<T>(
Comparable<T> getField(_Dessert d), int columnIndex, bool ascending) {
_dessertsDataSource._sort<T>(getField, ascending);
setState(() {
_sortColumnIndex = columnIndex;
_sortAscending = ascending;
});
}
@override
Widget build(BuildContext context) {
final localizations = GalleryLocalizations.of(context);
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(localizations.demoDataTableTitle),
),
body: Scrollbar(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
PaginatedDataTable(
header: Text(localizations.dataTableHeader),
rowsPerPage: _rowsPerPage,
onRowsPerPageChanged: (value) {
setState(() {
_rowsPerPage = value;
});
},
sortColumnIndex: _sortColumnIndex,
sortAscending: _sortAscending,
onSelectAll: _dessertsDataSource._selectAll,
columns: [
DataColumn(
label: Text(localizations.dataTableColumnDessert),
onSort: (columnIndex, ascending) =>
_sort<String>((d) => d.name, columnIndex, ascending),
),
DataColumn(
label: Text(localizations.dataTableColumnCalories),
numeric: true,
onSort: (columnIndex, ascending) =>
_sort<num>((d) => d.calories, columnIndex, ascending),
),
DataColumn(
label: Text(localizations.dataTableColumnFat),
numeric: true,
onSort: (columnIndex, ascending) =>
_sort<num>((d) => d.fat, columnIndex, ascending),
),
DataColumn(
label: Text(localizations.dataTableColumnCarbs),
numeric: true,
onSort: (columnIndex, ascending) =>
_sort<num>((d) => d.carbs, columnIndex, ascending),
),
DataColumn(
label: Text(localizations.dataTableColumnProtein),
numeric: true,
onSort: (columnIndex, ascending) =>
_sort<num>((d) => d.protein, columnIndex, ascending),
),
DataColumn(
label: Text(localizations.dataTableColumnSodium),
numeric: true,
onSort: (columnIndex, ascending) =>
_sort<num>((d) => d.sodium, columnIndex, ascending),
),
DataColumn(
label: Text(localizations.dataTableColumnCalcium),
numeric: true,
onSort: (columnIndex, ascending) =>
_sort<num>((d) => d.calcium, columnIndex, ascending),
),
DataColumn(
label: Text(localizations.dataTableColumnIron),
numeric: true,
onSort: (columnIndex, ascending) =>
_sort<num>((d) => d.iron, columnIndex, ascending),
),
],
source: _dessertsDataSource,
),
],
),
),
);
}
}
class _Dessert {
_Dessert(this.name, this.calories, this.fat, this.carbs, this.protein,
this.sodium, this.calcium, this.iron);
final String name;
final int calories;
final double fat;
final int carbs;
final double protein;
final int sodium;
final int calcium;
final int iron;
bool selected = false;
}
class _DessertDataSource extends DataTableSource {
_DessertDataSource(this.context) {
final localizations = GalleryLocalizations.of(context);
_desserts = <_Dessert>[
_Dessert(
localizations.dataTableRowFrozenYogurt,
159,
6.0,
24,
4.0,
87,
14,
1,
),
_Dessert(
localizations.dataTableRowIceCreamSandwich,
237,
9.0,
37,
4.3,
129,
8,
1,
),
_Dessert(
localizations.dataTableRowEclair,
262,
16.0,
24,
6.0,
337,
6,
7,
),
_Dessert(
localizations.dataTableRowCupcake,
305,
3.7,
67,
4.3,
413,
3,
8,
),
_Dessert(
localizations.dataTableRowGingerbread,
356,
16.0,
49,
3.9,
327,
7,
16,
),
_Dessert(
localizations.dataTableRowJellyBean,
375,
0.0,
94,
0.0,
50,
0,
0,
),
_Dessert(
localizations.dataTableRowLollipop,
392,
0.2,
98,
0.0,
38,
0,
2,
),
_Dessert(
localizations.dataTableRowHoneycomb,
408,
3.2,
87,
6.5,
562,
0,
45,
),
_Dessert(
localizations.dataTableRowDonut,
452,
25.0,
51,
4.9,
326,
2,
22,
),
_Dessert(
localizations.dataTableRowApplePie,
518,
26.0,
65,
7.0,
54,
12,
6,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowFrozenYogurt,
),
168,
6.0,
26,
4.0,
87,
14,
1,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowIceCreamSandwich,
),
246,
9.0,
39,
4.3,
129,
8,
1,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowEclair,
),
271,
16.0,
26,
6.0,
337,
6,
7,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowCupcake,
),
314,
3.7,
69,
4.3,
413,
3,
8,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowGingerbread,
),
345,
16.0,
51,
3.9,
327,
7,
16,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowJellyBean,
),
364,
0.0,
96,
0.0,
50,
0,
0,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowLollipop,
),
401,
0.2,
100,
0.0,
38,
0,
2,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowHoneycomb,
),
417,
3.2,
89,
6.5,
562,
0,
45,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowDonut,
),
461,
25.0,
53,
4.9,
326,
2,
22,
),
_Dessert(
localizations.dataTableRowWithSugar(
localizations.dataTableRowApplePie,
),
527,
26.0,
67,
7.0,
54,
12,
6,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowFrozenYogurt,
),
223,
6.0,
36,
4.0,
87,
14,
1,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowIceCreamSandwich,
),
301,
9.0,
49,
4.3,
129,
8,
1,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowEclair,
),
326,
16.0,
36,
6.0,
337,
6,
7,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowCupcake,
),
369,
3.7,
79,
4.3,
413,
3,
8,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowGingerbread,
),
420,
16.0,
61,
3.9,
327,
7,
16,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowJellyBean,
),
439,
0.0,
106,
0.0,
50,
0,
0,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowLollipop,
),
456,
0.2,
110,
0.0,
38,
0,
2,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowHoneycomb,
),
472,
3.2,
99,
6.5,
562,
0,
45,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowDonut,
),
516,
25.0,
63,
4.9,
326,
2,
22,
),
_Dessert(
localizations.dataTableRowWithHoney(
localizations.dataTableRowApplePie,
),
582,
26.0,
77,
7.0,
54,
12,
6,
),
];
}
final BuildContext context;
List<_Dessert> _desserts;
void _sort<T>(Comparable<T> getField(_Dessert d), bool ascending) {
_desserts.sort((a, b) {
final Comparable<T> aValue = getField(a);
final Comparable<T> bValue = getField(b);
return ascending
? Comparable.compare(aValue, bValue)
: Comparable.compare(bValue, aValue);
});
notifyListeners();
}
int _selectedCount = 0;
@override
DataRow getRow(int index) {
final format = NumberFormat.decimalPercentPattern(
locale: GalleryOptions.of(context).locale.toString(),
decimalDigits: 0,
);
assert(index >= 0);
if (index >= _desserts.length) return null;
final _Dessert dessert = _desserts[index];
return DataRow.byIndex(
index: index,
selected: dessert.selected,
onSelectChanged: (value) {
if (dessert.selected != value) {
_selectedCount += value ? 1 : -1;
assert(_selectedCount >= 0);
dessert.selected = value;
notifyListeners();
}
},
cells: [
DataCell(Text(dessert.name)),
DataCell(Text('${dessert.calories}')),
DataCell(Text(dessert.fat.toStringAsFixed(1))),
DataCell(Text('${dessert.carbs}')),
DataCell(Text(dessert.protein.toStringAsFixed(1))),
DataCell(Text('${dessert.sodium}')),
DataCell(Text('${format.format(dessert.calcium / 100)}')),
DataCell(Text('${format.format(dessert.iron / 100)}')),
],
);
}
@override
int get rowCount => _desserts.length;
@override
bool get isRowCountApproximate => false;
@override
int get selectedRowCount => _selectedCount;
void _selectAll(bool checked) {
for (final _Dessert dessert in _desserts) {
dessert.selected = checked;
}
_selectedCount = checked ? _desserts.length : 0;
notifyListeners();
}
}
// END

View File

@@ -0,0 +1,285 @@
// Copyright 2019 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/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN dialogDemo
enum DialogDemoType {
alert,
alertTitle,
simple,
fullscreen,
}
class DialogDemo extends StatelessWidget {
DialogDemo({Key key, @required this.type}) : super(key: key);
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final DialogDemoType type;
String _title(BuildContext context) {
switch (type) {
case DialogDemoType.alert:
return GalleryLocalizations.of(context).demoAlertDialogTitle;
case DialogDemoType.alertTitle:
return GalleryLocalizations.of(context).demoAlertTitleDialogTitle;
case DialogDemoType.simple:
return GalleryLocalizations.of(context).demoSimpleDialogTitle;
case DialogDemoType.fullscreen:
return GalleryLocalizations.of(context).demoFullscreenDialogTitle;
}
return '';
}
Future<void> _showDemoDialog<T>({BuildContext context, Widget child}) async {
child = ApplyTextOptions(
child: Theme(
data: Theme.of(context),
child: child,
),
);
T value = await showDialog<T>(
context: context,
builder: (context) => child,
);
// The value passed to Navigator.pop() or null.
if (value != null && value is String) {
_scaffoldKey.currentState.hideCurrentSnackBar();
_scaffoldKey.currentState.showSnackBar(SnackBar(
content:
Text(GalleryLocalizations.of(context).dialogSelectedOption(value)),
));
}
}
void _showAlertDialog(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextStyle dialogTextStyle =
theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
_showDemoDialog<String>(
context: context,
child: AlertDialog(
content: Text(
GalleryLocalizations.of(context).dialogDiscardTitle,
style: dialogTextStyle,
),
actions: [
_DialogButton(text: GalleryLocalizations.of(context).dialogCancel),
_DialogButton(text: GalleryLocalizations.of(context).dialogDiscard),
],
),
);
}
void _showAlertDialogWithTitle(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextStyle dialogTextStyle =
theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
_showDemoDialog<String>(
context: context,
child: AlertDialog(
title: Text(GalleryLocalizations.of(context).dialogLocationTitle),
content: Text(
GalleryLocalizations.of(context).dialogLocationDescription,
style: dialogTextStyle,
),
actions: [
_DialogButton(text: GalleryLocalizations.of(context).dialogDisagree),
_DialogButton(text: GalleryLocalizations.of(context).dialogAgree),
],
),
);
}
void _showSimpleDialog(BuildContext context) {
final ThemeData theme = Theme.of(context);
_showDemoDialog<String>(
context: context,
child: SimpleDialog(
title: Text(GalleryLocalizations.of(context).dialogSetBackup),
children: [
_DialogDemoItem(
icon: Icons.account_circle,
color: theme.colorScheme.primary,
text: 'username@gmail.com',
),
_DialogDemoItem(
icon: Icons.account_circle,
color: theme.colorScheme.secondary,
text: 'user02@gmail.com',
),
_DialogDemoItem(
icon: Icons.add_circle,
text: GalleryLocalizations.of(context).dialogAddAccount,
color: theme.disabledColor,
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Navigator(
// Adding [ValueKey] to make sure that the widget gets rebuilt when
// changing type.
key: ValueKey(type),
onGenerateRoute: (settings) {
return _NoAnimationMaterialPageRoute<void>(
builder: (context) => Scaffold(
key: _scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title(context)),
),
body: Center(
child: RaisedButton(
child: Text(GalleryLocalizations.of(context).dialogShow),
onPressed: () {
switch (type) {
case DialogDemoType.alert:
_showAlertDialog(context);
break;
case DialogDemoType.alertTitle:
_showAlertDialogWithTitle(context);
break;
case DialogDemoType.simple:
_showSimpleDialog(context);
break;
case DialogDemoType.fullscreen:
Navigator.push<void>(
context,
MaterialPageRoute(
builder: (context) => _FullScreenDialogDemo(),
fullscreenDialog: true,
),
);
break;
}
},
),
),
),
);
},
);
}
}
/// A MaterialPageRoute without any transition animations.
class _NoAnimationMaterialPageRoute<T> extends MaterialPageRoute<T> {
_NoAnimationMaterialPageRoute({
@required WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
}) : super(
builder: builder,
maintainState: maintainState,
settings: settings,
fullscreenDialog: fullscreenDialog);
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return child;
}
}
class _DialogButton extends StatelessWidget {
const _DialogButton({Key key, this.text}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
return FlatButton(
child: Text(text),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(text);
},
);
}
}
class _DialogDemoItem extends StatelessWidget {
const _DialogDemoItem({
Key key,
this.icon,
this.color,
this.text,
}) : super(key: key);
final IconData icon;
final Color color;
final String text;
@override
Widget build(BuildContext context) {
return SimpleDialogOption(
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(text);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(icon, size: 36, color: color),
Flexible(
child: Padding(
padding: const EdgeInsetsDirectional.only(start: 16),
child: Text(text),
),
),
],
),
);
}
}
class _FullScreenDialogDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
// Remove the MediaQuery padding because the demo is rendered inside of a
// different page that already accounts for this padding.
return MediaQuery.removePadding(
context: context,
removeTop: true,
removeBottom: true,
child: ApplyTextOptions(
child: Scaffold(
appBar: AppBar(
title: Text(GalleryLocalizations.of(context).dialogFullscreenTitle),
actions: [
FlatButton(
child: Text(
GalleryLocalizations.of(context).dialogFullscreenSave,
style: theme.textTheme.body1.copyWith(
color: theme.colorScheme.onPrimary,
),
),
onPressed: () {
Navigator.pop(context);
},
),
],
),
body: Center(
child: Text(
GalleryLocalizations.of(context).dialogFullscreenDescription,
),
),
),
),
);
}
}
// END

View File

@@ -0,0 +1,199 @@
// Copyright 2019 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 gridListsDemo
enum GridListDemoType {
imageOnly,
header,
footer,
}
class GridListDemo extends StatelessWidget {
const GridListDemo({Key key, this.type}) : super(key: key);
final GridListDemoType type;
List<_Photo> _photos(BuildContext context) {
return [
_Photo(
assetName: 'places/india_chennai_flower_market.png',
title: GalleryLocalizations.of(context).placeChennai,
subtitle: GalleryLocalizations.of(context).placeFlowerMarket,
),
_Photo(
assetName: 'places/india_tanjore_bronze_works.png',
title: GalleryLocalizations.of(context).placeTanjore,
subtitle: GalleryLocalizations.of(context).placeBronzeWorks,
),
_Photo(
assetName: 'places/india_tanjore_market_merchant.png',
title: GalleryLocalizations.of(context).placeTanjore,
subtitle: GalleryLocalizations.of(context).placeMarket,
),
_Photo(
assetName: 'places/india_tanjore_thanjavur_temple.png',
title: GalleryLocalizations.of(context).placeTanjore,
subtitle: GalleryLocalizations.of(context).placeThanjavurTemple,
),
_Photo(
assetName: 'places/india_tanjore_thanjavur_temple_carvings.png',
title: GalleryLocalizations.of(context).placeTanjore,
subtitle: GalleryLocalizations.of(context).placeThanjavurTemple,
),
_Photo(
assetName: 'places/india_pondicherry_salt_farm.png',
title: GalleryLocalizations.of(context).placePondicherry,
subtitle: GalleryLocalizations.of(context).placeSaltFarm,
),
_Photo(
assetName: 'places/india_chennai_highway.png',
title: GalleryLocalizations.of(context).placeChennai,
subtitle: GalleryLocalizations.of(context).placeScooters,
),
_Photo(
assetName: 'places/india_chettinad_silk_maker.png',
title: GalleryLocalizations.of(context).placeChettinad,
subtitle: GalleryLocalizations.of(context).placeSilkMaker,
),
_Photo(
assetName: 'places/india_chettinad_produce.png',
title: GalleryLocalizations.of(context).placeChettinad,
subtitle: GalleryLocalizations.of(context).placeLunchPrep,
),
_Photo(
assetName: 'places/india_tanjore_market_technology.png',
title: GalleryLocalizations.of(context).placeTanjore,
subtitle: GalleryLocalizations.of(context).placeMarket,
),
_Photo(
assetName: 'places/india_pondicherry_beach.png',
title: GalleryLocalizations.of(context).placePondicherry,
subtitle: GalleryLocalizations.of(context).placeBeach,
),
_Photo(
assetName: 'places/india_pondicherry_fisherman.png',
title: GalleryLocalizations.of(context).placePondicherry,
subtitle: GalleryLocalizations.of(context).placeFisherman,
),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoGridListsTitle),
),
body: GridView.count(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: const EdgeInsets.all(8),
childAspectRatio: 1,
children: _photos(context).map<Widget>((photo) {
return _GridDemoPhotoItem(
photo: photo,
tileStyle: type,
);
}).toList(),
),
);
}
}
class _Photo {
_Photo({
this.assetName,
this.title,
this.subtitle,
});
final String assetName;
final String title;
final String subtitle;
}
/// Allow the text size to shrink to fit in the space
class _GridTitleText extends StatelessWidget {
const _GridTitleText(this.text);
final String text;
@override
Widget build(BuildContext context) {
return FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: Text(text),
);
}
}
class _GridDemoPhotoItem extends StatelessWidget {
_GridDemoPhotoItem({
Key key,
@required this.photo,
@required this.tileStyle,
}) : super(key: key);
final _Photo photo;
final GridListDemoType tileStyle;
@override
Widget build(BuildContext context) {
final Widget image = Material(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
clipBehavior: Clip.antiAlias,
child: Image.asset(
photo.assetName,
package: 'flutter_gallery_assets',
fit: BoxFit.cover,
),
);
switch (tileStyle) {
case GridListDemoType.imageOnly:
return image;
case GridListDemoType.header:
return GridTile(
header: Material(
color: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(4)),
),
clipBehavior: Clip.antiAlias,
child: GridTileBar(
title: _GridTitleText(photo.title),
backgroundColor: Colors.black45,
),
),
child: image,
);
case GridListDemoType.footer:
return GridTile(
footer: Material(
color: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(4)),
),
clipBehavior: Clip.antiAlias,
child: GridTileBar(
backgroundColor: Colors.black45,
title: _GridTitleText(photo.title),
subtitle: _GridTitleText(photo.subtitle),
),
),
child: image,
);
}
return null;
}
}
// END

View File

@@ -0,0 +1,51 @@
// Copyright 2019 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 listDemo
enum ListDemoType {
oneLine,
twoLine,
}
class ListDemo extends StatelessWidget {
const ListDemo({Key key, this.type}) : super(key: key);
final ListDemoType type;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoListsTitle),
),
body: Scrollbar(
child: ListView(
padding: EdgeInsets.symmetric(vertical: 8),
children: [
for (int index = 1; index < 21; index++)
ListTile(
leading: ExcludeSemantics(
child: CircleAvatar(child: Text('$index')),
),
title: Text(
GalleryLocalizations.of(context).demoBottomSheetItem(index),
),
subtitle: type == ListDemoType.twoLine
? Text(GalleryLocalizations.of(context).demoListsSecondary)
: null,
),
],
),
),
);
}
}
// END

View File

@@ -0,0 +1,368 @@
// Copyright 2019 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';
enum MenuDemoType {
contextMenu,
sectionedMenu,
simpleMenu,
checklistMenu,
}
enum SimpleValue {
one,
two,
three,
}
enum CheckedValue {
one,
two,
three,
four,
}
class MenuDemo extends StatefulWidget {
const MenuDemo({Key key, this.type}) : super(key: key);
final MenuDemoType type;
@override
_MenuDemoState createState() => _MenuDemoState();
}
class _MenuDemoState extends State<MenuDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
void showInSnackBar(String value) {
_scaffoldKey.currentState.hideCurrentSnackBar();
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(value),
));
}
@override
Widget build(BuildContext context) {
Widget demo;
switch (widget.type) {
case MenuDemoType.contextMenu:
demo = _ContextMenuDemo(showInSnackBar: showInSnackBar);
break;
case MenuDemoType.sectionedMenu:
demo = _SectionedMenuDemo(showInSnackBar: showInSnackBar);
break;
case MenuDemoType.simpleMenu:
demo = _SimpleMenuDemo(showInSnackBar: showInSnackBar);
break;
case MenuDemoType.checklistMenu:
demo = _ChecklistMenuDemo(showInSnackBar: showInSnackBar);
break;
}
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(GalleryLocalizations.of(context).demoMenuTitle),
automaticallyImplyLeading: false,
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Center(
child: demo,
),
),
);
}
}
// BEGIN menuDemoContext
// Pressing the PopupMenuButton on the right of this item shows
// a simple menu with one disabled item. Typically the contents
// of this "contextual menu" would reflect the app's state.
class _ContextMenuDemo extends StatelessWidget {
const _ContextMenuDemo({Key key, this.showInSnackBar}) : super(key: key);
final void Function(String value) showInSnackBar;
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(GalleryLocalizations.of(context)
.demoMenuAnItemWithAContextMenuButton),
trailing: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: (value) => showInSnackBar(
GalleryLocalizations.of(context).demoMenuSelected(value),
),
itemBuilder: (context) => <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: GalleryLocalizations.of(context).demoMenuContextMenuItemOne,
child: Text(
GalleryLocalizations.of(context).demoMenuContextMenuItemOne,
),
),
PopupMenuItem<String>(
enabled: false,
child: Text(
GalleryLocalizations.of(context).demoMenuADisabledMenuItem,
),
),
PopupMenuItem<String>(
value:
GalleryLocalizations.of(context).demoMenuContextMenuItemThree,
child: Text(
GalleryLocalizations.of(context).demoMenuContextMenuItemThree,
),
),
],
),
);
}
}
// END
// BEGIN menuDemoSectioned
// Pressing the PopupMenuButton on the right of this item shows
// a menu whose items have text labels and icons and a divider
// That separates the first three items from the last one.
class _SectionedMenuDemo extends StatelessWidget {
const _SectionedMenuDemo({Key key, this.showInSnackBar}) : super(key: key);
final void Function(String value) showInSnackBar;
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(
GalleryLocalizations.of(context).demoMenuAnItemWithASectionedMenu),
trailing: PopupMenuButton<String>(
padding: EdgeInsets.zero,
onSelected: (value) => showInSnackBar(
GalleryLocalizations.of(context).demoMenuSelected(value)),
itemBuilder: (context) => <PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: GalleryLocalizations.of(context).demoMenuPreview,
child: ListTile(
leading: Icon(Icons.visibility),
title: Text(
GalleryLocalizations.of(context).demoMenuPreview,
),
),
),
PopupMenuItem<String>(
value: GalleryLocalizations.of(context).demoMenuShare,
child: ListTile(
leading: Icon(Icons.person_add),
title: Text(
GalleryLocalizations.of(context).demoMenuShare,
),
),
),
PopupMenuItem<String>(
value: GalleryLocalizations.of(context).demoMenuGetLink,
child: ListTile(
leading: Icon(Icons.link),
title: Text(
GalleryLocalizations.of(context).demoMenuGetLink,
),
),
),
const PopupMenuDivider(),
PopupMenuItem<String>(
value: GalleryLocalizations.of(context).demoMenuRemove,
child: ListTile(
leading: Icon(Icons.delete),
title: Text(
GalleryLocalizations.of(context).demoMenuRemove,
),
),
),
],
),
);
}
}
// END
// BEGIN menuDemoSimple
// This entire list item is a PopupMenuButton. Tapping anywhere shows
// a menu whose current value is highlighted and aligned over the
// list item's center line.
class _SimpleMenuDemo extends StatefulWidget {
const _SimpleMenuDemo({Key key, this.showInSnackBar}) : super(key: key);
final void Function(String value) showInSnackBar;
@override
_SimpleMenuDemoState createState() => _SimpleMenuDemoState();
}
class _SimpleMenuDemoState extends State<_SimpleMenuDemo> {
SimpleValue _simpleValue;
void showAndSetMenuSelection(BuildContext context, SimpleValue value) {
setState(() {
_simpleValue = value;
});
widget.showInSnackBar(
GalleryLocalizations.of(context)
.demoMenuSelected(simpleValueToString(context, value)),
);
}
String simpleValueToString(BuildContext context, SimpleValue value) => {
SimpleValue.one: GalleryLocalizations.of(context).demoMenuItemValueOne,
SimpleValue.two: GalleryLocalizations.of(context).demoMenuItemValueTwo,
SimpleValue.three:
GalleryLocalizations.of(context).demoMenuItemValueThree,
}[value];
@override
void initState() {
super.initState();
_simpleValue = SimpleValue.two;
}
@override
Widget build(BuildContext context) {
return PopupMenuButton<SimpleValue>(
padding: EdgeInsets.zero,
initialValue: _simpleValue,
onSelected: (value) => showAndSetMenuSelection(context, value),
child: ListTile(
title: Text(
GalleryLocalizations.of(context).demoMenuAnItemWithASimpleMenu),
subtitle: Text(simpleValueToString(context, _simpleValue)),
),
itemBuilder: (context) => <PopupMenuItem<SimpleValue>>[
PopupMenuItem<SimpleValue>(
value: SimpleValue.one,
child: Text(simpleValueToString(
context,
SimpleValue.one,
)),
),
PopupMenuItem<SimpleValue>(
value: SimpleValue.two,
child: Text(simpleValueToString(
context,
SimpleValue.two,
)),
),
PopupMenuItem<SimpleValue>(
value: SimpleValue.three,
child: Text(simpleValueToString(
context,
SimpleValue.three,
)),
),
],
);
}
}
// END
// BEGIN menuDemoChecklist
// Pressing the PopupMenuButton on the right of this item shows a menu
// whose items have checked icons that reflect this app's state.
class _ChecklistMenuDemo extends StatefulWidget {
const _ChecklistMenuDemo({Key key, this.showInSnackBar}) : super(key: key);
final void Function(String value) showInSnackBar;
@override
_ChecklistMenuDemoState createState() => _ChecklistMenuDemoState();
}
class _ChecklistMenuDemoState extends State<_ChecklistMenuDemo> {
List<CheckedValue> _checkedValues;
@override
void initState() {
super.initState();
_checkedValues = [CheckedValue.three];
}
void showCheckedMenuSelections(BuildContext context, CheckedValue value) {
if (_checkedValues.contains(value)) {
setState(() {
_checkedValues.remove(value);
});
} else {
setState(() {
_checkedValues.add(value);
});
}
widget.showInSnackBar(
GalleryLocalizations.of(context).demoMenuChecked(
_checkedValues.map((value) => checkedValueToString(context, value)),
),
);
}
String checkedValueToString(BuildContext context, CheckedValue value) => {
CheckedValue.one: GalleryLocalizations.of(context).demoMenuOne,
CheckedValue.two: GalleryLocalizations.of(context).demoMenuTwo,
CheckedValue.three: GalleryLocalizations.of(context).demoMenuThree,
CheckedValue.four: GalleryLocalizations.of(context).demoMenuFour,
}[value];
bool isChecked(CheckedValue value) => _checkedValues.contains(value);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(
GalleryLocalizations.of(context).demoMenuAnItemWithAChecklistMenu),
trailing: PopupMenuButton<CheckedValue>(
padding: EdgeInsets.zero,
onSelected: (value) => showCheckedMenuSelections(context, value),
itemBuilder: (context) => <PopupMenuItem<CheckedValue>>[
CheckedPopupMenuItem<CheckedValue>(
value: CheckedValue.one,
checked: isChecked(CheckedValue.one),
child: Text(
checkedValueToString(context, CheckedValue.one),
),
),
CheckedPopupMenuItem<CheckedValue>(
value: CheckedValue.two,
enabled: false,
checked: isChecked(CheckedValue.two),
child: Text(
checkedValueToString(context, CheckedValue.two),
),
),
CheckedPopupMenuItem<CheckedValue>(
value: CheckedValue.three,
checked: isChecked(CheckedValue.three),
child: Text(
checkedValueToString(context, CheckedValue.three),
),
),
CheckedPopupMenuItem<CheckedValue>(
value: CheckedValue.four,
checked: isChecked(CheckedValue.four),
child: Text(
checkedValueToString(context, CheckedValue.four),
),
),
],
),
);
}
}
// END

View File

@@ -0,0 +1,112 @@
// Copyright 2019 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 'dart:async';
import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:intl/intl.dart';
// BEGIN pickerDemo
enum PickerDemoType {
date,
time,
}
class PickerDemo extends StatefulWidget {
const PickerDemo({Key key, this.type}) : super(key: key);
final PickerDemoType type;
@override
_PickerDemoState createState() => _PickerDemoState();
}
class _PickerDemoState extends State<PickerDemo> {
DateTime _fromDate = DateTime.now();
TimeOfDay _fromTime = TimeOfDay.fromDateTime(DateTime.now());
String get _title {
switch (widget.type) {
case PickerDemoType.date:
return GalleryLocalizations.of(context).demoDatePickerTitle;
case PickerDemoType.time:
return GalleryLocalizations.of(context).demoTimePickerTitle;
}
return '';
}
String get _labelText {
switch (widget.type) {
case PickerDemoType.date:
return DateFormat.yMMMd().format(_fromDate);
case PickerDemoType.time:
return _fromTime.format(context);
}
return '';
}
Future<void> _showDatePicker() async {
final picked = await showDatePicker(
context: context,
initialDate: _fromDate,
firstDate: DateTime(2015, 1),
lastDate: DateTime(2100),
);
if (picked != null && picked != _fromDate) {
setState(() {
_fromDate = picked;
});
}
}
Future<void> _showTimePicker() async {
final TimeOfDay picked = await showTimePicker(
context: context,
initialTime: _fromTime,
);
if (picked != null && picked != _fromTime) {
setState(() {
_fromTime = picked;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(_labelText),
SizedBox(height: 16),
RaisedButton(
child: Text(
GalleryLocalizations.of(context).demoPickersShowPicker,
),
onPressed: () {
switch (widget.type) {
case PickerDemoType.date:
_showDatePicker();
break;
case PickerDemoType.time:
_showTimePicker();
break;
}
},
)
],
),
),
);
}
}
// END

View File

@@ -0,0 +1,114 @@
// Copyright 2019 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 progressIndicatorsDemo
enum ProgressIndicatorDemoType {
circular,
linear,
}
class ProgressIndicatorDemo extends StatefulWidget {
const ProgressIndicatorDemo({Key key, this.type}) : super(key: key);
final ProgressIndicatorDemoType type;
@override
_ProgressIndicatorDemoState createState() => _ProgressIndicatorDemoState();
}
class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
animationBehavior: AnimationBehavior.preserve,
)..forward();
_animation = CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.9, curve: Curves.fastOutSlowIn),
reverseCurve: Curves.fastOutSlowIn,
)..addStatusListener((status) {
if (status == AnimationStatus.dismissed) {
_controller.forward();
} else if (status == AnimationStatus.completed) {
_controller.reverse();
}
});
}
@override
void dispose() {
_controller.stop();
super.dispose();
}
String get _title {
switch (widget.type) {
case ProgressIndicatorDemoType.circular:
return GalleryLocalizations.of(context)
.demoCircularProgressIndicatorTitle;
case ProgressIndicatorDemoType.linear:
return GalleryLocalizations.of(context)
.demoLinearProgressIndicatorTitle;
}
return '';
}
Widget _buildIndicators(BuildContext context, Widget child) {
switch (widget.type) {
case ProgressIndicatorDemoType.circular:
return Column(
children: [
const CircularProgressIndicator(),
SizedBox(height: 32),
CircularProgressIndicator(value: _animation.value),
],
);
case ProgressIndicatorDemoType.linear:
return Column(
children: [
const LinearProgressIndicator(),
SizedBox(height: 32),
LinearProgressIndicator(value: _animation.value),
],
);
default:
return Container();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title),
),
body: Center(
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(8),
child: AnimatedBuilder(
animation: _animation,
builder: _buildIndicators,
),
),
),
),
);
}
}
// END

View File

@@ -0,0 +1,176 @@
// Copyright 2019 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';
enum SelectionControlsDemoType {
checkbox,
radio,
switches,
}
class SelectionControlsDemo extends StatelessWidget {
SelectionControlsDemo({Key key, @required this.type}) : super(key: key);
final SelectionControlsDemoType type;
String _title(BuildContext context) {
switch (type) {
case SelectionControlsDemoType.checkbox:
return GalleryLocalizations.of(context)
.demoSelectionControlsCheckboxTitle;
case SelectionControlsDemoType.radio:
return GalleryLocalizations.of(context).demoSelectionControlsRadioTitle;
case SelectionControlsDemoType.switches:
return GalleryLocalizations.of(context)
.demoSelectionControlsSwitchTitle;
}
return '';
}
@override
Widget build(BuildContext context) {
Widget controls;
switch (type) {
case SelectionControlsDemoType.checkbox:
controls = _CheckboxDemo();
break;
case SelectionControlsDemoType.radio:
controls = _RadioDemo();
break;
case SelectionControlsDemoType.switches:
controls = _SwitchDemo();
break;
}
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title(context)),
),
body: controls,
);
}
}
// BEGIN selectionControlsDemoCheckbox
class _CheckboxDemo extends StatefulWidget {
@override
_CheckboxDemoState createState() => _CheckboxDemoState();
}
class _CheckboxDemoState extends State<_CheckboxDemo> {
bool checkboxValueA = true;
bool checkboxValueB = false;
bool checkboxValueC;
@override
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(
value: checkboxValueA,
onChanged: (value) {
setState(() {
checkboxValueA = value;
});
},
),
Checkbox(
value: checkboxValueB,
onChanged: (value) {
setState(() {
checkboxValueB = value;
});
},
),
Checkbox(
value: checkboxValueC,
tristate: true,
onChanged: (value) {
setState(() {
checkboxValueC = value;
});
},
),
],
),
);
}
}
// END
// BEGIN selectionControlsDemoRadio
class _RadioDemo extends StatefulWidget {
@override
_RadioDemoState createState() => _RadioDemoState();
}
class _RadioDemoState extends State<_RadioDemo> {
int radioValue = 0;
void handleRadioValueChanged(int value) {
setState(() {
radioValue = value;
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
for (int index = 0; index < 3; ++index)
Radio<int>(
value: index,
groupValue: radioValue,
onChanged: handleRadioValueChanged,
),
],
),
);
}
}
// END
// BEGIN selectionControlsDemoSwitches
class _SwitchDemo extends StatefulWidget {
@override
_SwitchDemoState createState() => _SwitchDemoState();
}
class _SwitchDemoState extends State<_SwitchDemo> {
bool switchValue = false;
@override
Widget build(BuildContext context) {
return Center(
child: Semantics(
container: true,
label:
GalleryLocalizations.of(context).demoSelectionControlsSwitchTitle,
child: Switch(
value: switchValue,
onChanged: (value) {
setState(() {
switchValue = value;
});
},
),
),
);
}
}
// END

View File

@@ -0,0 +1,503 @@
// Copyright 2019 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 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
enum SlidersDemoType {
sliders,
rangeSliders,
customSliders,
}
class SlidersDemo extends StatelessWidget {
const SlidersDemo({Key key, this.type}) : super(key: key);
final SlidersDemoType type;
String _title(BuildContext context) {
switch (type) {
case SlidersDemoType.sliders:
return GalleryLocalizations.of(context).demoSlidersTitle;
case SlidersDemoType.rangeSliders:
return GalleryLocalizations.of(context).demoRangeSlidersTitle;
case SlidersDemoType.customSliders:
return GalleryLocalizations.of(context).demoCustomSlidersTitle;
}
return '';
}
@override
Widget build(BuildContext context) {
Widget sliders;
switch (type) {
case SlidersDemoType.sliders:
sliders = _Sliders();
break;
case SlidersDemoType.rangeSliders:
sliders = _RangeSliders();
break;
case SlidersDemoType.customSliders:
sliders = _CustomSliders();
}
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_title(context)),
),
body: sliders,
);
}
}
// BEGIN slidersDemo
class _Sliders extends StatefulWidget {
@override
_SlidersState createState() => _SlidersState();
}
class _SlidersState extends State<_Sliders> {
double _continuousValue = 25;
double _discreteValue = 20;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Semantics(
label: GalleryLocalizations.of(context)
.demoSlidersEditableNumericalValue,
child: SizedBox(
width: 64,
height: 48,
child: TextField(
textAlign: TextAlign.center,
onSubmitted: (value) {
final double newValue = double.tryParse(value);
if (newValue != null && newValue != _continuousValue) {
setState(() {
_continuousValue = newValue.clamp(0, 100) as double;
});
}
},
keyboardType: TextInputType.number,
controller: TextEditingController(
text: _continuousValue.toStringAsFixed(0),
),
),
),
),
Slider(
value: _continuousValue,
min: 0,
max: 100,
onChanged: (value) {
setState(() {
_continuousValue = value;
});
},
),
Text(GalleryLocalizations.of(context)
.demoSlidersContinuousWithEditableNumericalValue),
],
),
const SizedBox(height: 80),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Slider(
value: _discreteValue,
min: 0,
max: 200,
divisions: 5,
label: _discreteValue.round().toString(),
onChanged: (value) {
setState(() {
_discreteValue = value;
});
},
),
Text(GalleryLocalizations.of(context).demoSlidersDiscrete),
],
),
],
),
);
}
}
// END
// BEGIN rangeSlidersDemo
class _RangeSliders extends StatefulWidget {
@override
_RangeSlidersState createState() => _RangeSlidersState();
}
class _RangeSlidersState extends State<_RangeSliders> {
RangeValues _continuousValues = const RangeValues(25, 75);
RangeValues _discreteValues = const RangeValues(40, 120);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
RangeSlider(
values: _continuousValues,
min: 0,
max: 100,
onChanged: (values) {
setState(() {
_continuousValues = values;
});
},
),
Text(GalleryLocalizations.of(context).demoSlidersContinuous),
],
),
const SizedBox(height: 80),
Column(
mainAxisSize: MainAxisSize.min,
children: [
RangeSlider(
values: _discreteValues,
min: 0,
max: 200,
divisions: 5,
labels: RangeLabels(
_discreteValues.start.round().toString(),
_discreteValues.end.round().toString(),
),
onChanged: (values) {
setState(() {
_discreteValues = values;
});
},
),
Text(GalleryLocalizations.of(context).demoSlidersDiscrete),
],
),
],
),
);
}
}
// END
// BEGIN customSlidersDemo
Path _downTriangle(double size, Offset thumbCenter, {bool invert = false}) {
final thumbPath = Path();
final height = math.sqrt(3) / 2;
final centerHeight = size * height / 3;
final halfSize = size / 2;
final sign = invert ? -1 : 1;
thumbPath.moveTo(
thumbCenter.dx - halfSize, thumbCenter.dy + sign * centerHeight);
thumbPath.lineTo(thumbCenter.dx, thumbCenter.dy - 2 * sign * centerHeight);
thumbPath.lineTo(
thumbCenter.dx + halfSize, thumbCenter.dy + sign * centerHeight);
thumbPath.close();
return thumbPath;
}
Path _rightTriangle(double size, Offset thumbCenter, {bool invert = false}) {
final thumbPath = Path();
final halfSize = size / 2;
final sign = invert ? -1 : 1;
thumbPath.moveTo(thumbCenter.dx + halfSize * sign, thumbCenter.dy);
thumbPath.lineTo(thumbCenter.dx - halfSize * sign, thumbCenter.dy - size);
thumbPath.lineTo(thumbCenter.dx - halfSize * sign, thumbCenter.dy + size);
thumbPath.close();
return thumbPath;
}
Path _upTriangle(double size, Offset thumbCenter) =>
_downTriangle(size, thumbCenter, invert: true);
Path _leftTriangle(double size, Offset thumbCenter) =>
_rightTriangle(size, thumbCenter, invert: true);
class _CustomRangeThumbShape extends RangeSliderThumbShape {
static const double _thumbSize = 4;
static const double _disabledThumbSize = 3;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return isEnabled
? const Size.fromRadius(_thumbSize)
: const Size.fromRadius(_disabledThumbSize);
}
static final Animatable<double> sizeTween = Tween<double>(
begin: _disabledThumbSize,
end: _thumbSize,
);
@override
void paint(
PaintingContext context,
Offset center, {
@required Animation<double> activationAnimation,
@required Animation<double> enableAnimation,
bool isDiscrete = false,
bool isEnabled = false,
bool isOnTop,
@required SliderThemeData sliderTheme,
TextDirection textDirection,
Thumb thumb,
}) {
final canvas = context.canvas;
final colorTween = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.thumbColor,
);
final size = _thumbSize * sizeTween.evaluate(enableAnimation);
Path thumbPath;
switch (textDirection) {
case TextDirection.rtl:
switch (thumb) {
case Thumb.start:
thumbPath = _rightTriangle(size, center);
break;
case Thumb.end:
thumbPath = _leftTriangle(size, center);
break;
}
break;
case TextDirection.ltr:
switch (thumb) {
case Thumb.start:
thumbPath = _leftTriangle(size, center);
break;
case Thumb.end:
thumbPath = _rightTriangle(size, center);
break;
}
break;
}
canvas.drawPath(
thumbPath,
Paint()..color = colorTween.evaluate(enableAnimation),
);
}
}
class _CustomThumbShape extends SliderComponentShape {
static const double _thumbSize = 4;
static const double _disabledThumbSize = 3;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return isEnabled
? const Size.fromRadius(_thumbSize)
: const Size.fromRadius(_disabledThumbSize);
}
static final Animatable<double> sizeTween = Tween<double>(
begin: _disabledThumbSize,
end: _thumbSize,
);
@override
void paint(
PaintingContext context,
Offset thumbCenter, {
Animation<double> activationAnimation,
Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
}) {
final canvas = context.canvas;
final colorTween = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.thumbColor,
);
final size = _thumbSize * sizeTween.evaluate(enableAnimation);
final thumbPath = _downTriangle(size, thumbCenter);
canvas.drawPath(
thumbPath,
Paint()..color = colorTween.evaluate(enableAnimation),
);
}
}
class _CustomValueIndicatorShape extends SliderComponentShape {
static const double _indicatorSize = 4;
static const double _disabledIndicatorSize = 3;
static const double _slideUpHeight = 40;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);
}
static final Animatable<double> sizeTween = Tween<double>(
begin: _disabledIndicatorSize,
end: _indicatorSize,
);
@override
void paint(
PaintingContext context,
Offset thumbCenter, {
Animation<double> activationAnimation,
Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
}) {
final canvas = context.canvas;
final enableColor = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.valueIndicatorColor,
);
final slideUpTween = Tween<double>(
begin: 0,
end: _slideUpHeight,
);
final size = _indicatorSize * sizeTween.evaluate(enableAnimation);
final slideUpOffset =
Offset(0, -slideUpTween.evaluate(activationAnimation));
final thumbPath = _upTriangle(size, thumbCenter + slideUpOffset);
final paintColor = enableColor
.evaluate(enableAnimation)
.withAlpha((255 * activationAnimation.value).round());
canvas.drawPath(
thumbPath,
Paint()..color = paintColor,
);
canvas.drawLine(
thumbCenter,
thumbCenter + slideUpOffset,
Paint()
..color = paintColor
..style = PaintingStyle.stroke
..strokeWidth = 2);
labelPainter.paint(
canvas,
thumbCenter +
slideUpOffset +
Offset(-labelPainter.width / 2, -labelPainter.height - 4),
);
}
}
class _CustomSliders extends StatefulWidget {
@override
_CustomSlidersState createState() => _CustomSlidersState();
}
class _CustomSlidersState extends State<_CustomSliders> {
double _discreteCustomValue = 25;
RangeValues _continuousCustomValues = const RangeValues(40, 160);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
SliderTheme(
data: theme.sliderTheme.copyWith(
activeTrackColor: Colors.deepPurple,
inactiveTrackColor:
theme.colorScheme.onSurface.withOpacity(0.5),
activeTickMarkColor:
theme.colorScheme.onSurface.withOpacity(0.7),
inactiveTickMarkColor:
theme.colorScheme.surface.withOpacity(0.7),
overlayColor: theme.colorScheme.onSurface.withOpacity(0.12),
thumbColor: Colors.deepPurple,
valueIndicatorColor: Colors.deepPurpleAccent,
thumbShape: _CustomThumbShape(),
valueIndicatorShape: _CustomValueIndicatorShape(),
valueIndicatorTextStyle: theme.accentTextTheme.body2
.copyWith(color: theme.colorScheme.onSurface),
),
child: Slider(
value: _discreteCustomValue,
min: 0,
max: 200,
divisions: 5,
semanticFormatterCallback: (value) =>
value.round().toString(),
label: '${_discreteCustomValue.round()}',
onChanged: (value) {
setState(() {
_discreteCustomValue = value;
});
},
),
),
Text(GalleryLocalizations.of(context)
.demoSlidersDiscreteSliderWithCustomTheme),
],
),
const SizedBox(height: 80),
Column(
mainAxisSize: MainAxisSize.min,
children: [
SliderTheme(
data: SliderThemeData(
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: Colors.black26,
activeTickMarkColor: Colors.white70,
inactiveTickMarkColor: Colors.black,
overlayColor: Colors.black12,
thumbColor: Colors.deepPurple,
rangeThumbShape: _CustomRangeThumbShape(),
showValueIndicator: ShowValueIndicator.never,
),
child: RangeSlider(
values: _continuousCustomValues,
min: 0,
max: 200,
onChanged: (values) {
setState(() {
_continuousCustomValues = values;
});
},
),
),
Text(GalleryLocalizations.of(context)
.demoSlidersContinuousRangeSliderWithCustomTheme),
],
),
],
),
);
}
}
// END

View File

@@ -0,0 +1,54 @@
// Copyright 2019 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 snackbarsDemo
class SnackbarsDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoSnackbarsTitle),
),
body: Builder(
// Create an inner BuildContext so that the snackBar onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: (context) {
return Center(
child: RaisedButton(
child: Text(
GalleryLocalizations.of(context).demoSnackbarsButtonLabel),
onPressed: () {
Scaffold.of(context).hideCurrentSnackBar();
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
GalleryLocalizations.of(context).demoSnackbarsText,
),
action: SnackBarAction(
label: GalleryLocalizations.of(context)
.demoSnackbarsActionButtonLabel,
onPressed: () {
Scaffold.of(context).hideCurrentSnackBar();
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
GalleryLocalizations.of(context).demoSnackbarsAction,
),
));
},
),
));
},
),
);
},
),
);
}
}
// END

View File

@@ -0,0 +1,118 @@
// Copyright 2019 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';
enum TabsDemoType {
scrollable,
nonScrollable,
}
class TabsDemo extends StatelessWidget {
const TabsDemo({Key key, this.type}) : super(key: key);
final TabsDemoType type;
@override
Widget build(BuildContext context) {
Widget tabs;
switch (type) {
case TabsDemoType.scrollable:
tabs = _TabsScrollableDemo();
break;
case TabsDemoType.nonScrollable:
tabs = _TabsNonScrollableDemo();
}
return tabs;
}
}
// BEGIN tabsScrollableDemo
class _TabsScrollableDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
List<String> tabs = [
GalleryLocalizations.of(context).colorsRed,
GalleryLocalizations.of(context).colorsOrange,
GalleryLocalizations.of(context).colorsGreen,
GalleryLocalizations.of(context).colorsBlue,
GalleryLocalizations.of(context).colorsIndigo,
GalleryLocalizations.of(context).colorsPurple,
GalleryLocalizations.of(context).colorsRed,
GalleryLocalizations.of(context).colorsOrange,
GalleryLocalizations.of(context).colorsGreen,
GalleryLocalizations.of(context).colorsBlue,
GalleryLocalizations.of(context).colorsIndigo,
GalleryLocalizations.of(context).colorsPurple,
];
return DefaultTabController(
length: tabs.length,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoTabsScrollingTitle),
bottom: TabBar(
isScrollable: true,
tabs: [
for (final tab in tabs) Tab(text: tab),
],
),
),
body: TabBarView(
children: [
for (final tab in tabs)
Center(
child: Text(tab),
),
],
),
),
);
}
}
// END
// BEGIN tabsNonScrollableDemo
class _TabsNonScrollableDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
List<String> tabs = [
GalleryLocalizations.of(context).colorsRed,
GalleryLocalizations.of(context).colorsOrange,
GalleryLocalizations.of(context).colorsGreen,
];
return DefaultTabController(
length: tabs.length,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title:
Text(GalleryLocalizations.of(context).demoTabsNonScrollingTitle),
bottom: TabBar(
isScrollable: false,
tabs: [
for (final tab in tabs) Tab(text: tab),
],
),
),
body: TabBarView(
children: [
for (final tab in tabs)
Center(
child: Text(tab),
),
],
),
),
);
}
}
// END

View File

@@ -0,0 +1,357 @@
// Copyright 2019 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:flutter/services.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:gallery/l10n/gallery_localizations.dart';
// BEGIN textFieldDemo
class TextFieldDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoTextFieldTitle),
),
body: TextFormFieldDemo(),
);
}
}
class TextFormFieldDemo extends StatefulWidget {
const TextFormFieldDemo({Key key}) : super(key: key);
@override
TextFormFieldDemoState createState() => TextFormFieldDemoState();
}
class PersonData {
String name = '';
String phoneNumber = '';
String email = '';
String password = '';
}
class PasswordField extends StatefulWidget {
const PasswordField({
this.fieldKey,
this.hintText,
this.labelText,
this.helperText,
this.onSaved,
this.validator,
this.onFieldSubmitted,
});
final Key fieldKey;
final String hintText;
final String labelText;
final String helperText;
final FormFieldSetter<String> onSaved;
final FormFieldValidator<String> validator;
final ValueChanged<String> onFieldSubmitted;
@override
_PasswordFieldState createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State<PasswordField> {
bool _obscureText = true;
@override
Widget build(BuildContext context) {
return TextFormField(
key: widget.fieldKey,
obscureText: _obscureText,
cursorColor: Theme.of(context).cursorColor,
maxLength: 8,
onSaved: widget.onSaved,
validator: widget.validator,
onFieldSubmitted: widget.onFieldSubmitted,
decoration: InputDecoration(
filled: true,
hintText: widget.hintText,
labelText: widget.labelText,
helperText: widget.helperText,
suffixIcon: GestureDetector(
dragStartBehavior: DragStartBehavior.down,
onTap: () {
setState(() {
_obscureText = !_obscureText;
});
},
child: Icon(
_obscureText ? Icons.visibility : Icons.visibility_off,
semanticLabel: _obscureText
? GalleryLocalizations.of(context)
.demoTextFieldShowPasswordLabel
: GalleryLocalizations.of(context)
.demoTextFieldHidePasswordLabel,
),
),
),
);
}
}
class TextFormFieldDemoState extends State<TextFormFieldDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
PersonData person = PersonData();
void showInSnackBar(String value) {
_scaffoldKey.currentState.hideCurrentSnackBar();
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(value),
));
}
bool _autoValidate = false;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final GlobalKey<FormFieldState<String>> _passwordFieldKey =
GlobalKey<FormFieldState<String>>();
final _UsNumberTextInputFormatter _phoneNumberFormatter =
_UsNumberTextInputFormatter();
void _handleSubmitted() {
final form = _formKey.currentState;
if (!form.validate()) {
_autoValidate = true; // Start validating on every change.
showInSnackBar(
GalleryLocalizations.of(context).demoTextFieldFormErrors,
);
} else {
form.save();
showInSnackBar(GalleryLocalizations.of(context)
.demoTextFieldNameHasPhoneNumber(person.name, person.phoneNumber));
}
}
String _validateName(String value) {
if (value.isEmpty) {
return GalleryLocalizations.of(context).demoTextFieldNameRequired;
}
final nameExp = RegExp(r'^[A-Za-z ]+$');
if (!nameExp.hasMatch(value)) {
return GalleryLocalizations.of(context)
.demoTextFieldOnlyAlphabeticalChars;
}
return null;
}
String _validatePhoneNumber(String value) {
final phoneExp = RegExp(r'^\(\d\d\d\) \d\d\d\-\d\d\d\d$');
if (!phoneExp.hasMatch(value)) {
return GalleryLocalizations.of(context).demoTextFieldEnterUSPhoneNumber;
}
return null;
}
String _validatePassword(String value) {
final passwordField = _passwordFieldKey.currentState;
if (passwordField.value == null || passwordField.value.isEmpty) {
return GalleryLocalizations.of(context).demoTextFieldEnterPassword;
}
if (passwordField.value != value) {
return GalleryLocalizations.of(context).demoTextFieldPasswordsDoNotMatch;
}
return null;
}
@override
Widget build(BuildContext context) {
final cursorColor = Theme.of(context).cursorColor;
const sizedBoxSpace = SizedBox(height: 24);
return Scaffold(
key: _scaffoldKey,
body: Form(
key: _formKey,
autovalidate: _autoValidate,
child: Scrollbar(
child: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
sizedBoxSpace,
TextFormField(
textCapitalization: TextCapitalization.words,
cursorColor: cursorColor,
decoration: InputDecoration(
filled: true,
icon: Icon(Icons.person),
hintText: GalleryLocalizations.of(context)
.demoTextFieldWhatDoPeopleCallYou,
labelText:
GalleryLocalizations.of(context).demoTextFieldNameField,
),
onSaved: (value) {
person.name = value;
},
validator: _validateName,
),
sizedBoxSpace,
TextFormField(
cursorColor: cursorColor,
decoration: InputDecoration(
filled: true,
icon: Icon(Icons.phone),
hintText: GalleryLocalizations.of(context)
.demoTextFieldWhereCanWeReachYou,
labelText: GalleryLocalizations.of(context)
.demoTextFieldPhoneNumber,
prefixText: '+1 ',
),
keyboardType: TextInputType.phone,
onSaved: (value) {
person.phoneNumber = value;
},
maxLength: 14,
maxLengthEnforced: false,
validator: _validatePhoneNumber,
// TextInputFormatters are applied in sequence.
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly,
// Fit the validating format.
_phoneNumberFormatter,
],
),
sizedBoxSpace,
TextFormField(
cursorColor: cursorColor,
decoration: InputDecoration(
filled: true,
icon: Icon(Icons.email),
hintText: GalleryLocalizations.of(context)
.demoTextFieldYourEmailAddress,
labelText:
GalleryLocalizations.of(context).demoTextFieldEmail,
),
keyboardType: TextInputType.emailAddress,
onSaved: (value) {
person.email = value;
},
),
sizedBoxSpace,
TextFormField(
cursorColor: cursorColor,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: GalleryLocalizations.of(context)
.demoTextFieldTellUsAboutYourself,
helperText: GalleryLocalizations.of(context)
.demoTextFieldKeepItShort,
labelText:
GalleryLocalizations.of(context).demoTextFieldLifeStory,
),
maxLines: 3,
),
sizedBoxSpace,
TextFormField(
cursorColor: cursorColor,
keyboardType: TextInputType.number,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
GalleryLocalizations.of(context).demoTextFieldSalary,
suffixText:
GalleryLocalizations.of(context).demoTextFieldUSD,
),
maxLines: 1,
),
sizedBoxSpace,
PasswordField(
fieldKey: _passwordFieldKey,
helperText:
GalleryLocalizations.of(context).demoTextFieldNoMoreThan,
labelText:
GalleryLocalizations.of(context).demoTextFieldPassword,
onFieldSubmitted: (value) {
setState(() {
person.password = value;
});
},
),
sizedBoxSpace,
TextFormField(
cursorColor: cursorColor,
decoration: InputDecoration(
filled: true,
labelText: GalleryLocalizations.of(context)
.demoTextFieldRetypePassword,
),
maxLength: 8,
obscureText: true,
validator: _validatePassword,
),
sizedBoxSpace,
Center(
child: RaisedButton(
child: Text(
GalleryLocalizations.of(context).demoTextFieldSubmit),
onPressed: _handleSubmitted,
),
),
sizedBoxSpace,
Text(
GalleryLocalizations.of(context).demoTextFieldRequiredField,
style: Theme.of(context).textTheme.caption,
),
sizedBoxSpace,
],
),
),
),
),
);
}
}
/// Format incoming numeric text to fit the format of (###) ###-#### ##
class _UsNumberTextInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final newTextLength = newValue.text.length;
final newText = StringBuffer();
int selectionIndex = newValue.selection.end;
int usedSubstringIndex = 0;
if (newTextLength >= 1) {
newText.write('(');
if (newValue.selection.end >= 1) selectionIndex++;
}
if (newTextLength >= 4) {
newText.write(newValue.text.substring(0, usedSubstringIndex = 3) + ') ');
if (newValue.selection.end >= 3) selectionIndex += 2;
}
if (newTextLength >= 7) {
newText.write(newValue.text.substring(3, usedSubstringIndex = 6) + '-');
if (newValue.selection.end >= 6) selectionIndex++;
}
if (newTextLength >= 11) {
newText.write(newValue.text.substring(6, usedSubstringIndex = 10) + ' ');
if (newValue.selection.end >= 10) selectionIndex++;
}
// Dump the rest.
if (newTextLength >= usedSubstringIndex) {
newText.write(newValue.text.substring(usedSubstringIndex));
}
return TextEditingValue(
text: newText.toString(),
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}
// END

View File

@@ -0,0 +1,46 @@
// Copyright 2019 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 tooltipDemo
class TooltipDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoTooltipTitle),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
GalleryLocalizations.of(context).demoTooltipInstructions,
textAlign: TextAlign.center,
),
SizedBox(height: 16),
Tooltip(
message:
GalleryLocalizations.of(context).starterAppTooltipSearch,
child: IconButton(
color: Theme.of(context).colorScheme.primary,
onPressed: () {},
icon: Icon(Icons.search),
),
),
],
),
),
),
);
}
}
// END

View File

@@ -0,0 +1,265 @@
// Copyright 2019 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 colorsDemo
const double kColorItemHeight = 48;
class _Palette {
_Palette({
this.name,
this.primary,
this.accent,
this.threshold = 900,
}) : assert(name != null),
assert(primary != null),
assert(threshold != null);
final String name;
final MaterialColor primary;
final MaterialAccentColor accent;
// Titles for indices > threshold are white, otherwise black.
final int threshold;
}
List<_Palette> _allPalettes(BuildContext context) {
return [
_Palette(
name: GalleryLocalizations.of(context).colorsRed,
primary: Colors.red,
accent: Colors.redAccent,
threshold: 300,
),
_Palette(
name: GalleryLocalizations.of(context).colorsPink,
primary: Colors.pink,
accent: Colors.pinkAccent,
threshold: 200,
),
_Palette(
name: GalleryLocalizations.of(context).colorsPurple,
primary: Colors.purple,
accent: Colors.purpleAccent,
threshold: 200,
),
_Palette(
name: GalleryLocalizations.of(context).colorsDeepPurple,
primary: Colors.deepPurple,
accent: Colors.deepPurpleAccent,
threshold: 200,
),
_Palette(
name: GalleryLocalizations.of(context).colorsIndigo,
primary: Colors.indigo,
accent: Colors.indigoAccent,
threshold: 200,
),
_Palette(
name: GalleryLocalizations.of(context).colorsBlue,
primary: Colors.blue,
accent: Colors.blueAccent,
threshold: 400,
),
_Palette(
name: GalleryLocalizations.of(context).colorsLightBlue,
primary: Colors.lightBlue,
accent: Colors.lightBlueAccent,
threshold: 500,
),
_Palette(
name: GalleryLocalizations.of(context).colorsCyan,
primary: Colors.cyan,
accent: Colors.cyanAccent,
threshold: 600,
),
_Palette(
name: GalleryLocalizations.of(context).colorsTeal,
primary: Colors.teal,
accent: Colors.tealAccent,
threshold: 400,
),
_Palette(
name: GalleryLocalizations.of(context).colorsGreen,
primary: Colors.green,
accent: Colors.greenAccent,
threshold: 500),
_Palette(
name: GalleryLocalizations.of(context).colorsLightGreen,
primary: Colors.lightGreen,
accent: Colors.lightGreenAccent,
threshold: 600,
),
_Palette(
name: GalleryLocalizations.of(context).colorsLime,
primary: Colors.lime,
accent: Colors.limeAccent,
threshold: 800,
),
_Palette(
name: GalleryLocalizations.of(context).colorsYellow,
primary: Colors.yellow,
accent: Colors.yellowAccent,
),
_Palette(
name: GalleryLocalizations.of(context).colorsAmber,
primary: Colors.amber,
accent: Colors.amberAccent,
),
_Palette(
name: GalleryLocalizations.of(context).colorsOrange,
primary: Colors.orange,
accent: Colors.orangeAccent,
threshold: 700,
),
_Palette(
name: GalleryLocalizations.of(context).colorsDeepOrange,
primary: Colors.deepOrange,
accent: Colors.deepOrangeAccent,
threshold: 400,
),
_Palette(
name: GalleryLocalizations.of(context).colorsBrown,
primary: Colors.brown,
threshold: 200,
),
_Palette(
name: GalleryLocalizations.of(context).colorsGrey,
primary: Colors.grey,
threshold: 500,
),
_Palette(
name: GalleryLocalizations.of(context).colorsBlueGrey,
primary: Colors.blueGrey,
threshold: 500,
),
];
}
class _ColorItem extends StatelessWidget {
const _ColorItem({
Key key,
@required this.index,
@required this.color,
this.prefix = '',
}) : assert(index != null),
assert(color != null),
assert(prefix != null),
super(key: key);
final int index;
final Color color;
final String prefix;
String get _colorString =>
"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}";
@override
Widget build(BuildContext context) {
return Semantics(
container: true,
child: Container(
height: kColorItemHeight,
padding: const EdgeInsets.symmetric(horizontal: 16),
color: color,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('$prefix$index'),
Flexible(child: Text(_colorString)),
],
),
),
);
}
}
class PaletteTabView extends StatelessWidget {
PaletteTabView({
Key key,
@required this.colors,
}) : assert(colors != null),
super(key: key);
final _Palette colors;
static const primaryKeys = <int>[
50,
100,
200,
300,
400,
500,
600,
700,
800,
900
];
static const accentKeys = <int>[100, 200, 400, 700];
@override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
final TextStyle whiteTextStyle = textTheme.body1.copyWith(
color: Colors.white,
);
final TextStyle blackTextStyle = textTheme.body1.copyWith(
color: Colors.black,
);
return Scrollbar(
child: ListView(
itemExtent: kColorItemHeight,
children: [
for (final key in primaryKeys)
DefaultTextStyle(
style: key > colors.threshold ? whiteTextStyle : blackTextStyle,
child: _ColorItem(index: key, color: colors.primary[key]),
),
if (colors.accent != null)
for (final key in accentKeys)
DefaultTextStyle(
style: key > colors.threshold ? whiteTextStyle : blackTextStyle,
child: _ColorItem(
index: key,
color: colors.accent[key],
prefix: 'A',
),
),
],
),
);
}
}
class ColorsDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final palettes = _allPalettes(context);
return DefaultTabController(
length: palettes.length,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoColorsTitle),
bottom: TabBar(
isScrollable: true,
tabs: [
for (final palette in palettes) Tab(text: palette.name),
],
),
),
body: TabBarView(
children: [
for (final palette in palettes) PaletteTabView(colors: palette),
],
),
),
);
}
}
// END

View File

@@ -0,0 +1,175 @@
// Copyright 2019 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 'dart:ui' show Vertices;
import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'transformations_demo_board.dart';
import 'transformations_demo_edit_board_point.dart';
import 'transformations_demo_gesture_transformable.dart';
// BEGIN transformationsDemo#1
class TransformationsDemo extends StatefulWidget {
const TransformationsDemo({Key key}) : super(key: key);
@override
_TransformationsDemoState createState() => _TransformationsDemoState();
}
class _TransformationsDemoState extends State<TransformationsDemo> {
// The radius of a hexagon tile in pixels.
static const _kHexagonRadius = 32.0;
// The margin between hexagons.
static const _kHexagonMargin = 1.0;
// The radius of the entire board in hexagons, not including the center.
static const _kBoardRadius = 12;
bool _reset = false;
Board _board = Board(
boardRadius: _kBoardRadius,
hexagonRadius: _kHexagonRadius,
hexagonMargin: _kHexagonMargin,
);
@override
Widget build(BuildContext context) {
final BoardPainter painter = BoardPainter(
board: _board,
);
// The scene is drawn by a CustomPaint, but user interaction is handled by
// the GestureTransformable parent widget.
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.primary,
appBar: AppBar(
automaticallyImplyLeading: false,
title:
Text(GalleryLocalizations.of(context).demo2dTransformationsTitle),
),
body: Container(
color: backgroundColor,
child: LayoutBuilder(
builder: (context, constraints) {
// Draw the scene as big as is available, but allow the user to
// translate beyond that to a visibleSize that's a bit bigger.
final Size size = Size(constraints.maxWidth, constraints.maxHeight);
final Size visibleSize = Size(size.width * 3, size.height * 2);
return GestureTransformable(
reset: _reset,
onResetEnd: () {
setState(() {
_reset = false;
});
},
child: CustomPaint(
painter: painter,
),
boundaryRect: Rect.fromLTWH(
-visibleSize.width / 2,
-visibleSize.height / 2,
visibleSize.width,
visibleSize.height,
),
// Center the board in the middle of the screen. It's drawn centered
// at the origin, which is the top left corner of the
// GestureTransformable.
initialTranslation: Offset(size.width / 2, size.height / 2),
onTapUp: _onTapUp,
size: size,
);
},
),
),
persistentFooterButtons: [resetButton, editButton],
);
}
IconButton get resetButton {
return IconButton(
onPressed: () {
setState(() {
_reset = true;
});
},
tooltip:
GalleryLocalizations.of(context).demo2dTransformationsResetTooltip,
color: Theme.of(context).colorScheme.surface,
icon: const Icon(Icons.replay),
);
}
IconButton get editButton {
return IconButton(
onPressed: () {
if (_board.selected == null) {
return;
}
showModalBottomSheet<Widget>(
context: context,
builder: (context) {
return Container(
width: double.infinity,
height: 150,
padding: const EdgeInsets.all(12),
child: EditBoardPoint(
boardPoint: _board.selected,
onColorSelection: (color) {
setState(() {
_board = _board.copyWithBoardPointColor(
_board.selected, color);
Navigator.pop(context);
});
},
),
);
});
},
tooltip:
GalleryLocalizations.of(context).demo2dTransformationsEditTooltip,
color: Theme.of(context).colorScheme.surface,
icon: const Icon(Icons.edit),
);
}
void _onTapUp(TapUpDetails details) {
final Offset scenePoint = details.globalPosition;
final BoardPoint boardPoint = _board.pointToBoardPoint(scenePoint);
setState(() {
_board = _board.copyWithSelected(boardPoint);
});
}
}
// CustomPainter is what is passed to CustomPaint and actually draws the scene
// when its `paint` method is called.
class BoardPainter extends CustomPainter {
const BoardPainter({
this.board,
});
final Board board;
@override
void paint(Canvas canvas, Size size) {
void drawBoardPoint(BoardPoint boardPoint) {
final Color color = boardPoint.color.withOpacity(
board.selected == boardPoint ? 0.7 : 1,
);
final Vertices vertices =
board.getVerticesForBoardPoint(boardPoint, color);
canvas.drawVertices(vertices, BlendMode.color, Paint());
}
board.forEach(drawBoardPoint);
}
// We should repaint whenever the board changes, such as board.selected.
@override
bool shouldRepaint(BoardPainter oldDelegate) {
return oldDelegate.board != board;
}
}
// END

View File

@@ -0,0 +1,289 @@
// Copyright 2019 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 'dart:collection' show IterableMixin;
import 'dart:math';
import 'dart:ui' show Vertices;
import 'package:flutter/material.dart' hide Gradient;
import 'package:vector_math/vector_math_64.dart' show Vector3;
// BEGIN transformationsDemo#2
// The entire state of the hex board and abstraction to get information about
// it. Iterable so that all BoardPoints on the board can be iterated over.
@immutable
class Board extends Object with IterableMixin<BoardPoint> {
Board({
@required this.boardRadius,
@required this.hexagonRadius,
@required this.hexagonMargin,
this.selected,
List<BoardPoint> boardPoints,
}) : assert(boardRadius > 0),
assert(hexagonRadius > 0),
assert(hexagonMargin >= 0) {
// Set up the positions for the center hexagon where the entire board is
// centered on the origin.
// Start point of hexagon (top vertex).
final Point<double> hexStart = Point<double>(0, -hexagonRadius);
final double hexagonRadiusPadded = hexagonRadius - hexagonMargin;
final double centerToFlat = sqrt(3) / 2 * hexagonRadiusPadded;
positionsForHexagonAtOrigin.addAll(<Offset>[
Offset(hexStart.x, hexStart.y),
Offset(hexStart.x + centerToFlat, hexStart.y + 0.5 * hexagonRadiusPadded),
Offset(hexStart.x + centerToFlat, hexStart.y + 1.5 * hexagonRadiusPadded),
Offset(hexStart.x + centerToFlat, hexStart.y + 1.5 * hexagonRadiusPadded),
Offset(hexStart.x, hexStart.y + 2 * hexagonRadiusPadded),
Offset(hexStart.x, hexStart.y + 2 * hexagonRadiusPadded),
Offset(hexStart.x - centerToFlat, hexStart.y + 1.5 * hexagonRadiusPadded),
Offset(hexStart.x - centerToFlat, hexStart.y + 1.5 * hexagonRadiusPadded),
Offset(hexStart.x - centerToFlat, hexStart.y + 0.5 * hexagonRadiusPadded),
]);
if (boardPoints != null) {
_boardPoints.addAll(boardPoints);
} else {
// Generate boardPoints for a fresh board.
BoardPoint boardPoint = _getNextBoardPoint(null);
while (boardPoint != null) {
_boardPoints.add(boardPoint);
boardPoint = _getNextBoardPoint(boardPoint);
}
}
}
final int boardRadius; // Number of hexagons from center to edge.
final double hexagonRadius; // Pixel radius of a hexagon (center to vertex).
final double hexagonMargin; // Margin between hexagons.
final List<Offset> positionsForHexagonAtOrigin = <Offset>[];
final BoardPoint selected;
final List<BoardPoint> _boardPoints = <BoardPoint>[];
@override
Iterator<BoardPoint> get iterator => _BoardIterator(_boardPoints);
// For a given q axial coordinate, get the range of possible r values
// See the definition of BoardPoint for more information about hex grids and
// axial coordinates.
_Range _getRRangeForQ(int q) {
int rStart;
int rEnd;
if (q <= 0) {
rStart = -boardRadius - q;
rEnd = boardRadius;
} else {
rEnd = boardRadius - q;
rStart = -boardRadius;
}
return _Range(rStart, rEnd);
}
// Get the BoardPoint that comes after the given BoardPoint. If given null,
// returns the origin BoardPoint. If given BoardPoint is the last, returns
// null.
BoardPoint _getNextBoardPoint(BoardPoint boardPoint) {
// If before the first element.
if (boardPoint == null) {
return BoardPoint(-boardRadius, 0);
}
final _Range rRange = _getRRangeForQ(boardPoint.q);
// If at or after the last element.
if (boardPoint.q >= boardRadius && boardPoint.r >= rRange.max) {
return null;
}
// If wrapping from one q to the next.
if (boardPoint.r >= rRange.max) {
return BoardPoint(boardPoint.q + 1, _getRRangeForQ(boardPoint.q + 1).min);
}
// Otherwise we're just incrementing r.
return BoardPoint(boardPoint.q, boardPoint.r + 1);
}
// Check if the board point is actually on the board.
bool _validateBoardPoint(BoardPoint boardPoint) {
const BoardPoint center = BoardPoint(0, 0);
final int distanceFromCenter = getDistance(center, boardPoint);
return distanceFromCenter <= boardRadius;
}
// Get the distance between two BoardPoins.
static int getDistance(BoardPoint a, BoardPoint b) {
final Vector3 a3 = a.cubeCoordinates;
final Vector3 b3 = b.cubeCoordinates;
return ((a3.x - b3.x).abs() + (a3.y - b3.y).abs() + (a3.z - b3.z).abs()) ~/
2;
}
// Return the q,r BoardPoint for a point in the scene, where the origin is in
// the center of the board in both coordinate systems. If no BoardPoint at the
// location, return null.
BoardPoint pointToBoardPoint(Offset point) {
final BoardPoint boardPoint = BoardPoint(
((sqrt(3) / 3 * point.dx - 1 / 3 * point.dy) / hexagonRadius).round(),
((2 / 3 * point.dy) / hexagonRadius).round(),
);
if (!_validateBoardPoint(boardPoint)) {
return null;
}
return _boardPoints.firstWhere((boardPointI) {
return boardPointI.q == boardPoint.q && boardPointI.r == boardPoint.r;
});
}
// Return a scene point for the center of a hexagon given its q,r point.
Point<double> boardPointToPoint(BoardPoint boardPoint) {
return Point<double>(
sqrt(3) * hexagonRadius * boardPoint.q +
sqrt(3) / 2 * hexagonRadius * boardPoint.r,
1.5 * hexagonRadius * boardPoint.r,
);
}
// Get Vertices that can be drawn to a Canvas for the given BoardPoint.
Vertices getVerticesForBoardPoint(BoardPoint boardPoint, Color color) {
final Point<double> centerOfHexZeroCenter = boardPointToPoint(boardPoint);
final List<Offset> positions = positionsForHexagonAtOrigin.map((offset) {
return offset.translate(centerOfHexZeroCenter.x, centerOfHexZeroCenter.y);
}).toList();
return Vertices(
VertexMode.triangleFan,
positions,
colors: List<Color>.filled(positions.length, color),
);
}
// Return a new board with the given BoardPoint selected.
Board copyWithSelected(BoardPoint boardPoint) {
if (selected == boardPoint) {
return this;
}
final Board nextBoard = Board(
boardRadius: boardRadius,
hexagonRadius: hexagonRadius,
hexagonMargin: hexagonMargin,
selected: boardPoint,
boardPoints: _boardPoints,
);
return nextBoard;
}
// Return a new board where boardPoint has the given color.
Board copyWithBoardPointColor(BoardPoint boardPoint, Color color) {
final BoardPoint nextBoardPoint = boardPoint.copyWithColor(color);
final int boardPointIndex = _boardPoints.indexWhere((boardPointI) =>
boardPointI.q == boardPoint.q && boardPointI.r == boardPoint.r);
if (elementAt(boardPointIndex) == boardPoint && boardPoint.color == color) {
return this;
}
final List<BoardPoint> nextBoardPoints =
List<BoardPoint>.from(_boardPoints);
nextBoardPoints[boardPointIndex] = nextBoardPoint;
final BoardPoint selectedBoardPoint =
boardPoint == selected ? nextBoardPoint : selected;
return Board(
boardRadius: boardRadius,
hexagonRadius: hexagonRadius,
hexagonMargin: hexagonMargin,
selected: selectedBoardPoint,
boardPoints: nextBoardPoints,
);
}
}
class _BoardIterator extends Iterator<BoardPoint> {
_BoardIterator(this.boardPoints);
final List<BoardPoint> boardPoints;
int currentIndex;
@override
BoardPoint current;
@override
bool moveNext() {
if (currentIndex == null) {
currentIndex = 0;
} else {
currentIndex++;
}
if (currentIndex >= boardPoints.length) {
current = null;
return false;
}
current = boardPoints[currentIndex];
return true;
}
}
// A range of q/r board coordinate values.
@immutable
class _Range {
const _Range(this.min, this.max)
: assert(min != null),
assert(max != null),
assert(min <= max);
final int min;
final int max;
}
// A location on the board in axial coordinates.
// Axial coordinates use two integers, q and r, to locate a hexagon on a grid.
// https://www.redblobgames.com/grids/hexagons/#coordinates-axial
@immutable
class BoardPoint {
const BoardPoint(
this.q,
this.r, {
this.color = const Color(0xFFCDCDCD),
});
final int q;
final int r;
final Color color;
@override
String toString() {
return 'BoardPoint($q, $r, $color)';
}
// Only compares by location.
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is BoardPoint && other.q == q && other.r == r;
}
@override
int get hashCode => hashValues(q, r);
BoardPoint copyWithColor(Color nextColor) =>
BoardPoint(q, r, color: nextColor);
// Convert from q,r axial coords to x,y,z cube coords.
Vector3 get cubeCoordinates {
return Vector3(
q.toDouble(),
r.toDouble(),
(-q - r).toDouble(),
);
}
}
// END

View File

@@ -0,0 +1,76 @@
// Copyright 2019 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';
// A generic widget for a list of selectable colors.
@immutable
class ColorPicker extends StatelessWidget {
const ColorPicker({
@required this.colors,
@required this.selectedColor,
this.onColorSelection,
}) : assert(colors != null),
assert(selectedColor != null);
final Set<Color> colors;
final Color selectedColor;
final ValueChanged<Color> onColorSelection;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: colors.map((color) {
return _ColorPickerSwatch(
color: color,
selected: color == selectedColor,
onTap: () {
if (onColorSelection != null) {
onColorSelection(color);
}
},
);
}).toList(),
);
}
}
// A single selectable color widget in the ColorPicker.
@immutable
class _ColorPickerSwatch extends StatelessWidget {
const _ColorPickerSwatch({
@required this.color,
@required this.selected,
this.onTap,
}) : assert(color != null),
assert(selected != null);
final Color color;
final bool selected;
final Function onTap;
@override
Widget build(BuildContext context) {
return Container(
width: 60,
height: 60,
padding: const EdgeInsets.fromLTRB(2, 0, 2, 0),
child: RawMaterialButton(
fillColor: color,
onPressed: () {
if (onTap != null) {
onTap();
}
},
child: !selected
? null
: const Icon(
Icons.check,
color: Colors.white,
),
),
);
}
}

View File

@@ -0,0 +1,53 @@
// Copyright 2019 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/themes/gallery_theme_data.dart';
import 'transformations_demo_board.dart';
import 'transformations_demo_color_picker.dart';
final backgroundColor = Color(0xFF272727);
// The panel for editing a board point.
@immutable
class EditBoardPoint extends StatelessWidget {
const EditBoardPoint({
Key key,
@required this.boardPoint,
this.onColorSelection,
}) : assert(boardPoint != null),
super(key: key);
final BoardPoint boardPoint;
final ValueChanged<Color> onColorSelection;
@override
Widget build(BuildContext context) {
print(GalleryThemeData.darkColorScheme);
final boardPointColors = <Color>{
Colors.white,
GalleryThemeData.darkColorScheme.primary,
GalleryThemeData.darkColorScheme.primaryVariant,
GalleryThemeData.darkColorScheme.secondary,
backgroundColor,
};
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'${boardPoint.q}, ${boardPoint.r}',
textAlign: TextAlign.right,
style: const TextStyle(fontWeight: FontWeight.bold),
),
ColorPicker(
colors: boardPointColors,
selectedColor: boardPoint.color,
onColorSelection: onColorSelection,
),
],
);
}
}

View File

@@ -0,0 +1,620 @@
// Copyright 2019 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:vector_math/vector_math_64.dart' show Vector3;
import 'transformations_demo_inertial_motion.dart';
// BEGIN transformationsDemo#3
// This widget allows 2D transform interactions on its child in relation to its
// parent. The user can transform the child by dragging to pan or pinching to
// zoom and rotate. All event callbacks for GestureDetector are supported, and
// the coordinates that are given are untransformed and in relation to the
// original position of the child.
@immutable
class GestureTransformable extends StatefulWidget {
const GestureTransformable({
Key key,
// The child to perform the transformations on.
@required this.child,
// The desired visible size of the widget and the area that is receptive to
// gestures. If a widget that's as big as possible is desired, then wrap
// this in a LayoutBuilder and pass
// `Size(constraints.maxWidth, constraints.maxHeight)`.
@required this.size,
// The scale will be clamped to between these values. A maxScale of null has
// no bounds. minScale must be greater than zero.
this.maxScale = 2.5,
this.minScale = 0.8,
// Transforms will be limited so that the viewport can not view beyond this
// Rect. The Rect does not rotate with the rest of the scene, so it is
// always aligned with the viewport. A null boundaryRect results in no
// limits to the distance that the viewport can be transformed to see.
this.boundaryRect,
// Initial values for the transform can be provided.
this.initialTranslation,
this.initialScale,
this.initialRotation,
// Any and all of the possible transformations can be disabled.
this.disableTranslation = false,
this.disableScale = false,
this.disableRotation = false,
// If set to true, this widget will animate back to its initial transform
// and call onResetEnd when done. When utilizing reset, onResetEnd should
// also be implemented, and it should set reset to false when called.
this.reset = false,
// Access to event callbacks from GestureDetector. Called with untransformed
// coordinates in an Offset.
this.onTapDown,
this.onTapUp,
this.onTap,
this.onTapCancel,
this.onDoubleTap,
this.onLongPress,
this.onLongPressUp,
this.onVerticalDragDown,
this.onVerticalDragStart,
this.onVerticalDragUpdate,
this.onVerticalDragEnd,
this.onVerticalDragCancel,
this.onHorizontalDragDown,
this.onHorizontalDragStart,
this.onHorizontalDragUpdate,
this.onHorizontalDragEnd,
this.onHorizontalDragCancel,
this.onPanDown,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.onResetEnd,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
}) : assert(child != null),
assert(size != null),
assert(minScale != null),
assert(minScale > 0),
assert(disableTranslation != null),
assert(disableScale != null),
assert(disableRotation != null),
assert(reset != null),
assert(
!reset || onResetEnd != null,
'Must implement onResetEnd to use reset.',
),
super(key: key);
final Widget child;
final Size size;
final bool reset;
final GestureTapDownCallback onTapDown;
final GestureTapUpCallback onTapUp;
final GestureTapCallback onTap;
final GestureTapCancelCallback onTapCancel;
final GestureTapCallback onDoubleTap;
final GestureLongPressCallback onLongPress;
final GestureLongPressUpCallback onLongPressUp;
final GestureDragDownCallback onVerticalDragDown;
final GestureDragStartCallback onVerticalDragStart;
final GestureDragUpdateCallback onVerticalDragUpdate;
final GestureDragEndCallback onVerticalDragEnd;
final GestureDragCancelCallback onVerticalDragCancel;
final GestureDragDownCallback onHorizontalDragDown;
final GestureDragStartCallback onHorizontalDragStart;
final GestureDragUpdateCallback onHorizontalDragUpdate;
final GestureDragEndCallback onHorizontalDragEnd;
final GestureDragCancelCallback onHorizontalDragCancel;
final GestureDragDownCallback onPanDown;
final GestureDragStartCallback onPanStart;
final GestureDragUpdateCallback onPanUpdate;
final GestureDragEndCallback onPanEnd;
final GestureDragCancelCallback onPanCancel;
final VoidCallback onResetEnd;
final GestureScaleStartCallback onScaleStart;
final GestureScaleUpdateCallback onScaleUpdate;
final GestureScaleEndCallback onScaleEnd;
final double maxScale;
final double minScale;
final Rect boundaryRect;
final bool disableTranslation;
final bool disableScale;
final bool disableRotation;
final Offset initialTranslation;
final double initialScale;
final double initialRotation;
@override
_GestureTransformableState createState() => _GestureTransformableState();
}
// A single user event can only represent one of these gestures. The user can't
// do multiple at the same time, which results in more precise transformations.
enum _GestureType {
translate,
scale,
rotate,
}
// This is public only for access from a unit test.
class _GestureTransformableState extends State<GestureTransformable>
with TickerProviderStateMixin {
Animation<Offset> _animation;
AnimationController _controller;
Animation<Matrix4> _animationReset;
AnimationController _controllerReset;
// The translation that will be applied to the scene (not viewport).
// A positive x offset moves the scene right, viewport left.
// A positive y offset moves the scene down, viewport up.
Offset _translateFromScene; // Point where a single translation began.
double _scaleStart; // Scale value at start of scaling gesture.
double _rotationStart = 0; // Rotation at start of rotation gesture.
Rect _boundaryRect;
Matrix4 _transform = Matrix4.identity();
double _currentRotation = 0;
_GestureType gestureType;
// The transformation matrix that gives the initial home position.
Matrix4 get _initialTransform {
Matrix4 matrix = Matrix4.identity();
if (widget.initialTranslation != null) {
matrix = matrixTranslate(matrix, widget.initialTranslation);
}
if (widget.initialScale != null) {
matrix = matrixScale(matrix, widget.initialScale);
}
if (widget.initialRotation != null) {
matrix = matrixRotate(matrix, widget.initialRotation, Offset.zero);
}
return matrix;
}
// Return the scene point at the given viewport point.
static Offset fromViewport(Offset viewportPoint, Matrix4 transform) {
// On viewportPoint, perform the inverse transformation of the scene to get
// where the point would be in the scene before the transformation.
final Matrix4 inverseMatrix = Matrix4.inverted(transform);
final Vector3 untransformed = inverseMatrix.transform3(Vector3(
viewportPoint.dx,
viewportPoint.dy,
0,
));
return Offset(untransformed.x, untransformed.y);
}
// Get the offset of the current widget from the global screen coordinates.
// TODO(justinmc): Protect against calling this during first build.
static Offset getOffset(BuildContext context) {
final RenderBox renderObject = context.findRenderObject() as RenderBox;
return renderObject.localToGlobal(Offset.zero);
}
@override
void initState() {
super.initState();
_boundaryRect = widget.boundaryRect ?? Offset.zero & widget.size;
_transform = _initialTransform;
_controller = AnimationController(
vsync: this,
);
_controllerReset = AnimationController(
vsync: this,
);
if (widget.reset) {
_animateResetInitialize();
}
}
@override
void didUpdateWidget(GestureTransformable oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.reset && !oldWidget.reset && _animationReset == null) {
_animateResetInitialize();
} else if (!widget.reset && oldWidget.reset && _animationReset != null) {
_animateResetStop();
}
}
@override
Widget build(BuildContext context) {
// A GestureDetector allows the detection of panning and zooming gestures on
// its child, which is the CustomPaint.
return GestureDetector(
behavior: HitTestBehavior.opaque, // Necessary when translating off screen
onTapDown: widget.onTapDown == null
? null
: (details) {
widget.onTapDown(TapDownDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onTapUp: widget.onTapUp == null
? null
: (details) {
widget.onTapUp(TapUpDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onTap: widget.onTap,
onTapCancel: widget.onTapCancel,
onDoubleTap: widget.onDoubleTap,
onLongPress: widget.onLongPress,
onLongPressUp: widget.onLongPressUp,
onVerticalDragDown: widget.onVerticalDragDown == null
? null
: (details) {
widget.onVerticalDragDown(DragDownDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onVerticalDragStart: widget.onVerticalDragStart == null
? null
: (details) {
widget.onVerticalDragStart(DragStartDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onVerticalDragUpdate: widget.onVerticalDragUpdate == null
? null
: (details) {
widget.onVerticalDragUpdate(DragUpdateDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onVerticalDragEnd: widget.onVerticalDragEnd,
onVerticalDragCancel: widget.onVerticalDragCancel,
onHorizontalDragDown: widget.onHorizontalDragDown == null
? null
: (details) {
widget.onHorizontalDragDown(DragDownDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onHorizontalDragStart: widget.onHorizontalDragStart == null
? null
: (details) {
widget.onHorizontalDragStart(DragStartDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onHorizontalDragUpdate: widget.onHorizontalDragUpdate == null
? null
: (details) {
widget.onHorizontalDragUpdate(DragUpdateDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onHorizontalDragEnd: widget.onHorizontalDragEnd,
onHorizontalDragCancel: widget.onHorizontalDragCancel,
onPanDown: widget.onPanDown == null
? null
: (details) {
widget.onPanDown(DragDownDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onPanStart: widget.onPanStart == null
? null
: (details) {
widget.onPanStart(DragStartDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onPanUpdate: widget.onPanUpdate == null
? null
: (details) {
widget.onPanUpdate(DragUpdateDetails(
globalPosition: fromViewport(
details.globalPosition - getOffset(context), _transform),
));
},
onPanEnd: widget.onPanEnd,
onPanCancel: widget.onPanCancel,
onScaleEnd: _onScaleEnd,
onScaleStart: _onScaleStart,
onScaleUpdate: _onScaleUpdate,
child: ClipRect(
// The scene is panned/zoomed/rotated using this Transform widget.
child: Transform(
transform: _transform,
child: Container(
child: widget.child,
height: widget.size.height,
width: widget.size.width,
),
),
),
);
}
// Return a new matrix representing the given matrix after applying the given
// translation.
Matrix4 matrixTranslate(Matrix4 matrix, Offset translation) {
if (widget.disableTranslation || translation == Offset.zero) {
return matrix;
}
// Clamp translation so the viewport remains inside _boundaryRect.
final double scale = _transform.getMaxScaleOnAxis();
final Size scaledSize = widget.size / scale;
final Rect viewportBoundaries = Rect.fromLTRB(
_boundaryRect.left,
_boundaryRect.top,
_boundaryRect.right - scaledSize.width,
_boundaryRect.bottom - scaledSize.height,
);
// Translation is reversed (a positive translation moves the scene to the
// right, viewport to the left).
final Rect translationBoundaries = Rect.fromLTRB(
-scale * viewportBoundaries.right,
-scale * viewportBoundaries.bottom,
-scale * viewportBoundaries.left,
-scale * viewportBoundaries.top,
);
final Matrix4 nextMatrix = matrix.clone()
..translate(
translation.dx,
translation.dy,
);
final Vector3 nextTranslationVector = nextMatrix.getTranslation();
final Offset nextTranslation = Offset(
nextTranslationVector.x,
nextTranslationVector.y,
);
final bool inBoundaries = translationBoundaries.contains(
Offset(nextTranslation.dx, nextTranslation.dy),
);
if (!inBoundaries) {
// TODO(justinmc): Instead of canceling translation when it goes out of
// bounds, stop translation at boundary.
return matrix;
}
return nextMatrix;
}
// Return a new matrix representing the given matrix after applying the given
// scale transform.
Matrix4 matrixScale(Matrix4 matrix, double scale) {
if (widget.disableScale || scale == 1) {
return matrix;
}
assert(scale != 0);
// Don't allow a scale that moves the viewport outside of _boundaryRect.
final Offset tl = fromViewport(const Offset(0, 0), _transform);
final Offset tr = fromViewport(Offset(widget.size.width, 0), _transform);
final Offset bl = fromViewport(Offset(0, widget.size.height), _transform);
final Offset br = fromViewport(
Offset(widget.size.width, widget.size.height),
_transform,
);
if (!_boundaryRect.contains(tl) ||
!_boundaryRect.contains(tr) ||
!_boundaryRect.contains(bl) ||
!_boundaryRect.contains(br)) {
return matrix;
}
// Don't allow a scale that results in an overall scale beyond min/max
// scale.
final double currentScale = _transform.getMaxScaleOnAxis();
final double totalScale = currentScale * scale;
final double clampedTotalScale = totalScale.clamp(
widget.minScale,
widget.maxScale,
) as double;
final double clampedScale = clampedTotalScale / currentScale;
return matrix..scale(clampedScale);
}
// Return a new matrix representing the given matrix after applying the given
// rotation transform.
// Rotating the scene cannot cause the viewport to view beyond _boundaryRect.
Matrix4 matrixRotate(Matrix4 matrix, double rotation, Offset focalPoint) {
if (widget.disableRotation || rotation == 0) {
return matrix;
}
final Offset focalPointScene = fromViewport(focalPoint, matrix);
return matrix
..translate(focalPointScene.dx, focalPointScene.dy)
..rotateZ(-rotation)
..translate(-focalPointScene.dx, -focalPointScene.dy);
}
// Handle the start of a gesture of _GestureType.
void _onScaleStart(ScaleStartDetails details) {
if (widget.onScaleStart != null) {
widget.onScaleStart(details);
}
if (_controller.isAnimating) {
_controller.stop();
_controller.reset();
_animation?.removeListener(_onAnimate);
_animation = null;
}
if (_controllerReset.isAnimating) {
_animateResetStop();
}
gestureType = null;
setState(() {
_scaleStart = _transform.getMaxScaleOnAxis();
_translateFromScene = fromViewport(details.focalPoint, _transform);
_rotationStart = _currentRotation;
});
}
// Handle an update to an ongoing gesture of _GestureType.
void _onScaleUpdate(ScaleUpdateDetails details) {
double scale = _transform.getMaxScaleOnAxis();
if (widget.onScaleUpdate != null) {
widget.onScaleUpdate(ScaleUpdateDetails(
focalPoint: fromViewport(details.focalPoint, _transform),
scale: details.scale,
rotation: details.rotation,
));
}
final Offset focalPointScene = fromViewport(
details.focalPoint,
_transform,
);
if (gestureType == null) {
// Decide which type of gesture this is by comparing the amount of scale
// and rotation in the gesture, if any. Scale starts at 1 and rotation
// starts at 0. Translate will have 0 scale and 0 rotation because it uses
// only one finger.
if ((details.scale - 1).abs() > details.rotation.abs()) {
gestureType = _GestureType.scale;
} else if (details.rotation != 0) {
gestureType = _GestureType.rotate;
} else {
gestureType = _GestureType.translate;
}
}
setState(() {
if (gestureType == _GestureType.scale && _scaleStart != null) {
// details.scale gives us the amount to change the scale as of the
// start of this gesture, so calculate the amount to scale as of the
// previous call to _onScaleUpdate.
final double desiredScale = _scaleStart * details.scale;
final double scaleChange = desiredScale / scale;
_transform = matrixScale(_transform, scaleChange);
scale = _transform.getMaxScaleOnAxis();
// While scaling, translate such that the user's two fingers stay on the
// same places in the scene. That means that the focal point of the
// scale should be on the same place in the scene before and after the
// scale.
final Offset focalPointSceneNext = fromViewport(
details.focalPoint,
_transform,
);
_transform =
matrixTranslate(_transform, focalPointSceneNext - focalPointScene);
} else if (gestureType == _GestureType.rotate && details.rotation != 0) {
final double desiredRotation = _rotationStart + details.rotation;
_transform = matrixRotate(
_transform, _currentRotation - desiredRotation, details.focalPoint);
_currentRotation = desiredRotation;
} else if (_translateFromScene != null && details.scale == 1) {
// Translate so that the same point in the scene is underneath the
// focal point before and after the movement.
final Offset translationChange = focalPointScene - _translateFromScene;
_transform = matrixTranslate(_transform, translationChange);
_translateFromScene = fromViewport(details.focalPoint, _transform);
}
});
}
// Handle the end of a gesture of _GestureType.
void _onScaleEnd(ScaleEndDetails details) {
if (widget.onScaleEnd != null) {
widget.onScaleEnd(details);
}
setState(() {
_scaleStart = null;
_rotationStart = null;
_translateFromScene = null;
});
_animation?.removeListener(_onAnimate);
_controller.reset();
// If the scale ended with velocity, animate inertial movement
final double velocityTotal = details.velocity.pixelsPerSecond.dx.abs() +
details.velocity.pixelsPerSecond.dy.abs();
if (velocityTotal == 0) {
return;
}
final Vector3 translationVector = _transform.getTranslation();
final Offset translation = Offset(translationVector.x, translationVector.y);
final InertialMotion inertialMotion =
InertialMotion(details.velocity, translation);
_animation = Tween<Offset>(
begin: translation,
end: inertialMotion.finalPosition,
).animate(_controller);
_controller.duration =
Duration(milliseconds: inertialMotion.duration.toInt());
_animation.addListener(_onAnimate);
_controller.fling();
}
// Handle inertia drag animation.
void _onAnimate() {
setState(() {
// Translate _transform such that the resulting translation is
// _animation.value.
final Vector3 translationVector = _transform.getTranslation();
final Offset translation =
Offset(translationVector.x, translationVector.y);
final Offset translationScene = fromViewport(translation, _transform);
final Offset animationScene = fromViewport(_animation.value, _transform);
final Offset translationChangeScene = animationScene - translationScene;
_transform = matrixTranslate(_transform, translationChangeScene);
});
if (!_controller.isAnimating) {
_animation?.removeListener(_onAnimate);
_animation = null;
_controller.reset();
}
}
// Handle reset to home transform animation.
void _onAnimateReset() {
setState(() {
_transform = _animationReset.value;
});
if (!_controllerReset.isAnimating) {
_animationReset?.removeListener(_onAnimateReset);
_animationReset = null;
_controllerReset.reset();
widget.onResetEnd();
}
}
// Initialize the reset to home transform animation.
void _animateResetInitialize() {
_controllerReset.reset();
_animationReset = Matrix4Tween(
begin: _transform,
end: _initialTransform,
).animate(_controllerReset);
_controllerReset.duration = const Duration(milliseconds: 400);
_animationReset.addListener(_onAnimateReset);
_controllerReset.forward();
}
// Stop a running reset to home transform animation.
void _animateResetStop() {
_controllerReset.stop();
_animationReset?.removeListener(_onAnimateReset);
_animationReset = null;
_controllerReset.reset();
widget.onResetEnd();
}
@override
void dispose() {
_controller.dispose();
_controllerReset.dispose();
super.dispose();
}
}
// END

View File

@@ -0,0 +1,72 @@
// Copyright 2019 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 'dart:math';
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math.dart' show Vector2;
// Provides calculations for an object moving with inertia and friction using
// the equation of motion from physics.
// https://en.wikipedia.org/wiki/Equations_of_motion#Constant_translational_acceleration_in_a_straight_line
// TODO(justinmc): Can this be replaced with friction_simulation.dart?
@immutable
class InertialMotion {
const InertialMotion(this._initialVelocity, this._initialPosition);
static const double _kFrictionalAcceleration = 0.01; // How quickly to stop
final Velocity _initialVelocity;
final Offset _initialPosition;
// The position when the motion stops.
Offset get finalPosition {
return _getPositionAt(Duration(milliseconds: duration.toInt()));
}
// The total time that the animation takes start to stop in milliseconds.
double get duration {
return (_initialVelocity.pixelsPerSecond.dx / 1000 / _acceleration.x).abs();
}
// The acceleration opposing the initial velocity in x and y components.
Vector2 get _acceleration {
// TODO(justinmc): Find actual velocity instead of summing?
final velocityTotal = _initialVelocity.pixelsPerSecond.dx.abs() +
_initialVelocity.pixelsPerSecond.dy.abs();
final vRatioX = _initialVelocity.pixelsPerSecond.dx / velocityTotal;
final vRatioY = _initialVelocity.pixelsPerSecond.dy / velocityTotal;
return Vector2(
_kFrictionalAcceleration * vRatioX,
_kFrictionalAcceleration * vRatioY,
);
}
// The position at a given time.
Offset _getPositionAt(Duration time) {
final xf = _getPosition(
r0: _initialPosition.dx,
v0: _initialVelocity.pixelsPerSecond.dx / 1000,
t: time.inMilliseconds,
a: _acceleration.x,
);
final yf = _getPosition(
r0: _initialPosition.dy,
v0: _initialVelocity.pixelsPerSecond.dy / 1000,
t: time.inMilliseconds,
a: _acceleration.y,
);
return Offset(xf, yf);
}
// Solve the equation of motion to find the position at a given point in time
// in one dimension.
double _getPosition({double r0, double v0, int t, double a}) {
// Stop movement when it would otherwise reverse direction.
final stopTime = (v0 / a).abs();
if (t > stopTime) {
t = stopTime.toInt();
}
return r0 + v0 * t + 0.5 * a * pow(t, 2);
}
}

View File

@@ -0,0 +1,128 @@
// Copyright 2019 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 typographyDemo
class _TextStyleItem extends StatelessWidget {
const _TextStyleItem({
Key key,
@required this.name,
@required this.style,
@required this.text,
}) : assert(name != null),
assert(style != null),
assert(text != null),
super(key: key);
final String name;
final TextStyle style;
final String text;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 72,
child: Text(name, style: Theme.of(context).textTheme.caption),
),
Expanded(
child: Text(text, style: style),
),
],
),
);
}
}
class TypographyDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final styleItems = [
_TextStyleItem(
name: 'Display 4',
style: textTheme.display4,
text: 'Light 96sp',
),
_TextStyleItem(
name: 'Display 3',
style: textTheme.display3,
text: 'Light 60sp',
),
_TextStyleItem(
name: 'Display 2',
style: textTheme.display2,
text: 'Regular 48sp',
),
_TextStyleItem(
name: 'Display 1',
style: textTheme.display1,
text: 'Regular 34sp',
),
_TextStyleItem(
name: 'Headline',
style: textTheme.headline,
text: 'Regular 24sp',
),
_TextStyleItem(
name: 'Title',
style: textTheme.title,
text: 'Medium 20sp',
),
_TextStyleItem(
name: 'Subhead',
style: textTheme.subhead,
text: 'Regular 16sp',
),
_TextStyleItem(
name: 'Subtitle',
style: textTheme.subtitle,
text: 'Medium 14sp',
),
_TextStyleItem(
name: 'Body 1',
style: textTheme.body1,
text: 'Regular 16sp',
),
_TextStyleItem(
name: 'Body 2',
style: textTheme.body2,
text: 'Regular 14sp',
),
_TextStyleItem(
name: 'Button',
style: textTheme.button,
text: 'MEDIUM (ALL CAPS) 14sp',
),
_TextStyleItem(
name: 'Caption',
style: textTheme.caption,
text: 'Regular 12sp',
),
_TextStyleItem(
name: 'Overline',
style: textTheme.overline,
text: 'REGULAR (ALL CAPS) 10sp',
),
];
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(GalleryLocalizations.of(context).demoTypographyTitle),
),
body: Scrollbar(child: ListView(children: styleItems)),
);
}
}
// END