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:
@@ -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
|
||||
342
gallery/lib/demos/cupertino/cupertino_alert_demo.dart
Normal file
342
gallery/lib/demos/cupertino/cupertino_alert_demo.dart
Normal 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
|
||||
44
gallery/lib/demos/cupertino/cupertino_button_demo.dart
Normal file
44
gallery/lib/demos/cupertino/cupertino_button_demo.dart
Normal 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
|
||||
@@ -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
|
||||
241
gallery/lib/demos/cupertino/cupertino_picker_demo.dart
Normal file
241
gallery/lib/demos/cupertino/cupertino_picker_demo.dart
Normal 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
|
||||
74
gallery/lib/demos/cupertino/cupertino_refresh_demo.dart
Normal file
74
gallery/lib/demos/cupertino/cupertino_refresh_demo.dart
Normal 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
|
||||
@@ -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
|
||||
94
gallery/lib/demos/cupertino/cupertino_slider_demo.dart
Normal file
94
gallery/lib/demos/cupertino/cupertino_slider_demo.dart
Normal 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
|
||||
47
gallery/lib/demos/cupertino/cupertino_switch_demo.dart
Normal file
47
gallery/lib/demos/cupertino/cupertino_switch_demo.dart
Normal 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
|
||||
88
gallery/lib/demos/cupertino/cupertino_tab_bar_demo.dart
Normal file
88
gallery/lib/demos/cupertino/cupertino_tab_bar_demo.dart
Normal 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
|
||||
66
gallery/lib/demos/cupertino/cupertino_text_field_demo.dart
Normal file
66
gallery/lib/demos/cupertino/cupertino_text_field_demo.dart
Normal 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
|
||||
125
gallery/lib/demos/material/banner_demo.dart
Normal file
125
gallery/lib/demos/material/banner_demo.dart
Normal 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
|
||||
169
gallery/lib/demos/material/bottom_app_bar_demo.dart
Normal file
169
gallery/lib/demos/material/bottom_app_bar_demo.dart
Normal 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
|
||||
212
gallery/lib/demos/material/bottom_navigation_demo.dart
Normal file
212
gallery/lib/demos/material/bottom_navigation_demo.dart
Normal 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
|
||||
192
gallery/lib/demos/material/bottom_sheet_demo.dart
Normal file
192
gallery/lib/demos/material/bottom_sheet_demo.dart
Normal 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
|
||||
214
gallery/lib/demos/material/button_demo.dart
Normal file
214
gallery/lib/demos/material/button_demo.dart
Normal 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
|
||||
411
gallery/lib/demos/material/cards_demo.dart
Normal file
411
gallery/lib/demos/material/cards_demo.dart
Normal 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
|
||||
215
gallery/lib/demos/material/chip_demo.dart
Normal file
215
gallery/lib/demos/material/chip_demo.dart
Normal 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
|
||||
551
gallery/lib/demos/material/data_table_demo.dart
Normal file
551
gallery/lib/demos/material/data_table_demo.dart
Normal 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
|
||||
285
gallery/lib/demos/material/dialog_demo.dart
Normal file
285
gallery/lib/demos/material/dialog_demo.dart
Normal 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
|
||||
199
gallery/lib/demos/material/grid_list_demo.dart
Normal file
199
gallery/lib/demos/material/grid_list_demo.dart
Normal 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
|
||||
51
gallery/lib/demos/material/list_demo.dart
Normal file
51
gallery/lib/demos/material/list_demo.dart
Normal 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
|
||||
368
gallery/lib/demos/material/menu_demo.dart
Normal file
368
gallery/lib/demos/material/menu_demo.dart
Normal 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
|
||||
112
gallery/lib/demos/material/picker_demo.dart
Normal file
112
gallery/lib/demos/material/picker_demo.dart
Normal 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
|
||||
114
gallery/lib/demos/material/progress_indicator_demo.dart
Normal file
114
gallery/lib/demos/material/progress_indicator_demo.dart
Normal 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
|
||||
176
gallery/lib/demos/material/selection_controls_demo.dart
Normal file
176
gallery/lib/demos/material/selection_controls_demo.dart
Normal 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
|
||||
503
gallery/lib/demos/material/sliders_demo.dart
Normal file
503
gallery/lib/demos/material/sliders_demo.dart
Normal 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
|
||||
54
gallery/lib/demos/material/snackbar_demo.dart
Normal file
54
gallery/lib/demos/material/snackbar_demo.dart
Normal 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
|
||||
118
gallery/lib/demos/material/tabs_demo.dart
Normal file
118
gallery/lib/demos/material/tabs_demo.dart
Normal 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
|
||||
357
gallery/lib/demos/material/text_field_demo.dart
Normal file
357
gallery/lib/demos/material/text_field_demo.dart
Normal 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
|
||||
46
gallery/lib/demos/material/tooltip_demo.dart
Normal file
46
gallery/lib/demos/material/tooltip_demo.dart
Normal 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
|
||||
265
gallery/lib/demos/reference/colors_demo.dart
Normal file
265
gallery/lib/demos/reference/colors_demo.dart
Normal 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
|
||||
175
gallery/lib/demos/reference/transformations_demo.dart
Normal file
175
gallery/lib/demos/reference/transformations_demo.dart
Normal 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
|
||||
289
gallery/lib/demos/reference/transformations_demo_board.dart
Normal file
289
gallery/lib/demos/reference/transformations_demo_board.dart
Normal 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
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
128
gallery/lib/demos/reference/typography_demo.dart
Normal file
128
gallery/lib/demos/reference/typography_demo.dart
Normal 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
|
||||
Reference in New Issue
Block a user