1
0
mirror of https://github.com/flutter/samples.git synced 2026-05-28 09:59:29 +00:00

Moving Shrine into a material_studies subdirectory (#128)

This commit is contained in:
Andrew Brogdon
2019-08-14 16:27:59 -07:00
committed by GitHub
parent c68165e8a5
commit cf270c32f3
84 changed files with 25 additions and 5 deletions

View File

@@ -0,0 +1,96 @@
// Copyright 2018-present the Flutter authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'package:flutter/material.dart';
import '../model/product.dart';
import 'product_columns.dart';
class AsymmetricView extends StatelessWidget {
final List<Product> products;
const AsymmetricView({Key key, this.products});
List<Container> _buildColumns(BuildContext context) {
if (products == null || products.isEmpty) {
return const <Container>[];
}
/// This will return a list of columns. It will oscillate between the two
/// kinds of columns. Even cases of the index (0, 2, 4, etc) will be
/// TwoProductCardColumn and the odd cases will be OneProductCardColumn.
///
/// Each pair of columns will advance us 3 products forward (2 + 1). That's
/// some kinda awkward math so we use _evenCasesIndex and _oddCasesIndex as
/// helpers for creating the index of the product list that will correspond
/// to the index of the list of columns.
return List.generate(_listItemCount(products.length), (index) {
double width = .59 * MediaQuery.of(context).size.width;
Widget column;
if (index % 2 == 0) {
/// Even cases
int bottom = _evenCasesIndex(index);
column = TwoProductCardColumn(
bottom: products[bottom],
top: products.length - 1 >= bottom + 1
? products[bottom + 1]
: null);
width += 32.0;
} else {
/// Odd cases
column = OneProductCardColumn(
product: products[_oddCasesIndex(index)],
);
}
return Container(
width: width,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: column,
),
);
}).toList();
}
int _evenCasesIndex(int input) {
/// The operator ~/ is a cool one. It's the truncating division operator. It
/// divides the number and if there's a remainder / decimal, it cuts it off.
/// This is like dividing and then casting the result to int. Also, it's
/// functionally equivalent to floor() in this case.
return input ~/ 2 * 3;
}
int _oddCasesIndex(int input) {
assert(input > 0);
return (input / 2).ceil() * 3 - 1;
}
int _listItemCount(int totalItems) {
if (totalItems % 3 == 0) {
return totalItems ~/ 3 * 2;
} else {
return (totalItems / 3).ceil() * 2 - 1;
}
}
@override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.fromLTRB(0.0, 34.0, 16.0, 44.0),
children: _buildColumns(context),
physics: AlwaysScrollableScrollPhysics(),
);
}
}

View File

@@ -0,0 +1,139 @@
// Copyright 2018-present the Flutter authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:ui' show lerpDouble;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class CutCornersBorder extends OutlineInputBorder {
const CutCornersBorder({
BorderSide borderSide = const BorderSide(),
BorderRadius borderRadius = const BorderRadius.all(Radius.circular(2.0)),
this.cut = 7.0,
double gapPadding = 2.0,
}) : super(
borderSide: borderSide,
borderRadius: borderRadius,
gapPadding: gapPadding);
@override
CutCornersBorder copyWith({
BorderSide borderSide,
BorderRadius borderRadius,
double gapPadding,
double cut,
}) {
return CutCornersBorder(
borderRadius: borderRadius ?? this.borderRadius,
borderSide: borderSide ?? this.borderSide,
cut: cut ?? this.cut,
gapPadding: gapPadding ?? this.gapPadding,
);
}
final double cut;
@override
ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is CutCornersBorder) {
final CutCornersBorder outline = a;
return CutCornersBorder(
borderRadius: BorderRadius.lerp(outline.borderRadius, borderRadius, t),
borderSide: BorderSide.lerp(outline.borderSide, borderSide, t),
cut: cut,
gapPadding: outline.gapPadding,
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is CutCornersBorder) {
final CutCornersBorder outline = b;
return CutCornersBorder(
borderRadius: BorderRadius.lerp(borderRadius, outline.borderRadius, t),
borderSide: BorderSide.lerp(borderSide, outline.borderSide, t),
cut: cut,
gapPadding: outline.gapPadding,
);
}
return super.lerpTo(b, t);
}
Path _notchedCornerPath(Rect center,
[double start = 0.0, double extent = 0.0]) {
final Path path = Path();
if (start > 0.0 || extent > 0.0) {
path.relativeMoveTo(extent + start, center.top);
_notchedSidesAndBottom(center, path);
path..lineTo(center.left + cut, center.top)..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) {
return path
..lineTo(center.right - cut, center.top)
..lineTo(center.right, center.top + cut)
..lineTo(center.right, center.top + center.height - cut)
..lineTo(center.right - cut, center.top + center.height)
..lineTo(center.left + cut, center.top + center.height)
..lineTo(center.left, center.top + center.height - cut)
..lineTo(center.left, center.top + cut);
}
@override
void paint(
Canvas canvas,
Rect rect, {
double gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection textDirection,
}) {
assert(gapExtent != null);
assert(gapPercentage >= 0.0 && gapPercentage <= 1.0);
final Paint paint = borderSide.toPaint();
final RRect outer = borderRadius.toRRect(rect);
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
canvas.drawPath(_notchedCornerPath(outer.middleRect), paint);
} else {
final double extent =
lerpDouble(0.0, gapExtent + gapPadding * 2.0, gapPercentage);
switch (textDirection) {
case TextDirection.rtl:
{
final Path path = _notchedCornerPath(
outer.middleRect, gapStart + gapPadding - extent, extent);
canvas.drawPath(path, paint);
break;
}
case TextDirection.ltr:
{
final Path path = _notchedCornerPath(
outer.middleRect, gapStart - gapPadding, extent);
canvas.drawPath(path, paint);
break;
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
// Copyright 2018-present the Flutter authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:scoped_model/scoped_model.dart';
import '../model/app_state_model.dart';
import '../model/product.dart';
class ProductCard extends StatelessWidget {
ProductCard({this.imageAspectRatio = 33 / 49, this.product})
: assert(imageAspectRatio == null || imageAspectRatio > 0);
final double imageAspectRatio;
final Product product;
static final kTextBoxHeight = 65.0;
@override
Widget build(BuildContext context) {
final NumberFormat formatter = NumberFormat.simpleCurrency(
decimalDigits: 0, locale: Localizations.localeOf(context).toString());
final ThemeData theme = Theme.of(context);
final imageWidget = Image.asset(
product.assetName,
package: product.assetPackage,
fit: BoxFit.cover,
);
return ScopedModelDescendant<AppStateModel>(
builder: (context, child, model) => GestureDetector(
onTap: () {
model.addProductToCart(product.id);
// TODO: Add Snackbar
},
child: child,
),
child: Stack(
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
AspectRatio(
aspectRatio: imageAspectRatio,
child: imageWidget,
),
SizedBox(
height: kTextBoxHeight * MediaQuery.of(context).textScaleFactor,
width: 121.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
product == null ? '' : product.name,
style: theme.textTheme.button,
softWrap: false,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
SizedBox(height: 4.0),
Text(
product == null ? '' : formatter.format(product.price),
style: theme.textTheme.caption,
),
],
),
),
],
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Icon(Icons.add_shopping_cart),
),
],
),
);
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2018-present the Flutter authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'package:flutter/material.dart';
import '../model/product.dart';
import 'product_card.dart';
class TwoProductCardColumn extends StatelessWidget {
TwoProductCardColumn({
this.bottom,
this.top,
}) : assert(bottom != null);
final Product bottom, top;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
const spacerHeight = 44.0;
double heightOfCards =
(constraints.biggest.height - spacerHeight) / 2.0;
double heightOfImages = heightOfCards - ProductCard.kTextBoxHeight;
double imageAspectRatio = (heightOfImages >= 0.0 &&
constraints.biggest.width > heightOfImages)
? constraints.biggest.width / heightOfImages
: 33 / 49;
return ListView(
children: <Widget>[
Padding(
padding: EdgeInsetsDirectional.only(start: 28.0),
child: top != null
? ProductCard(
imageAspectRatio: imageAspectRatio,
product: top,
)
: SizedBox(
height: heightOfCards > 0 ? heightOfCards : spacerHeight,
),
),
SizedBox(height: spacerHeight),
Padding(
padding: EdgeInsetsDirectional.only(end: 28.0),
child: ProductCard(
imageAspectRatio: imageAspectRatio,
product: bottom,
),
),
],
);
},
);
}
}
class OneProductCardColumn extends StatelessWidget {
OneProductCardColumn({this.product});
final Product product;
@override
Widget build(BuildContext context) {
return ListView(
reverse: true,
children: <Widget>[
SizedBox(
height: 40.0,
),
ProductCard(
product: product,
),
],
);
}
}