guanghuispark
4 年前
当前提交
b0cfb93e
共有 22 个文件被更改,包括 2169 次插入 和 12 次删除
-
4Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/cupertino/cupertino_navigation_demo.cs
-
2Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/model/app_state_model.cs
-
4Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/model/product.cs
-
2Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/gallery/backdrop.cs
-
4com.unity.uiwidgets/Runtime/animation/tween_sequence.cs
-
2com.unity.uiwidgets/Runtime/painting/image_provider.cs
-
4com.unity.uiwidgets/Runtime/widgets/animated_list.cs
-
2com.unity.uiwidgets/Runtime/widgets/heroes.cs
-
119Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/app.cs
-
399Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/backdrop.cs
-
85Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/category_menu_page.cs
-
22Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/colors.cs
-
655Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/expanding_bottom_sheet.cs
-
53Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/home.cs
-
136Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/login.cs
-
283Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/shopping_cart.cs
-
81Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/supplemental/asymmetric_view.cs
-
137Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/supplemental/cut_corners_border.cs
-
102Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/supplemental/product_card.cs
-
85Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/demo/shrine/supplemental/product_columns.cs
-
0/Samples/UIWidgetsSamples_2019_4/Assets/StreamingAssets/logo.png
|
|||
using UIWidgetsGallery.demo.shrine; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine |
|||
{ |
|||
public class ShrineApp : StatefulWidget { |
|||
public override State createState() => new _ShrineAppState(); |
|||
} |
|||
|
|||
public class _ShrineAppState : State<ShrineApp> {//with SingleTickerProviderStateMixin {
|
|||
|
|||
AnimationController _controller; |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
_controller = new AnimationController( |
|||
vsync: this, |
|||
duration: Durantion(milliseconds: 450), |
|||
value: 1.0 |
|||
); |
|||
} |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new MaterialApp( |
|||
title: "Shrine", |
|||
home: HomePage( |
|||
backdrop: Backdrop( |
|||
frontLayer: ProductPage(), |
|||
backLayer: CategoryMenuPage(onCategoryTap: () => _controller.forward()), |
|||
frontTitle: new Text("SHRINE"), |
|||
backTitle: new Text("MENU"), |
|||
controller: _controller |
|||
), |
|||
expandingBottomSheet: ExpandingBottomSheet(hideController: _controller), |
|||
), |
|||
initialRoute: "/login", |
|||
onGenerateRoute: _getRoute, |
|||
theme: _kShrineTheme.copyWith(platform: Theme.of(context).platform), |
|||
); |
|||
} |
|||
} |
|||
|
|||
Route<object> _getRoute(RouteSettings settings) { |
|||
if (settings.name != '/login') { |
|||
return null; |
|||
} |
|||
|
|||
return new MaterialPageRoute( |
|||
settings: settings, |
|||
builder: (BuildContext context) => LoginPage(), |
|||
fullscreenDialog: true |
|||
); |
|||
} |
|||
|
|||
public readonly ThemeData _kShrineTheme = _buildShrineTheme(); |
|||
|
|||
IconThemeData _customIconTheme(IconThemeData original) { |
|||
return original.copyWith(color: shrineColorsUtils.kShrineBrown900); |
|||
} |
|||
|
|||
ThemeData _buildShrineTheme() { |
|||
ThemeData _base = ThemeData.light(); |
|||
return _base.copyWith( |
|||
colorScheme: kShrineColorScheme, |
|||
accentColor: shrineColorsUtils.kShrineBrown900, |
|||
primaryColor: shrineColorsUtils.kShrinePink100, |
|||
buttonColor: shrineColorsUtils.kShrinePink100, |
|||
scaffoldBackgroundColor: shrineColorsUtils.kShrineBackgroundWhite, |
|||
cardColor: shrineColorsUtils.kShrineBackgroundWhite, |
|||
textSelectionColor: shrineColorsUtils.kShrinePink100, |
|||
errorColor: shrineColorsUtils.kShrineErrorRed, |
|||
buttonTheme: new ButtonThemeData( |
|||
colorScheme: kShrineColorScheme, |
|||
textTheme: ButtonTextTheme.normal |
|||
), |
|||
primaryIconTheme: _customIconTheme(_base.iconTheme), |
|||
inputDecorationTheme: new InputDecorationTheme(border: CutCornersBorder()), |
|||
textTheme: _buildShrineTextTheme(_base.textTheme), |
|||
primaryTextTheme: _buildShrineTextTheme(_base.primaryTextTheme), |
|||
accentTextTheme: _buildShrineTextTheme(_base.accentTextTheme), |
|||
iconTheme: _customIconTheme(_base.iconTheme) |
|||
); |
|||
} |
|||
|
|||
TextTheme _buildShrineTextTheme(TextTheme _base) { |
|||
return _base.copyWith( |
|||
headline5: _base.headline5.copyWith(fontWeight: FontWeight.w500), |
|||
headline6: _base.headline6.copyWith(fontSize: 18.0f), |
|||
caption: _base.caption.copyWith(fontWeight: FontWeight.w400, fontSize: 14.0f), |
|||
bodyText1: _base.bodyText1.copyWith(fontWeight: FontWeight.w500, fontSize: 16.0f), |
|||
button: _base.button.copyWith(fontWeight: FontWeight.w500, fontSize: 14.0f) |
|||
).apply( |
|||
fontFamily: "Raleway", |
|||
displayColor: shrineColorsUtils.kShrineBrown900, |
|||
bodyColor: shrineColorsUtils.kShrineBrown900 |
|||
); |
|||
} |
|||
|
|||
ColorScheme kShrineColorScheme = new ColorScheme( |
|||
primary: shrineColorsUtils.kShrinePink100, |
|||
primaryVariant: shrineColorsUtils.kShrineBrown900, |
|||
secondary: shrineColorsUtils.kShrinePink50, |
|||
secondaryVariant: shrineColorsUtils.kShrineBrown900, |
|||
surface: shrineColorsUtils.kShrineSurfaceWhite, |
|||
background: shrineColorsUtils.kShrineBackgroundWhite, |
|||
error: shrineColorsUtils.kShrineErrorRed, |
|||
onPrimary: shrineColorsUtils.kShrineBrown900, |
|||
onSecondary: shrineColorsUtils.kShrineBrown900, |
|||
onSurface: shrineColorsUtils.kShrineBrown900, |
|||
onBackground: shrineColorsUtils.kShrineBrown900, |
|||
onError: shrineColorsUtils.kShrineSurfaceWhite, |
|||
brightness: Brightness.light |
|||
); |
|||
|
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine |
|||
{ |
|||
public class backdropUtils |
|||
{ |
|||
public static readonly Cubic _kAccelerateCurve = new Cubic(0.548f, 0.0f, 0.757f, 0.464f); |
|||
public static readonly Cubic _kDecelerateCurve = new Cubic(0.23f, 0.94f, 0.41f, 1.0f); |
|||
public static readonly float _kPeakVelocityTime = 0.248210f; |
|||
public static readonly float _kPeakVelocityProgress = 0.379146f; |
|||
} |
|||
|
|||
|
|||
public class _TappableWhileStatusIs : StatefulWidget { |
|||
public _TappableWhileStatusIs( |
|||
AnimationStatus status, |
|||
Key key = null, |
|||
AnimationController controller = null, |
|||
Widget child = null |
|||
) : base(key: key) |
|||
{ |
|||
this.status = status; |
|||
this.controller = controller; |
|||
this.child = child; |
|||
} |
|||
|
|||
public readonly AnimationController controller; |
|||
public readonly AnimationStatus status; |
|||
public readonly Widget child; |
|||
|
|||
public override State createState() => new _TappableWhileStatusIsState(); |
|||
} |
|||
|
|||
public class _TappableWhileStatusIsState : State<_TappableWhileStatusIs> { |
|||
bool _active; |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
widget.controller.addStatusListener(_handleStatusChange); |
|||
_active = widget.controller.status == widget.status; |
|||
} |
|||
|
|||
|
|||
public override void dispose() { |
|||
widget.controller.removeStatusListener(_handleStatusChange); |
|||
base.dispose(); |
|||
} |
|||
|
|||
void _handleStatusChange(AnimationStatus status) { |
|||
bool value = widget.controller.status == widget.status; |
|||
if (_active != value) { |
|||
setState(() => { |
|||
_active = value; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
Widget child = new AbsorbPointer( |
|||
absorbing: !_active, |
|||
child: widget.child |
|||
); |
|||
|
|||
if (!_active) { |
|||
child = new FocusScope( |
|||
canRequestFocus: false, |
|||
debugLabel: "_TappableWhileStatusIs", |
|||
child: child |
|||
); |
|||
} |
|||
return child; |
|||
} |
|||
} |
|||
|
|||
public class _FrontLayer : StatelessWidget { |
|||
public _FrontLayer( |
|||
Key key = null, |
|||
VoidCallback onTap = null, |
|||
Widget child = null |
|||
) : base(key: key) |
|||
{ |
|||
this.onTap = onTap; |
|||
this.child = child; |
|||
} |
|||
|
|||
public readonly VoidCallback onTap; |
|||
public readonly Widget child; |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new Material( |
|||
elevation: 16.0f, |
|||
shape: new BeveledRectangleBorder( |
|||
borderRadius: BorderRadius.only(topLeft: Radius.circular(46.0f)) |
|||
), |
|||
child: new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.stretch, |
|||
children: new List<Widget>{ |
|||
new GestureDetector( |
|||
behavior: HitTestBehavior.opaque, |
|||
onTap: onTap, |
|||
child: new Container( |
|||
height: 40.0f, |
|||
alignment: AlignmentDirectional.centerStart |
|||
) |
|||
), |
|||
new Expanded( |
|||
child: child |
|||
), |
|||
} |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class _BackdropTitle : AnimatedWidget { |
|||
public _BackdropTitle( |
|||
Key key, |
|||
Animation<float> listenable, |
|||
Delegate onPress, |
|||
Widget frontTitle, |
|||
Widget backTitle |
|||
) : base(key: key, listenable: listenable) |
|||
{ |
|||
D.assert(frontTitle != null); |
|||
D.assert(backTitle != null); |
|||
this.backTitle = backTitle; |
|||
this.frontTitle = frontTitle; |
|||
this.onPress = onPress; |
|||
} |
|||
|
|||
public readonly Delegate onPress; |
|||
//public delegate void onPress(); [!!!]
|
|||
public readonly Widget frontTitle; |
|||
public readonly Widget backTitle; |
|||
|
|||
|
|||
protected override Widget build(BuildContext context) { |
|||
Animation<float> animation = new CurvedAnimation( |
|||
parent: listenable as Animation<float>, |
|||
curve: new Interval(0.0f, 0.78f) |
|||
); |
|||
|
|||
return new DefaultTextStyle( |
|||
style: Theme.of(context).primaryTextTheme.headline6, |
|||
softWrap: false, |
|||
overflow: TextOverflow.ellipsis, |
|||
child: new Row(children: new List<Widget>{ |
|||
new SizedBox( |
|||
width: 72.0f, |
|||
child: new IconButton( |
|||
padding: EdgeInsets.only(right: 8.0f), |
|||
onPressed: onPress, |
|||
icon: new Stack(children: new List<Widget>{ |
|||
new Opacity( |
|||
opacity: animation.value, |
|||
child: new ImageIcon(new FileImage("packages/shrine_images/slanted_menu.png")) |
|||
), |
|||
new FractionalTranslation( |
|||
translation: new Tween<Offset>( |
|||
begin: Offset.zero, |
|||
end: new Offset(1.0f, 0.0f)).evaluate(animation), |
|||
child: new ImageIcon(new FileImage("packages/shrine_images/diamond.png")) |
|||
) |
|||
}) |
|||
) |
|||
), |
|||
new Stack( |
|||
children: new List<Widget>{ |
|||
new Opacity( |
|||
opacity: new CurvedAnimation( |
|||
parent: new ReverseAnimation(animation), |
|||
curve: new Interval(0.5f, 1.0f) |
|||
).value, |
|||
child: new FractionalTranslation( |
|||
translation: new Tween<Offset>( |
|||
begin: Offset.zero, |
|||
end: new Offset(0.5f, 0.0f)).evaluate(animation), |
|||
child: backTitle |
|||
) |
|||
), |
|||
new Opacity( |
|||
opacity: new CurvedAnimation( |
|||
parent: animation, |
|||
curve: new Interval(0.5f, 1.0f)).value, |
|||
child: new FractionalTranslation( |
|||
translation: new Tween<Offset>( |
|||
begin: new Offset(-0.25f, 0.0f), |
|||
end: Offset.zero).evaluate(animation), |
|||
child: frontTitle |
|||
) |
|||
), |
|||
} |
|||
), |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class Backdrop : StatefulWidget { |
|||
public Backdrop( |
|||
Widget frontLayer, |
|||
Widget backLayer, |
|||
Widget frontTitle, |
|||
Widget backTitle, |
|||
AnimationController controller |
|||
) { |
|||
D.assert(frontLayer != null); |
|||
D.assert(backLayer != null); |
|||
D.assert(frontTitle != null); |
|||
D.assert(backTitle != null); |
|||
D.assert(controller != null); |
|||
this.frontLayer = frontLayer; |
|||
this.backLayer = backLayer; |
|||
this.frontTitle = frontTitle; |
|||
this.backTitle = backTitle; |
|||
this.controller = controller; |
|||
} |
|||
|
|||
public readonly Widget frontLayer; |
|||
public readonly Widget backLayer; |
|||
public readonly Widget frontTitle; |
|||
public readonly Widget backTitle; |
|||
public readonly AnimationController controller; |
|||
|
|||
|
|||
public override State createState() => new _BackdropState(); |
|||
} |
|||
|
|||
public class _BackdropState : State<Backdrop> {//with SingleTickerProviderStateMixin { [!!!]
|
|||
public readonly GlobalKey _backdropKey = GlobalKey.key(debugLabel: "Backdrop"); |
|||
AnimationController _controller; |
|||
Animation<RelativeRect> _layerAnimation; |
|||
|
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
_controller = widget.controller; |
|||
} |
|||
|
|||
public override void dispose() { |
|||
_controller.dispose(); |
|||
base.dispose(); |
|||
} |
|||
|
|||
bool _frontLayerVisible { |
|||
get |
|||
{ |
|||
AnimationStatus status = _controller.status; |
|||
return status == AnimationStatus.completed || status == AnimationStatus.forward; |
|||
} |
|||
} |
|||
|
|||
void _toggleBackdropLayerVisibility() { //[!!!]
|
|||
setState(() => { |
|||
var visible = _frontLayerVisible ? _controller.reverse() : _controller.forward(); |
|||
}); |
|||
} |
|||
|
|||
Animation<RelativeRect> _getLayerAnimation(Size layerSize, float layerTop) { |
|||
Curve firstCurve; |
|||
Curve secondCurve; |
|||
float firstWeight; |
|||
float secondWeight; |
|||
Animation<float> animation; |
|||
|
|||
if (_frontLayerVisible) { |
|||
firstCurve = backdropUtils._kAccelerateCurve; |
|||
secondCurve = backdropUtils._kDecelerateCurve; |
|||
firstWeight = backdropUtils._kPeakVelocityTime; |
|||
secondWeight = 1.0f - backdropUtils._kPeakVelocityTime; |
|||
animation = new CurvedAnimation( |
|||
parent: _controller.view, |
|||
curve: new Interval(0.0f, 0.78f) |
|||
); |
|||
} else { |
|||
firstCurve = backdropUtils._kDecelerateCurve.flipped; |
|||
secondCurve = backdropUtils._kAccelerateCurve.flipped; |
|||
firstWeight = 1.0f - backdropUtils._kPeakVelocityTime; |
|||
secondWeight = backdropUtils._kPeakVelocityTime; |
|||
animation = _controller.view; |
|||
} |
|||
|
|||
return new TweenSequence<RelativeRect>( |
|||
new List<TweenSequenceItem<RelativeRect>>{ |
|||
new TweenSequenceItem<RelativeRect>( |
|||
tween: new RelativeRectTween( |
|||
begin: RelativeRect.fromLTRB( |
|||
0.0f, |
|||
layerTop, |
|||
0.0f, |
|||
layerTop - layerSize.height |
|||
), |
|||
end: RelativeRect.fromLTRB( |
|||
0.0f, |
|||
layerTop * backdropUtils._kPeakVelocityProgress, |
|||
0.0f, |
|||
(layerTop - layerSize.height) * backdropUtils._kPeakVelocityProgress |
|||
) |
|||
).chain(new CurveTween(curve: firstCurve)), |
|||
weight: firstWeight |
|||
), |
|||
new TweenSequenceItem<RelativeRect>( |
|||
tween: new RelativeRectTween( |
|||
begin: RelativeRect.fromLTRB( |
|||
0.0f, |
|||
layerTop * backdropUtils._kPeakVelocityProgress, |
|||
0.0f, |
|||
(layerTop - layerSize.height) * backdropUtils._kPeakVelocityProgress |
|||
), |
|||
end: RelativeRect.fill |
|||
).chain(new CurveTween(curve: secondCurve)), |
|||
weight: secondWeight |
|||
), |
|||
} |
|||
).animate(animation); |
|||
} |
|||
|
|||
Widget _buildStack(BuildContext context, BoxConstraints constraints) { |
|||
float layerTitleHeight = 48.0f; |
|||
Size layerSize = constraints.biggest; |
|||
float layerTop = layerSize.height - layerTitleHeight; |
|||
|
|||
_layerAnimation = _getLayerAnimation(layerSize, layerTop); |
|||
|
|||
return new Stack( |
|||
key: _backdropKey, |
|||
children: new List<Widget>{ |
|||
new _TappableWhileStatusIs( |
|||
AnimationStatus.dismissed, |
|||
controller: _controller, |
|||
child: widget.backLayer |
|||
), |
|||
new PositionedTransition( |
|||
rect: _layerAnimation, |
|||
child: new _FrontLayer( |
|||
onTap: _toggleBackdropLayerVisibility, |
|||
child: new _TappableWhileStatusIs( |
|||
AnimationStatus.completed, |
|||
controller: _controller, |
|||
child: widget.frontLayer |
|||
) |
|||
) |
|||
), |
|||
} |
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
AppBar appBar = new AppBar( |
|||
brightness: Brightness.light, |
|||
elevation: 0.0f, |
|||
titleSpacing: 0.0f, |
|||
title: new _BackdropTitle( |
|||
listenable: _controller.view, |
|||
onPress: _toggleBackdropLayerVisibility, |
|||
frontTitle: widget.frontTitle, |
|||
backTitle: widget.backTitle |
|||
), |
|||
actions: new List<Widget>{ |
|||
new IconButton( |
|||
icon: new Icon(Icons.search), |
|||
onPressed: () => { |
|||
Navigator.push<Void>( |
|||
context, |
|||
new MaterialPageRoute<Void>(builder: (BuildContext context) => new LoginPage()) |
|||
); |
|||
} |
|||
), |
|||
new IconButton( |
|||
icon: new Icon(Icons.tune), |
|||
onPressed: () => { |
|||
Navigator.push<void>( |
|||
context, |
|||
new MaterialPageRoute<void>(builder: (BuildContext context) => new LoginPage()), |
|||
); |
|||
} |
|||
), |
|||
} |
|||
); |
|||
return new Scaffold( |
|||
appBar: appBar, |
|||
body: new LayoutBuilder( |
|||
builder: _buildStack |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using UIWidgetsGallery.demo.shrine.model; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine |
|||
{ |
|||
public class CategoryMenuPage : StatelessWidget { |
|||
public CategoryMenuPage( |
|||
Key key = null, |
|||
VoidCallback onCategoryTap = null |
|||
) : base(key: key) |
|||
{ |
|||
this.onCategoryTap = onCategoryTap; |
|||
} |
|||
|
|||
public readonly VoidCallback onCategoryTap; |
|||
|
|||
Widget _buildCategory(Category category, BuildContext context) |
|||
{ |
|||
string categoryString = category.ToString().Replace("Category", "").ToUpper(); |
|||
ThemeData theme = Theme.of(context); |
|||
return new ScopedModelDescendant<AppStateModel>( |
|||
builder: (BuildContext context2, Widget child, AppStateModel model) => |
|||
new GestureDetector( |
|||
onTap: () => |
|||
{ |
|||
model.setCategory(category); |
|||
if (onCategoryTap != null) |
|||
{ |
|||
onCategoryTap(); |
|||
} |
|||
}, |
|||
child: model.selectedCategory == category |
|||
? new Column( |
|||
children: new List<Widget> |
|||
{ |
|||
new SizedBox(height: 16.0f), |
|||
new Text( |
|||
categoryString, |
|||
style: theme.textTheme.bodyText1, |
|||
textAlign: TextAlign.center |
|||
), |
|||
new SizedBox(height: 14.0f), |
|||
new Container( |
|||
width: 70.0f, |
|||
height: 2.0f, |
|||
color: shrineColorsUtils.kShrinePink400 |
|||
), |
|||
} |
|||
) |
|||
: new Padding( |
|||
padding: EdgeInsets.symmetric(vertical: 16.0f), |
|||
child: new Text( |
|||
categoryString, |
|||
style: theme.textTheme.bodyText1.copyWith( |
|||
color: shrineColorsUtils.kShrineBrown900.withAlpha(153) |
|||
), |
|||
textAlign: TextAlign.center |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
|
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new Center( |
|||
child: new Container( |
|||
padding: EdgeInsets.only(top: 40.0f), |
|||
color: shrineColorsUtils.kShrinePink100, |
|||
child: new ListView( |
|||
children: Category.values.map((Category c) => _buildCategory(c, context)).toList(), |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
using uiwidgets; |
|||
using Unity.UIWidgets.ui; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine |
|||
{ |
|||
public class shrineColorsUtils |
|||
{ |
|||
public static readonly Color kShrinePink50 = new Color(0xFFFEEAE6); |
|||
public static readonly Color kShrinePink100 = new Color(0xFFFEDBD0); |
|||
public static readonly Color kShrinePink300 = new Color(0xFFFBB8AC); |
|||
public static readonly Color kShrinePink400 = new Color(0xFFEAA4A4); |
|||
|
|||
public static readonly Color kShrineBrown900 = new Color(0xFF442B2D); |
|||
public static readonly Color kShrineBrown600 = new Color(0xFF7D4F52); |
|||
|
|||
public static readonly Color kShrineErrorRed = new Color(0xFFC5032B); |
|||
|
|||
public static readonly Color kShrineSurfaceWhite = new Color(0xFFFFFBFA); |
|||
public static readonly Color kShrineBackgroundWhite = Colors.white; |
|||
|
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UIWidgetsGallery.demo.shrine.model; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.async2; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine |
|||
{ |
|||
public class expanding_buttom_sheetUtils |
|||
{ |
|||
public static readonly Cubic _kAccelerateCurve = new Cubic(0.548f, 0.0f, 0.757f, 0.464f); |
|||
public static readonly Cubic _kDecelerateCurve = new Cubic(0.23f, 0.94f, 0.41f, 1.0f); |
|||
public static readonly float _kPeakVelocityTime = 0.248210f; |
|||
public static readonly float _kPeakVelocityProgress = 0.379146f; |
|||
public static readonly float _kCartHeight = 56.0f; |
|||
public static readonly float _kCornerRadius = 24.0f; |
|||
public static readonly float _kWidthForCartIcon = 64.0f; |
|||
|
|||
Animation<T> _getEmphasizedEasingAnimation<T>( |
|||
T begin, |
|||
T peak, |
|||
T end, |
|||
bool isForward, |
|||
Animation<float> parent |
|||
) { |
|||
Curve firstCurve; |
|||
Curve secondCurve; |
|||
float firstWeight; |
|||
float secondWeight; |
|||
|
|||
if (isForward) { |
|||
firstCurve = _kAccelerateCurve; |
|||
secondCurve = _kDecelerateCurve; |
|||
firstWeight = _kPeakVelocityTime; |
|||
secondWeight = 1.0f - _kPeakVelocityTime; |
|||
} else { |
|||
firstCurve = _kDecelerateCurve.flipped; |
|||
secondCurve = _kAccelerateCurve.flipped; |
|||
firstWeight = 1.0f - _kPeakVelocityTime; |
|||
secondWeight = _kPeakVelocityTime; |
|||
} |
|||
|
|||
return new TweenSequence<T>( |
|||
new List<TweenSequenceItem<T>>{ |
|||
new TweenSequenceItem<T>( |
|||
weight: firstWeight, |
|||
tween: new Tween<T>( |
|||
begin: begin, |
|||
end: peak |
|||
).chain(new CurveTween(curve: firstCurve)) |
|||
), |
|||
new TweenSequenceItem<T>( |
|||
weight: secondWeight, |
|||
tween: new Tween<T>( |
|||
begin: peak, |
|||
end: end |
|||
).chain(new CurveTween(curve: secondCurve)) |
|||
), |
|||
} |
|||
).animate(parent); |
|||
} |
|||
|
|||
public static float _getPeakPoint(float begin, float end) { |
|||
return begin + (end - begin) * _kPeakVelocityProgress; |
|||
} |
|||
} |
|||
|
|||
|
|||
public class ExpandingBottomSheet : StatefulWidget { |
|||
public ExpandingBottomSheet(Key key = null, AnimationController hideController = null) |
|||
: base(key: key) |
|||
{ |
|||
D.assert(hideController != null); |
|||
this.hideController = hideController; |
|||
} |
|||
|
|||
public readonly AnimationController hideController; |
|||
|
|||
public override State createState() => new _ExpandingBottomSheetState(); |
|||
|
|||
static _ExpandingBottomSheetState of(BuildContext context, bool isNullOk = false) { |
|||
D.assert(context != null); |
|||
_ExpandingBottomSheetState result = context.findAncestorStateOfType<_ExpandingBottomSheetState>(); |
|||
if (isNullOk || result != null) { |
|||
return result; |
|||
} |
|||
throw new UIWidgetsError( |
|||
"ExpandingBottomSheet.of() called with a context that does not contain a ExpandingBottomSheet.\n"); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
class _ExpandingBottomSheetState : State<ExpandingBottomSheet> { //with TickerProviderStateMixin {
|
|||
public readonly GlobalKey _expandingBottomSheetKey = GlobalKey.key(debugLabel: "Expanding bottom sheet"); |
|||
|
|||
float _width = expanding_buttom_sheetUtils._kWidthForCartIcon; |
|||
AnimationController _controller; |
|||
|
|||
Animation<float> _widthAnimation; |
|||
Animation<float> _heightAnimation; |
|||
Animation<float> _thumbnailOpacityAnimation; |
|||
Animation<float> _cartOpacityAnimation; |
|||
Animation<float> _shapeAnimation; |
|||
Animation<Offset> _slideAnimation; |
|||
|
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
_controller = new AnimationController( |
|||
duration: Duration(milliseconds: 500), |
|||
vsync: this |
|||
); |
|||
} |
|||
|
|||
public override void dispose() { |
|||
_controller.dispose(); |
|||
base.dispose(); |
|||
} |
|||
|
|||
Animation<float> _getWidthAnimation(float screenWidth) { |
|||
if (_controller.status == AnimationStatus.forward) { |
|||
return new Tween<float>(begin: _width, end: screenWidth).animate( |
|||
new CurvedAnimation( |
|||
parent: _controller.view, |
|||
curve: new Interval(0.0f, 0.3f, curve: Curves.fastOutSlowIn) |
|||
) |
|||
); |
|||
} else { |
|||
return _getEmphasizedEasingAnimation( |
|||
begin: _width, |
|||
peak: _getPeakPoint(begin: _width, end: screenWidth), |
|||
end: screenWidth, |
|||
isForward: false, |
|||
parent: new CurvedAnimation(parent: _controller.view, curve: new Interval(0.0f, 0.87f)), |
|||
); |
|||
} |
|||
} |
|||
|
|||
Animation<float> _getHeightAnimation(float screenHeight) { |
|||
if (_controller.status == AnimationStatus.forward) { |
|||
return _getEmphasizedEasingAnimation( |
|||
begin: expanding_buttom_sheetUtils._kCartHeight, |
|||
peak: expanding_buttom_sheetUtils._kCartHeight + (screenHeight - expanding_buttom_sheetUtils._kCartHeight) * expanding_buttom_sheetUtils._kPeakVelocityProgress, |
|||
end: screenHeight, |
|||
isForward: true, |
|||
parent: _controller.view |
|||
); |
|||
} else { |
|||
return new Tween<float>( |
|||
begin: expanding_buttom_sheetUtils._kCartHeight, |
|||
end: screenHeight |
|||
).animate( |
|||
new CurvedAnimation( |
|||
parent: _controller.view, |
|||
curve: new Interval(0.434f, 1.0f, curve: Curves.linear), |
|||
reverseCurve: new Interval(0.434f, 1.0f, curve: Curves.fastOutSlowIn.flipped) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
// Animation of the cut corner. It's cut when closed and not cut when open.
|
|||
Animation<float> _getShapeAnimation() { |
|||
if (_controller.status == AnimationStatus.forward) { |
|||
return new Tween<float>(begin: expanding_buttom_sheetUtils._kCornerRadius, end: 0.0f).animate( |
|||
new CurvedAnimation( |
|||
parent: _controller.view, |
|||
curve: new Interval(0.0f, 0.3f, curve: Curves.fastOutSlowIn) |
|||
) |
|||
); |
|||
} else { |
|||
return _getEmphasizedEasingAnimation( |
|||
begin: expanding_buttom_sheetUtils._kCornerRadius, |
|||
peak: expanding_buttom_sheetUtils._getPeakPoint(begin: expanding_buttom_sheetUtils._kCornerRadius, end: 0.0f), |
|||
end: 0.0f, |
|||
isForward: false, |
|||
parent: _controller.view |
|||
); |
|||
} |
|||
} |
|||
|
|||
Animation<float> _getThumbnailOpacityAnimation() { |
|||
return new Tween<float>(begin: 1.0f, end: 0.0f).animate( |
|||
new CurvedAnimation( |
|||
parent: _controller.view, |
|||
curve: _controller.status == AnimationStatus.forward |
|||
? new Interval(0.0f, 0.3f) |
|||
: new Interval(0.532f, 0.766f) |
|||
) |
|||
); |
|||
} |
|||
|
|||
Animation<float> _getCartOpacityAnimation() { |
|||
return new CurvedAnimation( |
|||
parent: _controller.view, |
|||
curve: _controller.status == AnimationStatus.forward |
|||
? new Interval(0.3f, 0.6f) |
|||
: new Interval(0.766f, 1.0f) |
|||
); |
|||
} |
|||
|
|||
// Returns the correct width of the ExpandingBottomSheet based on the number of
|
|||
// products in the cart.
|
|||
float _widthFor(int numProducts) { |
|||
switch (numProducts) { |
|||
case 0: |
|||
return expanding_buttom_sheetUtils._kWidthForCartIcon; |
|||
case 1: |
|||
return 136.0f; |
|||
case 2: |
|||
return 192.0f; |
|||
case 3: |
|||
return 248.0f; |
|||
default: |
|||
return 278.0f; |
|||
} |
|||
} |
|||
|
|||
// Returns true if the cart is open or opening and false otherwise.
|
|||
bool _isOpen { |
|||
get |
|||
{ |
|||
AnimationStatus status = _controller.status; |
|||
return status == AnimationStatus.completed || status == AnimationStatus.forward; |
|||
} |
|||
} |
|||
|
|||
// Opens the ExpandingBottomSheet if it's closed, otherwise does nothing.
|
|||
void open() { |
|||
if (!_isOpen) { |
|||
_controller.forward(); |
|||
} |
|||
} |
|||
|
|||
// Closes the ExpandingBottomSheet if it's open or opening, otherwise does nothing.
|
|||
void close() { |
|||
if (_isOpen) { |
|||
_controller.reverse(); |
|||
} |
|||
} |
|||
|
|||
// Changes the padding between the start edge of the Material and the cart icon
|
|||
// based on the number of products in the cart (padding increases when > 0
|
|||
// products.)
|
|||
EdgeInsetsDirectional _cartPaddingFor(int numProducts) { |
|||
return (numProducts == 0) |
|||
? EdgeInsetsDirectional.only(start: 20.0f, end: 8.0f) |
|||
: EdgeInsetsDirectional.only(start: 32.0f, end: 8.0f); |
|||
} |
|||
|
|||
bool _cartIsVisible |
|||
{ |
|||
get |
|||
{ |
|||
return _thumbnailOpacityAnimation.value == 0.0; |
|||
} |
|||
} |
|||
|
|||
Widget _buildThumbnails(int numProducts) { |
|||
return new ExcludeSemantics( |
|||
child: new Opacity( |
|||
opacity: _thumbnailOpacityAnimation.value, |
|||
child: new Column( |
|||
children: new List<Widget>{ |
|||
new Row( |
|||
children: new List<Widget>{ |
|||
new AnimatedPadding( |
|||
padding: _cartPaddingFor(numProducts), |
|||
child: new Icon(Icons.shopping_cart), |
|||
duration: new Duration(milliseconds: 225) |
|||
), |
|||
new Container( |
|||
// Accounts for the overflow number
|
|||
width: numProducts > 3 ? _width - 94.0f: _width - 64.0f, |
|||
height: expanding_buttom_sheetUtils._kCartHeight, |
|||
padding: EdgeInsets.symmetric(vertical: 8.0f), |
|||
child: new ProductThumbnailRow() |
|||
), |
|||
new ExtraProductsNumber(), |
|||
} |
|||
), |
|||
} |
|||
) |
|||
) |
|||
); |
|||
} |
|||
|
|||
Widget _buildShoppingCartPage() { |
|||
return new Opacity( |
|||
opacity: _cartOpacityAnimation.value, |
|||
child: new ShoppingCartPage() |
|||
); |
|||
} |
|||
|
|||
Widget _buildCart(BuildContext context, Widget child) { |
|||
|
|||
AppStateModel model = ScopedModel.of<AppStateModel>(context); |
|||
int numProducts = model.productsInCart.Keys.Count; |
|||
int totalCartQuantity = model.totalCartQuantity; |
|||
Size screenSize = MediaQuery.of(context).size; |
|||
float screenWidth = screenSize.width; |
|||
float screenHeight = screenSize.height; |
|||
|
|||
_width = _widthFor(numProducts); |
|||
_widthAnimation = _getWidthAnimation(screenWidth); |
|||
_heightAnimation = _getHeightAnimation(screenHeight); |
|||
_shapeAnimation = _getShapeAnimation(); |
|||
_thumbnailOpacityAnimation = _getThumbnailOpacityAnimation(); |
|||
_cartOpacityAnimation = _getCartOpacityAnimation(); |
|||
|
|||
return Semantics( |
|||
button: true, |
|||
value: $"Shopping cart, {totalCartQuantity} items", |
|||
child: new Container( |
|||
width: _widthAnimation.value, |
|||
height: _heightAnimation.value, |
|||
child: new Material( |
|||
animationDuration: Duration(milliseconds: 0), |
|||
shape: new BeveledRectangleBorder( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(_shapeAnimation.value) |
|||
) |
|||
), |
|||
elevation: 4.0f, |
|||
color: shrineColorsUtils.kShrinePink50, |
|||
child: _cartIsVisible |
|||
? _buildShoppingCartPage() |
|||
: _buildThumbnails(numProducts) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
|
|||
// Builder for the hide and reveal animation when the backdrop opens and closes
|
|||
Widget _buildSlideAnimation(BuildContext context, Widget child) { |
|||
_slideAnimation = _getEmphasizedEasingAnimation( |
|||
begin: new Offset(1.0f, 0.0f), |
|||
peak: new Offset(expanding_buttom_sheetUtils._kPeakVelocityProgress, 0.0f), |
|||
end: new Offset(0.0f, 0.0f), |
|||
isForward: widget.hideController.status == AnimationStatus.forward, |
|||
parent: widget.hideController |
|||
); |
|||
|
|||
return new SlideTransition( |
|||
position: _slideAnimation, |
|||
child: child |
|||
); |
|||
} |
|||
|
|||
// Closes the cart if the cart is open, otherwise exits the app (this should
|
|||
// only be relevant for Android).
|
|||
Future<bool> _onWillPop() async { |
|||
if (!_isOpen) { |
|||
await SystemNavigator.pop(); |
|||
return true; |
|||
} |
|||
|
|||
close(); |
|||
return true; |
|||
} |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new AnimatedSize( |
|||
key: _expandingBottomSheetKey, |
|||
duration: Duration(milliseconds: 225), |
|||
curve: Curves.easeInOut, |
|||
vsync: this, |
|||
alignment: FractionalOffset.topLeft, |
|||
child: new WillPopScope( |
|||
onWillPop: _onWillPop, |
|||
child: new AnimatedBuilder( |
|||
animation: widget.hideController, |
|||
builder: _buildSlideAnimation, |
|||
child: new GestureDetector( |
|||
behavior: HitTestBehavior.opaque, |
|||
onTap: open, |
|||
child: new ScopedModelDescendant<AppStateModel>( |
|||
builder: (BuildContext context2, Widget child, AppStateModel model)=> { |
|||
return new AnimatedBuilder( |
|||
builder: _buildCart, |
|||
animation: _controller |
|||
); |
|||
} |
|||
) |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class ProductThumbnailRow : StatefulWidget { |
|||
|
|||
public override State createState() => new _ProductThumbnailRowState(); |
|||
} |
|||
|
|||
public class _ProductThumbnailRowState : State<ProductThumbnailRow> { |
|||
public readonly GlobalKey<AnimatedListState> _listKey = new GlobalKey<AnimatedListState>(); |
|||
|
|||
_ListModel _list; |
|||
List<int> _internalList; |
|||
|
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
_list = new _ListModel( |
|||
listKey: _listKey, |
|||
initialItems: ScopedModel.of<AppStateModel>(context).productsInCart.keys.toList(), |
|||
removedItemBuilder: _buildRemovedThumbnail |
|||
); |
|||
_internalList = new List<int>(_list.list); |
|||
} |
|||
|
|||
Product _productWithId(int productId) { |
|||
AppStateModel model = ScopedModel.of<AppStateModel>(context); |
|||
Product product = model.getProductById(productId); |
|||
D.assert(product != null); |
|||
return product; |
|||
} |
|||
|
|||
Widget _buildRemovedThumbnail(int item, BuildContext context, Animation<float> animation) { |
|||
return new ProductThumbnail(animation, animation, _productWithId(item)); |
|||
} |
|||
|
|||
Widget _buildThumbnail(BuildContext context, int index, Animation<float> animation) { |
|||
Animation<float> thumbnailSize = new Tween<float>(begin: 0.8f, end: 1.0f).animate( |
|||
new CurvedAnimation( |
|||
curve: new Interval(0.33f, 1.0f, curve: Curves.easeIn), |
|||
parent: animation |
|||
) |
|||
); |
|||
|
|||
Animation<float> opacity = new CurvedAnimation( |
|||
curve: new Interval(0.33f, 1.0f, curve: Curves.linear), |
|||
parent: animation |
|||
); |
|||
|
|||
return new ProductThumbnail(thumbnailSize, opacity, _productWithId(_list[index])); |
|||
} |
|||
|
|||
// If the lists are the same length, assume nothing has changed.
|
|||
// If the internalList is shorter than the ListModel, an item has been removed.
|
|||
// If the internalList is longer, then an item has been added.
|
|||
void _updateLists() { |
|||
// Update _internalList based on the model
|
|||
_internalList = ScopedModel.of<AppStateModel>(context).productsInCart.keys.toList(); |
|||
HashSet<int> internalSet = new HashSet<int>(_internalList); |
|||
HashSet<int> listSet = new HashSet<int>(_list.list); |
|||
|
|||
HashSet<int> difference = null; |
|||
foreach (var _set in internalSet) |
|||
{ |
|||
if (!listSet.Contains(_set)) |
|||
{ |
|||
difference.Add(_set); |
|||
} |
|||
} |
|||
if (difference.isEmpty()) { |
|||
return; |
|||
} |
|||
|
|||
foreach (int product in difference) { |
|||
if (_internalList.Count < _list.length) { |
|||
_list.remove(product); |
|||
} else if (_internalList.Count > _list.length) { |
|||
_list.add(product); |
|||
} |
|||
} |
|||
|
|||
while (_internalList.Count != _list.length) { |
|||
int index = 0; |
|||
// Check bounds and that the list elements are the same
|
|||
while (_internalList.isNotEmpty && |
|||
_list.length > 0 && |
|||
index < _internalList.Count && |
|||
index < _list.length && |
|||
_internalList[index] == _list[index]) { |
|||
index++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
Widget _buildAnimatedList() { |
|||
return new AnimatedList( |
|||
key: _listKey, |
|||
shrinkWrap: true, |
|||
itemBuilder: _buildThumbnail, |
|||
initialItemCount: _list.length, |
|||
scrollDirection: Axis.horizontal, |
|||
physics: new NeverScrollableScrollPhysics() // Cart shouldn't scroll
|
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
_updateLists(); |
|||
return new ScopedModelDescendant<AppStateModel>( |
|||
builder: (BuildContext context2, Widget child, AppStateModel model) => _buildAnimatedList() |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class ExtraProductsNumber : StatelessWidget { |
|||
int _calculateOverflow(AppStateModel model) { |
|||
Dictionary<int, int> productMap = model.productsInCart; |
|||
List<int> products = productMap.Keys.ToList(); |
|||
int overflow = 0; |
|||
int numProducts = products.Count; |
|||
if (numProducts > 3) { |
|||
for (int i = 3; i < numProducts; i++) { |
|||
overflow += productMap[products[i]]; |
|||
} |
|||
} |
|||
return overflow; |
|||
} |
|||
|
|||
Widget _buildOverflow(AppStateModel model, BuildContext context) { |
|||
if (model.productsInCart.Count <= 3) |
|||
return new Container(); |
|||
|
|||
int numOverflowProducts = _calculateOverflow(model); |
|||
int displayedOverflowProducts = numOverflowProducts <= 99 ? numOverflowProducts : 99; |
|||
return new Container( |
|||
child: new Text( |
|||
$"+{displayedOverflowProducts}", |
|||
style: Theme.of(context).primaryTextTheme.button |
|||
) |
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new ScopedModelDescendant<AppStateModel>( |
|||
builder: (BuildContext builder, Widget child, AppStateModel model) => _buildOverflow(model, context), |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class ProductThumbnail : StatelessWidget { |
|||
public ProductThumbnail(Animation<float> animation, Animation<float> opacityAnimation, Product product) |
|||
{ |
|||
this.animation = animation; |
|||
this.opacityAnimation = opacityAnimation; |
|||
this.product = product; |
|||
} |
|||
|
|||
public readonly Animation<float> animation; |
|||
public readonly Animation<float> opacityAnimation; |
|||
public readonly Product product; |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new FadeTransition( |
|||
opacity: opacityAnimation, |
|||
child: new ScaleTransition( |
|||
scale: animation, |
|||
child: new Container( |
|||
width: 40.0f, |
|||
height: 40.0f, |
|||
decoration: new BoxDecoration( |
|||
image: new DecorationImage( |
|||
image: new ExactAssetImage( |
|||
product.assetName, |
|||
package: product.assetPackage |
|||
), |
|||
fit: BoxFit.cover |
|||
), |
|||
borderRadius: BorderRadius.all(Radius.circular(10.0f)) |
|||
), |
|||
margin: EdgeInsets.only(left: 16.0f) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class _ListModel { |
|||
public _ListModel( |
|||
GlobalKey<AnimatedListState> listKey, |
|||
Delegate removedItemBuilder, |
|||
IEnumerable<int> initialItems |
|||
) |
|||
{ |
|||
D.assert(listKey != null); |
|||
D.assert(removedItemBuilder != null); |
|||
_items = initialItems?.ToList() ?? new List<int>(); |
|||
} |
|||
|
|||
public readonly GlobalKey<AnimatedListState> listKey; |
|||
|
|||
//public readonly Delegate removedItemBuilder;
|
|||
public delegate Widget removedItemBuilder(int item, BuildContext context, Animation<float> animation); |
|||
public readonly List<int> _items; |
|||
|
|||
AnimatedListState _animatedList |
|||
{ |
|||
get |
|||
{ |
|||
return listKey.currentState; |
|||
} |
|||
} |
|||
|
|||
public void add(int product) { |
|||
_insert(_items.Count, product); |
|||
} |
|||
|
|||
public void _insert(int index, int item) { |
|||
_items.Insert(index, item); |
|||
_animatedList.insertItem(index, duration: Duration(milliseconds: 225)); |
|||
} |
|||
|
|||
public void remove(int product) { |
|||
int index = _items.IndexOf(product); |
|||
if (index >= 0) { |
|||
_removeAt(index); |
|||
} |
|||
} |
|||
|
|||
public void _removeAt(int index) { |
|||
_items.RemoveAt(index); |
|||
_animatedList.removeItem(index, (BuildContext context, Animation<float> animation) =>{ |
|||
return removedItemBuilder(removedItem, context, animation); |
|||
}); |
|||
|
|||
} |
|||
|
|||
public int length |
|||
{ |
|||
get |
|||
{ |
|||
return _items.Count; |
|||
} |
|||
} |
|||
|
|||
int operator [](int index) => _items[index]; |
|||
|
|||
int indexOf(int item) => _items.IndexOf(item); |
|||
|
|||
public List<int> list |
|||
{ |
|||
get |
|||
{ |
|||
return _items; |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
using System.Collections.Generic; |
|||
using UIWidgetsGallery.demo.shrine.model; |
|||
using UIWidgetsGallery.gallery; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine |
|||
{ |
|||
public class ProductPage : StatelessWidget { |
|||
public ProductPage(Category category = Category.all) |
|||
{ |
|||
this.category = category; |
|||
} |
|||
|
|||
public readonly Category category; |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new ScopedModelDescendant<AppStateModel>( |
|||
builder: (BuildContext context, Widget child, AppStateModel model) => { |
|||
return AsymmetricView(products: model.getProducts()); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public class HomePage : StatelessWidget { |
|||
public HomePage( |
|||
ExpandingBottomSheet expandingBottomSheet = null, |
|||
Backdrop backdrop = null, |
|||
Key key = null |
|||
) : base(key: key) |
|||
{ |
|||
this.expandingBottomSheet = expandingBottomSheet; |
|||
this.backdrop = backdrop; |
|||
} |
|||
|
|||
public readonly ExpandingBottomSheet expandingBottomSheet; |
|||
public readonly Backdrop backdrop; |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new Stack( |
|||
children: new List<Widget> |
|||
{ |
|||
backdrop, |
|||
new Align(child: expandingBottomSheet, alignment: Alignment.bottomRight), |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
using System.Collections.Generic; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using Image = Unity.UIWidgets.widgets.Image; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine |
|||
{ |
|||
public class LoginPage : StatefulWidget { |
|||
|
|||
public override State createState() => new _LoginPageState(); |
|||
} |
|||
|
|||
public class _LoginPageState : State<LoginPage> { |
|||
public readonly TextEditingController _usernameController = new TextEditingController(); |
|||
public readonly TextEditingController _passwordController = new TextEditingController(); |
|||
static ShapeDecoration _decoration = new ShapeDecoration( |
|||
shape: new BeveledRectangleBorder( |
|||
side: new BorderSide(color: shrineColorsUtils.kShrineBrown900, width: 0.5f), |
|||
borderRadius: BorderRadius.all(Radius.circular(7.0f)) |
|||
) |
|||
); |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new Scaffold( |
|||
appBar: new AppBar( |
|||
elevation: 0.0f, |
|||
backgroundColor: Colors.white, |
|||
brightness: Brightness.light, |
|||
leading: new IconButton( |
|||
icon: new BackButtonIcon(), |
|||
tooltip: MaterialLocalizations.of(context).backButtonTooltip, |
|||
onPressed: () => { |
|||
Navigator.of(context, rootNavigator: true).pop(); |
|||
} |
|||
) |
|||
), |
|||
body: new SafeArea( |
|||
child: new ListView( |
|||
padding: EdgeInsets.symmetric(horizontal: 24.0f), |
|||
children: new List<Widget>{ |
|||
new SizedBox(height: 80.0f), |
|||
new Column( |
|||
children: new List<Widget> |
|||
{ |
|||
Image.asset("packages/shrine_images/diamond.png"), |
|||
new SizedBox(height: 16.0f), |
|||
new Text( |
|||
"SHRINE", |
|||
style: Theme.of(context).textTheme.headline5 |
|||
), |
|||
} |
|||
), |
|||
new SizedBox(height: 120.0f), |
|||
new PrimaryColorOverride( |
|||
color: shrineColorsUtils.kShrineBrown900, |
|||
child: new Container( |
|||
decoration: _decoration, |
|||
child: new TextField( |
|||
controller: _usernameController, |
|||
decoration: new InputDecoration( |
|||
labelText: "Username" |
|||
) |
|||
) |
|||
) |
|||
), |
|||
new SizedBox(height: 12.0f), |
|||
new PrimaryColorOverride( |
|||
color: shrineColorsUtils.kShrineBrown900, |
|||
child: new Container( |
|||
decoration: _decoration, |
|||
child: new TextField( |
|||
controller: _passwordController, |
|||
decoration: new InputDecoration( |
|||
labelText: "Password" |
|||
) |
|||
) |
|||
) |
|||
), |
|||
new Wrap( |
|||
children: new List<Widget>{ |
|||
new ButtonBar( |
|||
children:new List<Widget>{ |
|||
new FlatButton( |
|||
child: new Text("CANCEL"), |
|||
shape: new BeveledRectangleBorder( |
|||
borderRadius: BorderRadius.all(Radius.circular(7.0f)) |
|||
), |
|||
onPressed: () => { |
|||
Navigator.of(context, rootNavigator: true).pop(); |
|||
} |
|||
), |
|||
new RaisedButton( |
|||
child: new Text("NEXT"), |
|||
elevation: 8.0f, |
|||
shape: new BeveledRectangleBorder( |
|||
borderRadius: BorderRadius.all(Radius.circular(7.0f)) |
|||
), |
|||
onPressed: () => { |
|||
Navigator.pop(context); |
|||
} |
|||
) |
|||
} |
|||
), |
|||
} |
|||
), |
|||
} |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class PrimaryColorOverride : StatelessWidget { |
|||
public PrimaryColorOverride(Key key = null, Color color = null, Widget child = null) : base(key: key) |
|||
{ |
|||
this.child = child; |
|||
this.color = color; |
|||
} |
|||
|
|||
public readonly Color color; |
|||
public readonly Widget child; |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new Theme( |
|||
child: child, |
|||
data: Theme.of(context).copyWith(primaryColor: color) |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
using System.Collections.Generic; |
|||
using UIWidgetsGallery.demo.shrine.model; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using Image = Unity.UIWidgets.widgets.Image; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine |
|||
{ |
|||
|
|||
public class shopping_cartUtils |
|||
{ |
|||
public static readonly float _leftColumnWidth = 60.0f; |
|||
} |
|||
|
|||
|
|||
class ShoppingCartPage : StatefulWidget { |
|||
|
|||
public override State createState() => new _ShoppingCartPageState(); |
|||
} |
|||
|
|||
public class _ShoppingCartPageState : State<ShoppingCartPage> { |
|||
List<Widget> _createShoppingCartRows(AppStateModel model) { |
|||
return model.productsInCart.Keys |
|||
.map((int id) => new ShoppingCartRow( |
|||
product: model.getProductById(id), |
|||
quantity: model.productsInCart[id], |
|||
onPressed: () =>{ |
|||
model.removeItemFromCart(id); |
|||
} |
|||
) |
|||
) |
|||
.toList(); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
ThemeData localTheme = Theme.of(context); |
|||
|
|||
return new Scaffold( |
|||
backgroundColor: shrineColorsUtils.kShrinePink50, |
|||
body: new SafeArea( |
|||
child: new Container( |
|||
child: new ScopedModelDescendant<AppStateModel>( |
|||
builder: (BuildContext context2, Widget child, AppStateModel model) => { |
|||
return new Stack( |
|||
children: new List<Widget>{ |
|||
new ListView( |
|||
children: new List<Widget>{ |
|||
new Row( |
|||
children: new List<Widget>{ |
|||
new SizedBox( |
|||
width: shopping_cartUtils._leftColumnWidth, |
|||
child: new IconButton( |
|||
icon: new Icon(Icons.keyboard_arrow_down), |
|||
onPressed: () => ExpandingBottomSheet.of(context).close() |
|||
) |
|||
), |
|||
new Text( |
|||
"CART", |
|||
style: localTheme.textTheme.subtitle1.copyWith(fontWeight: FontWeight.w600) |
|||
), |
|||
new SizedBox(width: 16.0f), |
|||
new Text($"{model.totalCartQuantity} ITEMS") |
|||
} |
|||
), |
|||
new SizedBox(height: 16.0f), |
|||
new Column( |
|||
children: _createShoppingCartRows(model) |
|||
), |
|||
new ShoppingCartSummary(model: model), |
|||
new SizedBox(height: 100.0f) |
|||
} |
|||
), |
|||
new Positioned( |
|||
bottom: 16.0f, |
|||
left: 16.0f, |
|||
right: 16.0f, |
|||
child: new RaisedButton( |
|||
shape: new BeveledRectangleBorder( |
|||
borderRadius: BorderRadius.all(Radius.circular(7.0f)) |
|||
), |
|||
color: shrineColorsUtils.kShrinePink100, |
|||
splashColor: shrineColorsUtils.kShrineBrown600, |
|||
child: new Padding( |
|||
padding: EdgeInsets.symmetric(vertical: 12.0f), |
|||
child: new Text("CLEAR CART") |
|||
), |
|||
onPressed: () => { |
|||
model.clearCart(); |
|||
ExpandingBottomSheet.of(context).close(); |
|||
} |
|||
) |
|||
) |
|||
} |
|||
); |
|||
} |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class ShoppingCartSummary : StatelessWidget { |
|||
public ShoppingCartSummary(AppStateModel model) |
|||
{ |
|||
this.model = model; |
|||
} |
|||
|
|||
public readonly AppStateModel model; |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
TextStyle smallAmountStyle = Theme.of(context).textTheme.bodyText2.copyWith(color: shrineColorsUtils.kShrineBrown600); |
|||
TextStyle largeAmountStyle = Theme.of(context).textTheme.headline4; |
|||
NumberFormat formatter = NumberFormat.simpleCurrency( |
|||
decimalDigits: 2, |
|||
locale: Localizations.localeOf(context).ToString() |
|||
); |
|||
|
|||
return new Row( |
|||
children: new List<Widget> |
|||
{ |
|||
new SizedBox(width: shopping_cartUtils._leftColumnWidth), |
|||
new Expanded( |
|||
child: new Padding( |
|||
padding: EdgeInsets.only(right: 16.0f), |
|||
child: new Column( |
|||
children: new List<Widget> |
|||
{ |
|||
new Row( |
|||
crossAxisAlignment: CrossAxisAlignment.center, |
|||
children: new List<Widget> |
|||
{ |
|||
new Expanded( |
|||
child: new Text("TOTAL") |
|||
), |
|||
new Text( |
|||
formatter.format(model.totalCost), |
|||
style: largeAmountStyle |
|||
) |
|||
} |
|||
), |
|||
new SizedBox(height: 16.0f), |
|||
new Row( |
|||
children: new List<Widget> |
|||
{ |
|||
new Expanded( |
|||
child: new Text("Subtotal:") |
|||
), |
|||
new Text( |
|||
formatter.format(model.subtotalCost), |
|||
style: smallAmountStyle |
|||
) |
|||
} |
|||
), |
|||
new SizedBox(height: 4.0f), |
|||
new Row( |
|||
children: new List<Widget> |
|||
{ |
|||
new Expanded( |
|||
child: new Text("Shipping:") |
|||
), |
|||
new Text( |
|||
formatter.format(model.shippingCost), |
|||
style: smallAmountStyle |
|||
) |
|||
} |
|||
), |
|||
new SizedBox(height: 4.0f), |
|||
new Row( |
|||
children: new List<Widget> |
|||
{ |
|||
|
|||
new Expanded( |
|||
child: new Text("Tax:") |
|||
), |
|||
new Text( |
|||
formatter.format(model.tax), |
|||
style: smallAmountStyle |
|||
) |
|||
} |
|||
) |
|||
} |
|||
) |
|||
) |
|||
) |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class ShoppingCartRow : StatelessWidget { |
|||
public ShoppingCartRow( |
|||
Product product = null, |
|||
int? quantity = null, |
|||
VoidCallback onPressed = null |
|||
) |
|||
{ |
|||
this.product = product; |
|||
this.quantity = quantity?? 0; |
|||
this.onPressed = onPressed; |
|||
} |
|||
|
|||
public readonly Product product; |
|||
public readonly int quantity; |
|||
public readonly VoidCallback onPressed; |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
NumberFormat formatter = NumberFormat.simpleCurrency( |
|||
decimalDigits: 0, |
|||
locale: Localizations.localeOf(context).ToString() |
|||
); |
|||
ThemeData localTheme = Theme.of(context); |
|||
|
|||
return new Padding( |
|||
padding: EdgeInsets.only(bottom: 16.0f), |
|||
child: new Row( |
|||
key: ValueKey<int>(product.id), |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: new List<Widget>{ |
|||
new SizedBox( |
|||
width: shopping_cartUtils._leftColumnWidth, |
|||
child: new IconButton( |
|||
icon: new Icon(Icons.remove_circle_outline), |
|||
onPressed: onPressed |
|||
) |
|||
), |
|||
new Expanded( |
|||
child: new Padding( |
|||
padding: EdgeInsets.only(right: 16.0f), |
|||
child: new Column( |
|||
children: new List<Widget>{ |
|||
new Row( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: new List<Widget>{ |
|||
Image.asset( |
|||
product.assetName, |
|||
package: product.assetPackage, |
|||
fit: BoxFit.cover, |
|||
width: 75.0f, |
|||
height: 75.0f |
|||
), |
|||
new SizedBox(width: 16.0f), |
|||
new Expanded( |
|||
child: new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: new List<Widget>{ |
|||
new Row( |
|||
children: new List<Widget>{ |
|||
new Expanded( |
|||
child: new Text($"Quantity: {quantity}"), |
|||
), |
|||
new Text($"x {formatter.format(product.price)}") |
|||
} |
|||
), |
|||
new Text( |
|||
product.name, |
|||
style: localTheme.textTheme.subtitle1.copyWith(fontWeight: FontWeight.w600) |
|||
) |
|||
} |
|||
) |
|||
) |
|||
} |
|||
), |
|||
new SizedBox(height: 16.0f), |
|||
new Divider( |
|||
color: shrineColorsUtils.kShrineBrown900, |
|||
height: 10.0f |
|||
) |
|||
} |
|||
) |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UIWidgetsGallery.demo.shrine.model; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine.supplemental |
|||
{ |
|||
public class AsymmetricView : StatelessWidget { |
|||
public AsymmetricView(Key key = null, List<Product> products = null) : base(key: key) |
|||
{ |
|||
this.products = products; |
|||
} |
|||
|
|||
public readonly List<Product> products; |
|||
|
|||
List<Container> _buildColumns(BuildContext context) { |
|||
if (products == null || products.isEmpty()) { |
|||
return new List<Container>(); |
|||
} |
|||
|
|||
List<Container> list = new List<Container>(); |
|||
var len = _listItemCount(products.Count()); |
|||
for (int index = 0; index < len; index++) |
|||
{ |
|||
float width = 0.59f * MediaQuery.of(context).size.width; |
|||
Widget column; |
|||
if (index % 2 == 0) { |
|||
int bottom = _evenCasesIndex(index); |
|||
column = new TwoProductCardColumn( |
|||
bottom: products[bottom], |
|||
top: products.Count - 1 >= bottom + 1 |
|||
? products[bottom + 1] |
|||
: null |
|||
); |
|||
width += 32.0f; |
|||
} else { |
|||
column = new OneProductCardColumn( |
|||
product: products[_oddCasesIndex(index)] |
|||
); |
|||
} |
|||
list.Add(new Container( |
|||
width: width, |
|||
child: new Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: 16.0f), |
|||
child: column |
|||
) |
|||
)); |
|||
} |
|||
|
|||
return list; |
|||
} |
|||
|
|||
int _evenCasesIndex(int input) { |
|||
return input / 2 * 3; |
|||
} |
|||
|
|||
int _oddCasesIndex(int input) { |
|||
D.assert(input > 0); |
|||
return (input / 2) * 3 - 1; |
|||
} |
|||
|
|||
int _listItemCount(int totalItems) { |
|||
return (totalItems % 3 == 0) |
|||
? totalItems / 3 * 2 |
|||
: (totalItems / 3) * 2 - 1; |
|||
} |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new ListView( |
|||
scrollDirection: Axis.horizontal, |
|||
padding: EdgeInsets.fromLTRB(0.0f, 34.0f, 16.0f, 44.0f), |
|||
children: _buildColumns(context), |
|||
physics: new AlwaysScrollableScrollPhysics() |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.ui; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine.supplemental |
|||
{ |
|||
public class CutCornersBorder : OutlineInputBorder { |
|||
public CutCornersBorder( |
|||
BorderSide borderSide = null, |
|||
BorderRadius borderRadius = null, |
|||
float cut = 7.0f, |
|||
float gapPadding = 2.0f |
|||
) : base( |
|||
borderSide: borderSide?? BorderSide.none, |
|||
borderRadius: borderRadius?? BorderRadius.all(Radius.circular(2.0f)), |
|||
gapPadding: gapPadding |
|||
) |
|||
{ |
|||
this.cut = cut; |
|||
} |
|||
|
|||
|
|||
public CutCornersBorder copyWith( |
|||
BorderSide borderSide = null, |
|||
BorderRadius borderRadius = null, |
|||
float? gapPadding = null, |
|||
float? cut = null |
|||
) { |
|||
return new CutCornersBorder( |
|||
borderSide: borderSide ?? this.borderSide, |
|||
borderRadius: borderRadius ?? this.borderRadius, |
|||
gapPadding: gapPadding ?? this.gapPadding, |
|||
cut: cut ?? this.cut |
|||
); |
|||
} |
|||
|
|||
public readonly float cut; |
|||
|
|||
|
|||
public override ShapeBorder lerpFrom(ShapeBorder a, float t) { |
|||
if (a is CutCornersBorder) { |
|||
CutCornersBorder outline = (CutCornersBorder)a; |
|||
return new CutCornersBorder( |
|||
borderRadius: BorderRadius.lerp(outline.borderRadius, borderRadius, t), |
|||
borderSide: BorderSide.lerp(outline.borderSide, borderSide, t), |
|||
cut: cut, |
|||
gapPadding: outline.gapPadding |
|||
); |
|||
} |
|||
return base.lerpFrom(a, t); |
|||
} |
|||
|
|||
|
|||
public override ShapeBorder lerpTo(ShapeBorder b, float t) { |
|||
if (b is CutCornersBorder) { |
|||
CutCornersBorder outline = (CutCornersBorder)b; |
|||
return new CutCornersBorder( |
|||
borderRadius: BorderRadius.lerp(borderRadius, outline.borderRadius, t), |
|||
borderSide: BorderSide.lerp(borderSide, outline.borderSide, t), |
|||
cut: cut, |
|||
gapPadding: outline.gapPadding |
|||
); |
|||
} |
|||
return base.lerpTo(b, t); |
|||
} |
|||
|
|||
Path _notchedCornerPath(Rect center, float start = 0.0f, float extent = 0.0f) { |
|||
Path path = new Path(); |
|||
if (start > 0.0 || extent > 0.0) { |
|||
path.relativeMoveTo(extent + start, center.top); |
|||
_notchedSidesAndBottom(center, path); |
|||
path.lineTo(center.left + cut, center.top); |
|||
path.lineTo(start, center.top); |
|||
} else { |
|||
path.moveTo(center.left + cut, center.top); |
|||
_notchedSidesAndBottom(center, path); |
|||
path.lineTo(center.left + cut, center.top); |
|||
} |
|||
return path; |
|||
} |
|||
|
|||
Path _notchedSidesAndBottom(Rect center, Path path) |
|||
{ |
|||
path.lineTo(center.right - cut, center.top); |
|||
path.lineTo(center.right, center.top + cut); |
|||
path.lineTo(center.right, center.top + center.height - cut); |
|||
path.lineTo(center.right - cut, center.top + center.height); |
|||
path.lineTo(center.left + cut, center.top + center.height); |
|||
path.lineTo(center.left, center.top + center.height - cut); |
|||
path.lineTo(center.left, center.top + cut); |
|||
return path; |
|||
} |
|||
|
|||
|
|||
public void paint( |
|||
Canvas canvas, |
|||
Rect rect, |
|||
float gapStart, |
|||
float gapExtent = 0.0f, |
|||
float gapPercentage = 0.0f, |
|||
TextDirection? textDirection = null |
|||
) { |
|||
D.assert(gapPercentage >= 0.0 && gapPercentage <= 1.0); |
|||
|
|||
Paint paint = borderSide.toPaint(); |
|||
RRect outer = borderRadius.toRRect(rect); |
|||
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) { |
|||
canvas.drawPath(_notchedCornerPath(outer.middleRect), paint); |
|||
} else { |
|||
float extent = lerpFloat(0.0f, gapExtent + gapPadding * 2.0f, gapPercentage)?? 0.0f; |
|||
switch (textDirection) { |
|||
case TextDirection.rtl: { |
|||
Path path = _notchedCornerPath(outer.middleRect, gapStart + gapPadding - extent, extent); |
|||
canvas.drawPath(path, paint); |
|||
break; |
|||
} |
|||
case TextDirection.ltr: { |
|||
Path path = _notchedCornerPath(outer.middleRect, gapStart - gapPadding, extent); |
|||
canvas.drawPath(path, paint); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
float? lerpFloat(float? a, float? b, float t) { |
|||
if (a == null && b == null) |
|||
return null; |
|||
a = a?? 0.0f; |
|||
b = b?? 0.0f; |
|||
return a + (b - a) * t; |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|
|||
using System.Collections.Generic; |
|||
using UIWidgetsGallery.demo.shrine.model; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine.supplemental |
|||
{ |
|||
|
|||
public static class product_cardutils |
|||
{ |
|||
public static readonly float kTextBoxHeight = 65.0f; |
|||
} |
|||
public class ProductCard : StatelessWidget { |
|||
public ProductCard(float imageAspectRatio = 33f / 49f, Product product = null) |
|||
{ |
|||
D.assert(imageAspectRatio > 0); |
|||
this.imageAspectRatio = imageAspectRatio; |
|||
this.product = product; |
|||
} |
|||
|
|||
public readonly float imageAspectRatio; |
|||
public readonly Product product; |
|||
|
|||
|
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
NumberFormat formatter = NumberFormat.simpleCurrency( |
|||
decimalDigits: 0, |
|||
locale: Localizations.localeOf(context).ToString() |
|||
); |
|||
|
|||
ThemeData theme = Theme.of(context); |
|||
|
|||
Image imageWidget = Image.asset( |
|||
product.assetName, |
|||
package: product.assetPackage, |
|||
fit: BoxFit.cover |
|||
); |
|||
|
|||
return new ScopedModelDescendant<AppStateModel>( |
|||
builder: (BuildContext context2, Widget child, AppStateModel model) => |
|||
{ |
|||
return new GestureDetector( |
|||
onTap: () => |
|||
{ |
|||
model.addProductToCart(product.id); |
|||
}, |
|||
child: child |
|||
); |
|||
}, |
|||
child: new Stack( |
|||
children: new List<Widget> |
|||
{ |
|||
new Column( |
|||
mainAxisAlignment: MainAxisAlignment.center, |
|||
crossAxisAlignment: CrossAxisAlignment.center, |
|||
children: new List<Widget> |
|||
{ |
|||
new AspectRatio( |
|||
aspectRatio: imageAspectRatio, |
|||
child: imageWidget |
|||
), |
|||
new SizedBox( |
|||
height: product_cardutils.kTextBoxHeight * MediaQuery.of(context).textScaleFactor, |
|||
width: 121.0f, |
|||
child: new Column( |
|||
mainAxisAlignment: MainAxisAlignment.end, |
|||
crossAxisAlignment: CrossAxisAlignment.center, |
|||
children: new List<Widget> |
|||
{ |
|||
new Text( |
|||
product == null ? "" : product.name, |
|||
style: theme.textTheme.button, |
|||
softWrap: false, |
|||
overflow: TextOverflow.ellipsis, |
|||
maxLines: 1 |
|||
), |
|||
new SizedBox(height: 4.0f), |
|||
new Text( |
|||
product == null ? "" : formatter.format(product.price), |
|||
style: theme.textTheme.caption |
|||
), |
|||
} |
|||
) |
|||
) |
|||
} |
|||
), |
|||
new Padding( |
|||
padding: EdgeInsets.all(16.0f), |
|||
child: new Icon(Icons.add_shopping_cart) |
|||
), |
|||
} |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
using System.Collections.Generic; |
|||
using UIWidgetsGallery.demo.shrine.model; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsGallery.demo.shrine.supplemental |
|||
{ |
|||
public class TwoProductCardColumn : StatelessWidget { |
|||
|
|||
public TwoProductCardColumn(Product bottom = null, Product top = null) |
|||
{ |
|||
D.assert(bottom != null); |
|||
this.bottom = bottom; |
|||
this.top = top; |
|||
} |
|||
|
|||
public readonly Product bottom, top; |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new LayoutBuilder(builder: (BuildContext context1, BoxConstraints constraints) => { |
|||
float spacerHeight = 44.0f; |
|||
float heightOfCards = (constraints.biggest.height - spacerHeight) / 2.0f; |
|||
float availableHeightForImages = heightOfCards - product_cardutils.kTextBoxHeight; |
|||
float imageAspectRatio = availableHeightForImages >= 0.0 |
|||
? constraints.biggest.width / availableHeightForImages |
|||
: 49.0f / 33.0f; |
|||
|
|||
return new ListView( |
|||
physics:new ClampingScrollPhysics(), |
|||
children: new List<Widget> |
|||
{ |
|||
new Padding( |
|||
padding: EdgeInsets.only(left: 28.0f), |
|||
child: top != null |
|||
? new ProductCard( |
|||
imageAspectRatio: imageAspectRatio, |
|||
product: top |
|||
) |
|||
: new SizedBox( |
|||
height: heightOfCards > 0 ? heightOfCards : spacerHeight |
|||
) |
|||
), |
|||
new SizedBox(height: spacerHeight), |
|||
new Padding( |
|||
padding: EdgeInsets.only(right: 28.0f), |
|||
child: new ProductCard( |
|||
imageAspectRatio: imageAspectRatio, |
|||
product: bottom |
|||
) |
|||
), |
|||
} |
|||
); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public class OneProductCardColumn : StatelessWidget { |
|||
public OneProductCardColumn(Product product = null) |
|||
{ |
|||
this.product = product; |
|||
} |
|||
|
|||
public readonly Product product; |
|||
|
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new ListView( |
|||
physics: new ClampingScrollPhysics(), |
|||
reverse: true, |
|||
children: new List<Widget> |
|||
{ |
|||
new SizedBox( |
|||
height: 40.0f |
|||
), |
|||
new ProductCard( |
|||
product: product |
|||
), |
|||
} |
|||
); |
|||
} |
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue