From d2c10ed2f1f5d2e9cf978ee4cfb771fc8608387e Mon Sep 17 00:00:00 2001 From: xuyisheng Date: Sat, 29 Feb 2020 13:23:07 +0800 Subject: [PATCH] add some demos and update readme --- README.md | 30 +- lib/animation/animation/curveline.dart | 170 ++++++ lib/modle/animation/animation_category.dart | 11 +- lib/modle/pattern/pattern_category.dart | 40 ++ lib/modle/uikit/uikit_category.dart | 8 + ...get_category_app_structure_navigation.dart | 2 +- lib/modle/widget/widget_category_async.dart | 10 +- .../widget_category_single_child_layout.dart | 3 +- lib/pattern/display/avatarlist.dart | 47 ++ lib/pattern/display/layerblendmode.dart | 144 ++++- lib/pattern/display/rating.dart | 46 ++ lib/pattern/list/backdrop.dart | 287 ++++++++- lib/pattern/list/sliverheader.dart | 82 +++ lib/pattern/list/sliverheaderwitheffect.dart | 137 +++++ lib/pattern/list/zoom.dart | 58 ++ lib/pattern/viewpager/pagereveal.dart | 549 ++++++++++++++++++ lib/uikit/keyboard.dart | 2 - lib/uikit/linemetrics.dart | 92 +++ lib/widgets/async/valuenotifier.dart | 61 ++ .../custommultichildlayout.dart | 157 ++--- lib/widgets/multichildlayout/flow.dart | 57 +- lib/widgets/paintingeffect/shadermask.dart | 2 +- .../customsinglechildlayout.dart | 42 ++ lib/widgets/styling/mediaquery.dart | 5 + lib/widgets/text/richtext.dart | 2 +- lib/widgets/touchinteractions/listener.dart | 5 +- pubspec.yaml | 8 + 27 files changed, 1908 insertions(+), 149 deletions(-) create mode 100644 lib/animation/animation/curveline.dart create mode 100644 lib/pattern/display/rating.dart create mode 100644 lib/pattern/list/sliverheader.dart create mode 100644 lib/pattern/list/sliverheaderwitheffect.dart create mode 100644 lib/pattern/list/zoom.dart create mode 100644 lib/pattern/viewpager/pagereveal.dart create mode 100644 lib/uikit/linemetrics.dart create mode 100644 lib/widgets/async/valuenotifier.dart create mode 100644 lib/widgets/singlechildlayout/customsinglechildlayout.dart diff --git a/README.md b/README.md index 020c13c..5864cbd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # flutter_dojo +[![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + Dojo,源自日语「道場」。 flutter_dojo项目的主要目的是为了帮助Flutter初学者快速入门Flutter开发,并借助该项目快速建立起Flutter的知识体系架构。 @@ -44,13 +46,13 @@ Back-end Util这部分主要是针对Flutter中的非UI场景知识点进行的 所有的展示界面,都以演示效果为目的,展示该Widget最主要的属性,并通过实例演示更改属性后的效果,举例如下。 -![](resource/6.png) + 修改对应的属性后,可以直接展示修改效果,从而了解到该属性的作用。 同时,Demo代码可以直接查看。 -![](resource/61.png) + 而且代码也可以直接分享出来,分享出来的代码几乎是可以直接Copy使用的代码。 @@ -58,51 +60,51 @@ Back-end Util这部分主要是针对Flutter中的非UI场景知识点进行的 Widgets部分包含了Flutter官方Category中的几乎所有Widget,是的你没看错,按照官网的Category划分,穷举了官方列出的所有Widget,同时也新增了一些未出现在Category中,但却很常用的Widget。 -![](resource/1.png) + 在每个Category中,都按照A-Z的顺序展示Widget。 -![](resource/11.png) + ## UI Pattern UI Pattern部分包含了APP中常用的界面开发模板元素,例如APPBar、Banner、Login、Setting等等,在UI Pattern中,开发者可以找到各自分解的UI元素,了解如何使用Flutter来构建这些UI组件。 -![](resource/2.png) + 在UI Pattern中,我分类列举了很多不同的模板类型。 -![](resource/21.png) + ## Develop UI Kit Develop UI Kit列举了UI开发中的一些常用工具类和开发模板代码,开发者可以使用这些工具类来完成一些UI功能开发。 -![](resource/3.png) + 在Develop UI Kit中,按照A-Z对相关代码进行了排列。 -![](resource/31.png) + ## Animations 由于动画在APP开发中的重要性,这里特地将Animations作为一项单独列出。开发者可以在这里找到不同的Animation开发模式,了解不同的Animation使用方法。 -![](resource/4.png) + 在Animations中,同样是根据不同的功能进行了划分。 -![](resource/41.png) + ## Back-end Util Back-end Util中列举了非UI的一些Flutter知识点。 -![](resource/5.png) + Flutter不仅仅是一个UI跨平台框架,同样是一个完整的APP开发框架,所以,这里列举了除了UI开发之外的一些功能。 -![](resource/51.png) + ## 协作 @@ -112,11 +114,11 @@ Flutter不仅仅是一个UI跨平台框架,同样是一个完整的APP开发 有兴趣一起协作的开发者可以添加我的微信。 -![](resource/7.jpeg) + 或者感兴趣的开发者可以加入我的Flutter修仙群,加我微信我拉你进群。 ## 打赏 -![](resource/8.png) + diff --git a/lib/animation/animation/curveline.dart b/lib/animation/animation/curveline.dart new file mode 100644 index 0000000..9fecf66 --- /dev/null +++ b/lib/animation/animation/curveline.dart @@ -0,0 +1,170 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_dojo/common/main_title_widget.dart'; +import 'package:flutter_dojo/common/subtitle_widget.dart'; + +class CurveLineWidget extends StatefulWidget { + @override + _CurveLineWidgetState createState() => _CurveLineWidgetState(); +} + +class _CurveLineWidgetState extends State with SingleTickerProviderStateMixin { + AnimationController _controller; + Animation _animation; + double _fraction = 0.0; + int _seconds = 3; + Path _path; + GlobalKey customPaintKey = GlobalKey(); + + @override + void initState() { + _controller = AnimationController( + vsync: this, + duration: Duration(seconds: _seconds), + ); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + MainTitleWidget('通过PathMetrics计算Path上的坐标'), + SubtitleWidget('通过getTangentForOffset获取点的Position'), + Expanded( + child: Container( + constraints: BoxConstraints.expand(), + child: CustomPaint( + key: customPaintKey, + painter: PathPainter(_path, _fraction), + ), + ), + ), + RaisedButton( + onPressed: startPathMotion, + child: Text('Start'), + ), + SubtitleWidget('通过extractPath截取Path片段'), + Expanded( + child: Container( + constraints: BoxConstraints.expand(), + child: CustomPaint( + painter: PathSegmentPainter(_path, _fraction), + ), + ), + ), + RaisedButton( + onPressed: startPathMotion, + child: Text('Start'), + ), + ], + ); + } + + Path _getPath(Size size) { + Path path = Path(); + path.moveTo(20, 20); + path.cubicTo( + size.width / 4, + size.height / 2, + size.width / 4 * 3, + size.height / 2, + size.width - 20, + size.height - 20, + ); + return path; + } + + startPathMotion() { + _controller.reset(); + RenderBox renderBox = customPaintKey.currentContext.findRenderObject(); + Size s = renderBox.size; + _path = _getPath(s); + PathMetrics pms = _path.computeMetrics(); + PathMetric pm = pms.elementAt(0); + double len = pm.length; + _animation = Tween(begin: 0.0, end: len).animate(_controller) + ..addListener(() { + setState(() => _fraction = _animation.value); + }) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + _controller.stop(); + } + }); + _controller.forward(); + } +} + +class PathPainter extends CustomPainter { + double _fraction; + Path _path; + + PathPainter(this._path, this._fraction); + + Paint _paint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke + ..strokeWidth = 4.0; + + Paint _paint2 = Paint() + ..color = Colors.red + ..style = PaintingStyle.fill; + + @override + void paint(Canvas canvas, Size size) { + if (_path == null) { + return; + } + PathMetrics pms = _path.computeMetrics(); + PathMetric pm = pms.elementAt(0); + Tangent t = pm.getTangentForOffset(_fraction); + canvas.drawPath(_path, _paint); + canvas.drawCircle(t.position, 10, _paint2); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} + +class PathSegmentPainter extends CustomPainter { + double _fraction; + Path _path; + + PathSegmentPainter(this._path, this._fraction); + + Paint _paint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke + ..strokeWidth = 4.0; + + @override + void paint(Canvas canvas, Size size) { + if (_path == null) { + return; + } + Path segmentPath = Path(); + + PathMetrics pms = _path.computeMetrics(); + PathMetric pm = pms.elementAt(0); + + var extractPath = pm.extractPath(0.0, _fraction); + segmentPath.addPath(extractPath, Offset(0, 0)); + + canvas.drawPath(segmentPath, _paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/modle/animation/animation_category.dart b/lib/modle/animation/animation_category.dart index 14c84f2..43ee234 100644 --- a/lib/modle/animation/animation_category.dart +++ b/lib/modle/animation/animation_category.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_dojo/animation/animation/bouncing.dart'; import 'package:flutter_dojo/animation/animation/curve.dart'; +import 'package:flutter_dojo/animation/animation/curveline.dart'; import 'package:flutter_dojo/animation/animation/focus.dart'; import 'package:flutter_dojo/animation/animation/sequence.dart'; import 'package:flutter_dojo/animation/animation/showup.dart'; @@ -19,6 +20,7 @@ import 'package:flutter_dojo/animation/scrollanimation/listtopbottom.dart'; import 'package:flutter_dojo/animation/scrollanimation/scrollinganimation1.dart'; import 'package:flutter_dojo/animation/scrollanimation/scrollinganimation2.dart'; import 'package:flutter_dojo/animation/scrollanimation/scrollinganimation3.dart'; +import 'package:flutter_dojo/animation/scrollanimation/scrollparallax.dart'; import 'package:flutter_dojo/animation/tween/slidecard.dart'; import 'package:flutter_dojo/animation/tween/testanim1.dart'; import 'package:flutter_dojo/animation/tween/testanim2.dart'; @@ -27,7 +29,6 @@ import 'package:flutter_dojo/animation/tween/tweenanimationbuilder.dart'; import 'package:flutter_dojo/common/base_widget.dart'; import 'package:flutter_dojo/common/demo_item.dart'; import 'package:flutter_dojo/pages/pattern/pattern_mainpage.dart'; -import 'package:flutter_dojo/animation/scrollanimation/scrollparallax.dart'; var codePath = 'lib/animation/'; @@ -120,6 +121,13 @@ List buildAnimationDemoItems(String codePath) { documentationUrl: '', buildRoute: (context) => BaseWidget('Curve', codePath, CurveWidget()), ), + DemoItem( + icon: Icons.date_range, + title: 'CurveLine', + subtitle: 'CurveLine', + documentationUrl: '', + buildRoute: (context) => BaseWidget('CurveLine', codePath, CurveLineWidget()), + ), DemoItem( icon: Icons.date_range, title: 'Focus', @@ -247,6 +255,7 @@ List buildBarChatDemoItems(String codePath) { ), ]; } + List buildScrollAnimationDemoItems(String codePath) { return [ DemoItem( diff --git a/lib/modle/pattern/pattern_category.dart b/lib/modle/pattern/pattern_category.dart index 6099911..c89b16c 100644 --- a/lib/modle/pattern/pattern_category.dart +++ b/lib/modle/pattern/pattern_category.dart @@ -16,13 +16,17 @@ import 'package:flutter_dojo/pattern/display/layer.dart'; import 'package:flutter_dojo/pattern/display/layerblendmode.dart'; import 'package:flutter_dojo/pattern/display/overflow.dart'; import 'package:flutter_dojo/pattern/display/popup.dart'; +import 'package:flutter_dojo/pattern/display/rating.dart'; import 'package:flutter_dojo/pattern/display/shadowmask.dart'; import 'package:flutter_dojo/pattern/display/toast.dart'; import 'package:flutter_dojo/pattern/gesture/gesturescale.dart'; import 'package:flutter_dojo/pattern/list/backdrop.dart'; import 'package:flutter_dojo/pattern/list/listdetail.dart'; import 'package:flutter_dojo/pattern/list/searchlist.dart'; +import 'package:flutter_dojo/pattern/list/sliverheader.dart'; +import 'package:flutter_dojo/pattern/list/sliverheaderwitheffect.dart'; import 'package:flutter_dojo/pattern/list/tree.dart'; +import 'package:flutter_dojo/pattern/list/zoom.dart'; import 'package:flutter_dojo/pattern/listitem/itemlayout.dart'; import 'package:flutter_dojo/pattern/login/login1.dart'; import 'package:flutter_dojo/pattern/perspective/flipcard.dart'; @@ -35,6 +39,7 @@ import 'package:flutter_dojo/pattern/viewpager/anim_slider.dart'; import 'package:flutter_dojo/pattern/viewpager/cardflip.dart'; import 'package:flutter_dojo/pattern/viewpager/guide.dart'; import 'package:flutter_dojo/pattern/viewpager/pagechangeanim.dart'; +import 'package:flutter_dojo/pattern/viewpager/pagereveal.dart'; import 'package:flutter_dojo/pattern/viewpager/parallaxviewpager.dart'; import 'package:flutter_dojo/pattern/viewpager/slider.dart'; import 'package:flutter_dojo/pattern/viewpager/transformslider.dart'; @@ -123,6 +128,13 @@ List buildViewPagerDemoItems(String codePath) { documentationUrl: '', buildRoute: (context) => BaseWidget('PageChangeAnim', codePath, PageChangeAnimWidget()), ), + DemoItem( + icon: Icons.pages, + title: 'PageReveal', + subtitle: 'PageReveal', + documentationUrl: '', + buildRoute: (context) => BaseWidget('PageReveal', codePath, PageRevealWidget()), + ), DemoItem( icon: Icons.pages, title: 'Slider', @@ -205,6 +217,13 @@ List buildWidgetDisplayDemoItems(String codePath) { documentationUrl: '', buildRoute: (context) => BaseWidget('Popup', codePath, PopupWidget()), ), + DemoItem( + icon: Icons.pages, + title: 'Rating', + subtitle: 'Rating', + documentationUrl: '', + buildRoute: (context) => BaseWidget('Rating', codePath, RatingWidget()), + ), DemoItem( icon: Icons.pages, title: 'ShadowMask', @@ -309,6 +328,20 @@ List buildListDemoItems(String codePath) { documentationUrl: '', buildRoute: (context) => BaseWidget('Backdrop', codePath, BackdropWidget()), ), + DemoItem( + icon: Icons.pages, + title: 'SliverHeader', + subtitle: 'SliverHeader', + documentationUrl: '', + buildRoute: (context) => BaseWidget('SliverHeader', codePath, SliverHeaderWidget()), + ), + DemoItem( + icon: Icons.pages, + title: 'SliverHeaderWithEffect', + subtitle: 'SliverHeaderWithEffect', + documentationUrl: '', + buildRoute: (context) => BaseWidget('SliverHeaderWithEffect', codePath, SliverHeaderWithEffectWidget()), + ), DemoItem( icon: Icons.pages, title: 'Tree', @@ -323,6 +356,13 @@ List buildListDemoItems(String codePath) { documentationUrl: '', buildRoute: (context) => BaseWidget('ListDetail', codePath, ListDetailWidget()), ), + DemoItem( + icon: Icons.pages, + title: 'Zoom', + subtitle: 'Zoom', + documentationUrl: '', + buildRoute: (context) => BaseWidget('Zoom', codePath, ZoomWidget()), + ), ]; } diff --git a/lib/modle/uikit/uikit_category.dart b/lib/modle/uikit/uikit_category.dart index 1a4d7f4..d34ca33 100644 --- a/lib/modle/uikit/uikit_category.dart +++ b/lib/modle/uikit/uikit_category.dart @@ -9,6 +9,7 @@ import 'package:flutter_dojo/uikit/drawtext.dart'; import 'package:flutter_dojo/uikit/feedback.dart'; import 'package:flutter_dojo/uikit/gesturepainter.dart'; import 'package:flutter_dojo/uikit/keyboard.dart'; +import 'package:flutter_dojo/uikit/linemetrics.dart'; import 'package:flutter_dojo/uikit/matrix4.dart'; import 'package:flutter_dojo/uikit/nstar.dart'; import 'package:flutter_dojo/uikit/position.dart'; @@ -73,6 +74,13 @@ List buildUIKitCategoryList = [ documentationUrl: '', buildRoute: (context) => BaseWidget('GesturePainter', codePath, GesturePainterWidget()), ), + DemoItem( + icon: Icons.pages, + title: 'LineMetrics', + subtitle: 'LineMetrics', + documentationUrl: '', + buildRoute: (context) => BaseWidget('LineMetrics', codePath, LineMetricsWidget()), + ), DemoItem( icon: Icons.pages, title: 'Matrix4', diff --git a/lib/modle/widget/widget_category_app_structure_navigation.dart b/lib/modle/widget/widget_category_app_structure_navigation.dart index eb2b8f8..3f246a8 100644 --- a/lib/modle/widget/widget_category_app_structure_navigation.dart +++ b/lib/modle/widget/widget_category_app_structure_navigation.dart @@ -9,10 +9,10 @@ import 'package:flutter_dojo/widgets/appstructurenavigation/drawer.dart'; import 'package:flutter_dojo/widgets/appstructurenavigation/safearea.dart'; import 'package:flutter_dojo/widgets/appstructurenavigation/searchdelegate.dart'; import 'package:flutter_dojo/widgets/appstructurenavigation/sliverappbar.dart'; +import 'package:flutter_dojo/widgets/appstructurenavigation/tabbar.dart'; import 'package:flutter_dojo/widgets/appstructurenavigation/tabpageselector.dart'; import 'package:flutter_dojo/widgets/appstructurenavigation/widgetsapp.dart'; import 'package:flutter_dojo/widgets/basic/scaffold.dart'; -import 'package:flutter_dojo/widgets/appstructurenavigation/tabbar.dart'; import 'package:flutter_dojo/widgets/styling/material.dart'; List buildAppStructureNavigationDemoItems(String codePath) { diff --git a/lib/modle/widget/widget_category_async.dart b/lib/modle/widget/widget_category_async.dart index dcbaf60..4f750fa 100644 --- a/lib/modle/widget/widget_category_async.dart +++ b/lib/modle/widget/widget_category_async.dart @@ -6,6 +6,7 @@ import 'package:flutter_dojo/widgets/async/inheritedmodel.dart'; import 'package:flutter_dojo/widgets/async/inheritedwidget.dart'; import 'package:flutter_dojo/widgets/async/streambuilder.dart'; import 'package:flutter_dojo/widgets/async/valuelistenablebuilder.dart'; +import 'package:flutter_dojo/widgets/async/valuenotifier.dart'; List buildAsyncDemoItems(String codePath) { return [ @@ -43,6 +44,13 @@ List buildAsyncDemoItems(String codePath) { subtitle: 'ValueListenableBuilder', documentationUrl: 'https://api.flutter.dev/flutter/widgets/ValueListenableBuilder-class.html', buildRoute: (context) => BaseWidget('ValueListenableBuilder', codePath, ValueListenableBuilderWidget()), - ) + ), + DemoItem( + icon: Icons.network_wifi, + title: 'ValueNotifier', + subtitle: 'ValueNotifier', + documentationUrl: 'https://api.flutter.dev/flutter/widgets/ValueNotifier-class.html', + buildRoute: (context) => BaseWidget('ValueNotifier', codePath, ValueNotifierWidget()), + ), ]; } diff --git a/lib/modle/widget/widget_category_single_child_layout.dart b/lib/modle/widget/widget_category_single_child_layout.dart index e6913cb..239fa5f 100644 --- a/lib/modle/widget/widget_category_single_child_layout.dart +++ b/lib/modle/widget/widget_category_single_child_layout.dart @@ -8,6 +8,7 @@ import 'package:flutter_dojo/widgets/singlechildlayout/center.dart'; import 'package:flutter_dojo/widgets/singlechildlayout/circleavatar.dart'; import 'package:flutter_dojo/widgets/singlechildlayout/constrainedbox.dart'; import 'package:flutter_dojo/widgets/singlechildlayout/container.dart'; +import 'package:flutter_dojo/widgets/singlechildlayout/customsinglechildlayout.dart'; import 'package:flutter_dojo/widgets/singlechildlayout/fittedbox.dart'; import 'package:flutter_dojo/widgets/singlechildlayout/fractionallysizedbox.dart'; import 'package:flutter_dojo/widgets/singlechildlayout/intrinsicheight.dart'; @@ -77,7 +78,7 @@ List buildSingleChildLayoutDemoItems(String codePath) { title: 'CustomSingleChildLayout', subtitle: 'A widget that defers the layout of its single child to a delegate.', documentationUrl: 'https://api.flutter.dev/flutter/widgets/CustomSingleChildLayout-class.html', - buildRoute: (context) => BaseWidget('CustomSingleChildLayout', codePath, Container()), + buildRoute: (context) => BaseWidget('CustomSingleChildLayout', codePath, CustomSingleChildLayoutWidget()), ), DemoItem( icon: Icons.child_care, diff --git a/lib/pattern/display/avatarlist.dart b/lib/pattern/display/avatarlist.dart index 552848d..42ef7df 100644 --- a/lib/pattern/display/avatarlist.dart +++ b/lib/pattern/display/avatarlist.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_dojo/common/main_title_widget.dart'; class AvatarListWidget extends StatelessWidget { + final double sizeW = 50; + final double offsetW = 40; + @override Widget build(BuildContext context) { return Column( @@ -47,6 +50,22 @@ class AvatarListWidget extends StatelessWidget { ), ), ), + MainTitleWidget('通过Stack实现Avatar错列'), + Container( + alignment: Alignment.topRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + width: _getImageStackWidth(6), + height: sizeW, + child: Stack( + children: _getStackItems(6), + ), + ), + ], + ), + ), MainTitleWidget('通过Shape实现Avatar'), Container( width: 100, @@ -62,4 +81,32 @@ class AvatarListWidget extends StatelessWidget { ], ); } + + double _getImageStackWidth(int imageNumber) { + return offsetW * (imageNumber - 1) + sizeW; + } + + List _getStackItems(int count) { + List _list = new List(); + for (var i = 0; i < count; i++) { + double off = offsetW * i; + _list.add( + Positioned( + left: off, + child: Container( + width: sizeW, + height: sizeW, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('images/flower.jpg'), + fit: BoxFit.cover, + ), + shape: BoxShape.circle, + ), + ), + ), + ); + } + return _list; + } } diff --git a/lib/pattern/display/layerblendmode.dart b/lib/pattern/display/layerblendmode.dart index e9b4f52..b2da058 100644 --- a/lib/pattern/display/layerblendmode.dart +++ b/lib/pattern/display/layerblendmode.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_dojo/common/main_title_widget.dart'; import 'package:flutter_dojo/common/subtitle_widget.dart'; @@ -12,14 +13,33 @@ class LayerBlendModeWidget extends StatelessWidget { SubtitleWidget('BlendMode和ColorFilter只有在合并图层的时候才会生效'), Center( child: Container( - width: 300, - height: 300, + width: 100, + height: 100, color: Colors.blue, child: CustomPaint( painter: MyPainter(), ), ), ), + MainTitleWidget('图层混合动画'), + Shimmer( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Colors.blue, + Colors.red, + Colors.blue, + ], + stops: const [0.0, 0.5, 1.0], + ), + child: Center( + child: Text( + 'xuyisheng', + style: TextStyle(fontSize: 50), + ), + ), + ), ], ); } @@ -42,3 +62,123 @@ class MyPainter extends CustomPainter { @override bool shouldRepaint(CustomPainter oldDelegate) => true; } + +class Shimmer extends StatefulWidget { + final Widget child; + final Duration period; + final Gradient gradient; + + const Shimmer({ + Key key, + @required this.child, + @required this.gradient, + this.period = const Duration(milliseconds: 1000), + }) : super(key: key); + + @override + _ShimmerState createState() => _ShimmerState(); +} + +class _ShimmerState extends State with SingleTickerProviderStateMixin { + AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this, duration: widget.period); + _controller.repeat(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + child: widget.child, + builder: (BuildContext context, Widget child) => _Shimmer( + child: child, + gradient: widget.gradient, + percent: _controller.value, + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} + +class _Shimmer extends SingleChildRenderObjectWidget { + final double percent; + final Gradient gradient; + + const _Shimmer({ + Widget child, + this.percent, + this.gradient, + }) : super(child: child); + + @override + _ShimmerFilter createRenderObject(BuildContext context) { + return _ShimmerFilter(percent, gradient); + } + + @override + void updateRenderObject(BuildContext context, _ShimmerFilter shimmer) { + shimmer.percent = percent; + } +} + +class _ShimmerFilter extends RenderProxyBox { + final Paint _clearPaint = Paint(); + final Paint _gradientPaint; + final Gradient _gradient; + double _percent; + Rect _rect; + + _ShimmerFilter( + this._percent, + this._gradient, + ) : _gradientPaint = Paint()..blendMode = BlendMode.srcIn; + + @override + bool get alwaysNeedsCompositing => child != null; + + set percent(double newValue) { + if (newValue == _percent) { + return; + } + _percent = newValue; + markNeedsPaint(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child == null) { + return; + } + + context.canvas.saveLayer(offset & child.size, _clearPaint); + context.paintChild(child, offset); + + final double width = child.size.width; + final double height = child.size.height; + Rect rect; + double dx, dy; + dx = _offset(-width, width, _percent); + dy = 0.0; + rect = Rect.fromLTWH(offset.dx - width, offset.dy, 3 * width, height); + if (_rect != rect) { + _gradientPaint.shader = _gradient.createShader(rect); + _rect = rect; + } + context.canvas.translate(dx, dy); + context.canvas.drawRect(rect, _gradientPaint); + context.canvas.restore(); + } + + double _offset(double start, double end, double percent) { + return start + (end - start) * percent; + } +} diff --git a/lib/pattern/display/rating.dart b/lib/pattern/display/rating.dart new file mode 100644 index 0000000..5c8be40 --- /dev/null +++ b/lib/pattern/display/rating.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_dojo/common/main_title_widget.dart'; + +class RatingWidget extends StatelessWidget { + List generateStar(double score, int total) { + List list = List(); + for (var i = 0; i < total; i++) { + double factor = (score - i); + if (factor >= 1) { + factor = 1.0; + } else if (factor < 0) { + factor = 0; + } + Stack stack = Stack( + children: [ + Icon( + Icons.star, + color: Colors.grey, + ), + ClipRect( + child: Align( + alignment: Alignment.topLeft, + widthFactor: factor, + child: Icon( + Icons.star, + color: Colors.red, + ), + ), + ), + ], + ); + list.add(stack); + } + return list; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + MainTitleWidget('通过Clip实现'), + Row(children: generateStar(3.5, 5)), + ], + ); + } +} diff --git a/lib/pattern/list/backdrop.dart b/lib/pattern/list/backdrop.dart index f2277b5..ef3c1e2 100644 --- a/lib/pattern/list/backdrop.dart +++ b/lib/pattern/list/backdrop.dart @@ -1,8 +1,293 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; class BackdropWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(); + return Panels(); + } +} + +class Panels extends StatelessWidget { + final frontPanelVisible = ValueNotifier(false); + + @override + Widget build(BuildContext context) { + return Backdrop( + frontLayer: FrontPanel(), + backLayer: BackPanel( + frontPanelOpen: frontPanelVisible, + ), + frontHeader: FrontPanelTitle(), + panelVisible: frontPanelVisible, + frontPanelOpenHeight: 40.0, + frontHeaderHeight: 48.0, + frontHeaderVisibleClosed: true, + ); + } +} + +class FrontPanelTitle extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 16.0, left: 16.0), + child: Text( + 'Tap Me', + style: Theme.of(context).textTheme.subhead, + ), + ); + } +} + +class FrontPanel extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container(color: Theme.of(context).cardColor, child: Center(child: Text('Hello world'))); + } +} + +class BackPanel extends StatefulWidget { + BackPanel({@required this.frontPanelOpen}); + + final ValueNotifier frontPanelOpen; + + @override + createState() => _BackPanelState(); +} + +class _BackPanelState extends State { + bool panelOpen; + + @override + initState() { + super.initState(); + panelOpen = widget.frontPanelOpen.value; + widget.frontPanelOpen.addListener(_subscribeToValueNotifier); + } + + void _subscribeToValueNotifier() => setState(() => panelOpen = widget.frontPanelOpen.value); + + /// Required for resubscribing when hot reload occurs + @override + void didUpdateWidget(BackPanel oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.frontPanelOpen.removeListener(_subscribeToValueNotifier); + widget.frontPanelOpen.addListener(_subscribeToValueNotifier); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.only(top: 10.0), + child: Text('Front panel is ${panelOpen ? "open" : "closed"}'), + ), + ), + Center( + child: RaisedButton( + child: Text('Tap Me'), + onPressed: () => widget.frontPanelOpen.value = true, + ), + ), + // will not be seen; covered by front panel + Center(child: Text('Bottom of Panel')), + ], + ); + } +} + +const _kFlingVelocity = 2.0; + +class _BackdropPanel extends StatelessWidget { + const _BackdropPanel({ + Key key, + this.onTap, + this.onVerticalDragUpdate, + this.onVerticalDragEnd, + this.title, + this.child, + this.titleHeight, + this.padding, + }) : super(key: key); + + final VoidCallback onTap; + final GestureDragUpdateCallback onVerticalDragUpdate; + final GestureDragEndCallback onVerticalDragEnd; + final Widget title; + final Widget child; + final double titleHeight; + final EdgeInsets padding; + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: Material( + elevation: 12.0, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onVerticalDragUpdate: onVerticalDragUpdate, + onVerticalDragEnd: onVerticalDragEnd, + onTap: onTap, + child: Container(height: titleHeight, child: title), + ), + Divider(height: 1.0), + Expanded(child: child), + ], + ), + ), + ); + } +} + +class Backdrop extends StatefulWidget { + final Widget frontLayer; + final Widget backLayer; + final Widget frontHeader; + final double frontPanelOpenHeight; + final double frontHeaderHeight; + final bool frontHeaderVisibleClosed; + final EdgeInsets frontPanelPadding; + final ValueNotifier panelVisible; + + Backdrop( + {@required this.frontLayer, + @required this.backLayer, + this.frontPanelOpenHeight = 0.0, + this.frontHeaderHeight = 48.0, + this.frontPanelPadding = const EdgeInsets.all(0.0), + this.frontHeaderVisibleClosed = true, + this.panelVisible, + this.frontHeader}) + : assert(frontLayer != null), + assert(backLayer != null); + + @override + createState() => _BackdropState(); +} + +class _BackdropState extends State with SingleTickerProviderStateMixin { + final _backdropKey = GlobalKey(debugLabel: 'Backdrop'); + AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: Duration(milliseconds: 300), + // value of 0 hides the panel; value of 1 fully shows the panel + value: (widget.panelVisible?.value ?? true) ? 1.0 : 0.0, + vsync: this, + ); + + // Listen on the toggle value notifier if it's not null + + widget.panelVisible?.addListener(_subscribeToValueNotifier); + + // Ensure that the value notifier is updated when the panel is opened or closed + if (widget.panelVisible != null) { + _controller.addStatusListener((status) { + if (status == AnimationStatus.completed) + widget.panelVisible.value = true; + else if (status == AnimationStatus.dismissed) widget.panelVisible.value = false; + }); + } + } + + void _subscribeToValueNotifier() { + if (widget.panelVisible.value != _backdropPanelVisible) _toggleBackdropPanelVisibility(); + } + + /// Required for resubscribing when hot reload occurs + @override + void didUpdateWidget(Backdrop oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.panelVisible?.removeListener(_subscribeToValueNotifier); + widget.panelVisible?.addListener(_subscribeToValueNotifier); + } + + @override + void dispose() { + _controller.dispose(); + widget.panelVisible?.dispose(); + super.dispose(); + } + + bool get _backdropPanelVisible => + _controller.status == AnimationStatus.completed || _controller.status == AnimationStatus.forward; + + void _toggleBackdropPanelVisibility() => + _controller.fling(velocity: _backdropPanelVisible ? -_kFlingVelocity : _kFlingVelocity); + + double get _backdropHeight { + final RenderBox renderBox = _backdropKey.currentContext.findRenderObject(); + return renderBox.size.height; + } + + void _handleDragUpdate(DragUpdateDetails details) { + if (!_controller.isAnimating) _controller.value -= details.primaryDelta / _backdropHeight; + } + + void _handleDragEnd(DragEndDetails details) { + if (_controller.isAnimating || _controller.status == AnimationStatus.completed) return; + + final double flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight; + if (flingVelocity < 0.0) + _controller.fling(velocity: max(_kFlingVelocity, -flingVelocity)); + else if (flingVelocity > 0.0) + _controller.fling(velocity: min(-_kFlingVelocity, -flingVelocity)); + else + _controller.fling(velocity: _controller.value < 0.5 ? -_kFlingVelocity : _kFlingVelocity); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final panelSize = constraints.biggest; + final closedPercentage = + widget.frontHeaderVisibleClosed ? (panelSize.height - widget.frontHeaderHeight) / panelSize.height : 1.0; + final openPercentage = widget.frontPanelOpenHeight / panelSize.height; + + final panelDetailsPosition = Tween( + begin: Offset(0.0, closedPercentage), + end: Offset(0.0, openPercentage), + ).animate(_controller.view); + + return Container( + key: _backdropKey, + child: Stack( + children: [ + widget.backLayer, + SlideTransition( + position: panelDetailsPosition, + child: _BackdropPanel( + onTap: _toggleBackdropPanelVisibility, + onVerticalDragUpdate: _handleDragUpdate, + onVerticalDragEnd: _handleDragEnd, + title: widget.frontHeader, + titleHeight: widget.frontHeaderHeight, + child: widget.frontLayer, + padding: widget.frontPanelPadding, + ), + ), + ], + ), + ); + }, + ); } } diff --git a/lib/pattern/list/sliverheader.dart b/lib/pattern/list/sliverheader.dart new file mode 100644 index 0000000..f0c5fad --- /dev/null +++ b/lib/pattern/list/sliverheader.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; + +class SliverHeaderWidget extends StatefulWidget { + @override + _SliverHeaderWidgetState createState() => _SliverHeaderWidgetState(); +} + +class _SliverHeaderWidgetState extends State with SingleTickerProviderStateMixin { + TabController tabController; + + @override + void initState() { + super.initState(); + this.tabController = TabController(length: 2, vsync: this); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + pinned: true, + elevation: 0, + expandedHeight: 250, + flexibleSpace: FlexibleSpaceBar( + title: Text('SliverHeader'), + background: Image.asset( + 'images/book.jpg', + fit: BoxFit.cover, + ), + ), + ), + SliverPersistentHeader( + pinned: true, + delegate: StickyTabBarDelegate( + child: TabBar( + labelColor: Colors.black, + controller: this.tabController, + tabs: [ + Tab(text: 'Page1'), + Tab(text: 'Page2'), + ], + ), + ), + ), + SliverFillRemaining( + child: TabBarView( + controller: this.tabController, + children: [ + Center(child: Text('Page1')), + Center(child: Text('Page2')), + ], + ), + ), + ], + ), + ); + } +} + +class StickyTabBarDelegate extends SliverPersistentHeaderDelegate { + final TabBar child; + + StickyTabBarDelegate({@required this.child}); + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { + return this.child; + } + + @override + double get maxExtent => this.child.preferredSize.height; + + @override + double get minExtent => this.child.preferredSize.height; + + @override + bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) { + return true; + } +} diff --git a/lib/pattern/list/sliverheaderwitheffect.dart b/lib/pattern/list/sliverheaderwitheffect.dart new file mode 100644 index 0000000..28778a7 --- /dev/null +++ b/lib/pattern/list/sliverheaderwitheffect.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; + +class SliverHeaderWithEffectWidget extends StatefulWidget { + @override + _SliverHeaderWithEffectWidgetState createState() => _SliverHeaderWithEffectWidgetState(); +} + +class _SliverHeaderWithEffectWidgetState extends State { + @override + Widget build(BuildContext context) { + return CustomScrollView( + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: SliverCustomHeaderDelegate( + collapsedHeight: 64, + expandedHeight: 300, + ), + ), + SliverFillRemaining(child: FilmContent()), + ], + ); + } +} + +class SliverCustomHeaderDelegate extends SliverPersistentHeaderDelegate { + final double collapsedHeight; + final double expandedHeight; + + SliverCustomHeaderDelegate({ + this.collapsedHeight, + this.expandedHeight, + }); + + @override + double get minExtent => this.collapsedHeight; + + @override + double get maxExtent => this.expandedHeight; + + @override + bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) { + return true; + } + + Color getStickyHeaderBgColor(shrinkOffset) { + final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255).clamp(0, 255).toInt(); + return Color.fromARGB(alpha, 255, 255, 255); + } + + Color getStickyHeaderTextColor(shrinkOffset) { + if (shrinkOffset <= 50) { + return Colors.transparent; + } else { + final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255).clamp(0, 255).toInt(); + return Color.fromARGB(alpha, 0, 0, 0); + } + } + + Color getStickyHeaderIconColor(shrinkOffset) { + if (shrinkOffset <= 50) { + return Colors.white; + } else { + final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255).clamp(0, 255).toInt(); + return Color.fromARGB(alpha, 0, 0, 0); + } + } + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { + return Container( + height: this.maxExtent, + width: MediaQuery.of(context).size.width, + child: Stack( + fit: StackFit.expand, + children: [ + Container(child: Image.asset('images/flower.jpg', fit: BoxFit.cover)), + Positioned( + left: 0, + right: 0, + top: 0, + child: Container( + color: this.getStickyHeaderBgColor(shrinkOffset), + child: Container( + height: this.collapsedHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon( + Icons.arrow_back, + color: this.getStickyHeaderIconColor(shrinkOffset), + ), + onPressed: () => Navigator.pop(context), + ), + Text( + 'Header Effect', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: this.getStickyHeaderTextColor(shrinkOffset), + ), + ), + IconButton( + icon: Icon( + Icons.share, + color: this.getStickyHeaderIconColor(shrinkOffset), + ), + onPressed: () {}, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class FilmContent extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Text( + '《Android群英传》 & 《Android群英传-神兵利器》' * 38, + textAlign: TextAlign.justify, + style: TextStyle( + fontSize: 15, + color: Color(0xFF999999), + ), + ), + ); + } +} diff --git a/lib/pattern/list/zoom.dart b/lib/pattern/list/zoom.dart new file mode 100644 index 0000000..4b720b2 --- /dev/null +++ b/lib/pattern/list/zoom.dart @@ -0,0 +1,58 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class ZoomWidget extends StatefulWidget { + @override + _ZoomWidgetState createState() => _ZoomWidgetState(); +} + +class _ZoomWidgetState extends State { + ScrollController controller; + double offset = 0; + final double headerHeight = 150; + + _scrollListener() { + setState(() => offset = controller.offset); + } + + double _getHeaderHeight() { + if (offset == null) return headerHeight; + if (offset <= 0) { + return min(max((headerHeight - offset), headerHeight), 250); + } else { + return max(min((headerHeight - offset), headerHeight), 50); + } + } + + @override + void initState() { + controller = ScrollController(); + controller.addListener(_scrollListener); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox( + height: _getHeaderHeight(), + child: Container( + child: Image.asset('images/book.jpg', fit: BoxFit.cover), + ), + ), + Expanded( + child: ListView.builder( + physics: BouncingScrollPhysics(), + controller: controller, + itemCount: 20, + itemBuilder: (context, index) { + return ListTile(title: Text("Item : $index")); + }, + ), + ), + ], + ); + } +} diff --git a/lib/pattern/viewpager/pagereveal.dart b/lib/pattern/viewpager/pagereveal.dart new file mode 100644 index 0000000..6d3bb35 --- /dev/null +++ b/lib/pattern/viewpager/pagereveal.dart @@ -0,0 +1,549 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_dojo/common/main_title_widget.dart'; + +class PageRevealWidget extends StatefulWidget { + @override + _PageRevealWidgetState createState() => _PageRevealWidgetState(); +} + +class _PageRevealWidgetState extends State with TickerProviderStateMixin { + StreamController slideUpdateStream; + AnimatedPageDragger animatedPageDragger; + + int activeIndex = 0; + SlideDirection slideDirection = SlideDirection.none; + int nextPageIndex = 0; + int waitingNextPageIndex = -1; + + double slidePercent = 0.0; + + _PageRevealWidgetState() { + slideUpdateStream = StreamController(); + + slideUpdateStream.stream.listen((SlideUpdate event) { + if (mounted) { + setState(() { + if (event.updateType == UpdateType.dragging) { + slideDirection = event.direction; + slidePercent = event.slidePercent; + + if (slideDirection == SlideDirection.leftToRight) { + nextPageIndex = activeIndex - 1; + } else if (slideDirection == SlideDirection.rightToLeft) { + nextPageIndex = activeIndex + 1; + } else { + nextPageIndex = activeIndex; + } + } else if (event.updateType == UpdateType.doneDragging) { + if (slidePercent > 0.5) { + animatedPageDragger = AnimatedPageDragger( + slideDirection: slideDirection, + transitionGoal: TransitionGoal.open, + slidePercent: slidePercent, + slideUpdateStream: slideUpdateStream, + vsync: this, + ); + } else { + animatedPageDragger = AnimatedPageDragger( + slideDirection: slideDirection, + transitionGoal: TransitionGoal.close, + slidePercent: slidePercent, + slideUpdateStream: slideUpdateStream, + vsync: this, + ); + waitingNextPageIndex = activeIndex; + } + + animatedPageDragger.run(); + } else if (event.updateType == UpdateType.animating) { + slideDirection = event.direction; + slidePercent = event.slidePercent; + } else if (event.updateType == UpdateType.doneAnimating) { + if (waitingNextPageIndex != -1) { + nextPageIndex = waitingNextPageIndex; + waitingNextPageIndex = -1; + } else { + activeIndex = nextPageIndex; + } + + slideDirection = SlideDirection.none; + slidePercent = 0.0; + + animatedPageDragger.dispose(); + } + }); + } + }); + } + + @override + void dispose() { + super.dispose(); + slideUpdateStream.close(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + MainTitleWidget('阿里的实现,不好,还没时间改'), + Expanded( + child: Stack( + fit: StackFit.expand, + children: [ + Page( + viewModel: pages[activeIndex], + percentVisible: 1.0, + ), + PageReveal( + revealPercent: slidePercent, + child: Page( + viewModel: pages[nextPageIndex], + percentVisible: slidePercent, + ), + ), + PagerIndicator( + viewModel: PagerIndicatorViewModel( + pages, + activeIndex, + slideDirection, + slidePercent, + ), + ), + PageDragger( + canDragLeftToRight: activeIndex > 0, + canDragRightToLeft: activeIndex < pages.length - 1, + slideUpdateStream: this.slideUpdateStream, + ) + ], + ), + ), + ], + ); + } +} + +class PageReveal extends StatelessWidget { + final double revealPercent; + final Widget child; + + PageReveal({this.revealPercent, this.child}); + + @override + Widget build(BuildContext context) { + return ClipOval( + clipper: CircleRevealClipper(revealPercent), + child: child, + ); + } +} + +class CircleRevealClipper extends CustomClipper { + final double revealPercent; + + CircleRevealClipper(this.revealPercent); + + @override + Rect getClip(Size size) { + final epicenter = Offset(size.width / 2, size.height * 0.9); + + double theta = atan(epicenter.dy / epicenter.dx); + final distanceToCorner = epicenter.dy / sin(theta); + + final radius = distanceToCorner * revealPercent; + final diameter = 2 * radius; + + return Rect.fromLTWH(epicenter.dx - radius, epicenter.dy - radius, diameter, diameter); + } + + @override + bool shouldReclip(CustomClipper oldClipper) { + return true; + } +} + +final pages = [ + PageViewModel( + Colors.red, + 'PageItem1', + ), + PageViewModel( + Colors.blue, + 'PageItem2', + ), + PageViewModel( + Colors.green, + 'PageItem3', + ), +]; + +class Page extends StatelessWidget { + final PageViewModel viewModel; + final double percentVisible; + + Page({ + this.viewModel, + this.percentVisible = 1.0, + }); + + Widget creatButton(BuildContext context, String txt, IconData iconName, String type) { + return RaisedButton.icon( + onPressed: () {}, + elevation: 10.0, + color: Colors.black26, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(20.0))), + icon: Icon(iconName, color: Colors.white, size: 14.0), + label: Text( + txt, + maxLines: 1, + style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Container( + width: double.infinity, + color: viewModel.color, + padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), + child: Opacity( + opacity: percentVisible, + child: ListView( + children: [ + layout(context), + ], + ), + ), + ), + ], + ); + } + + Column layout(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Transform( + transform: Matrix4.translationValues(0.0, 70.0 * (1.0 - percentVisible), 0.0), + child: Padding( + padding: EdgeInsets.only(top: 10.0, bottom: 10.0), + child: Text( + viewModel.title, + style: TextStyle( + color: Colors.white, + fontSize: 28.0, + ), + ), + ), + ), + SizedBox(height: 300), + Transform( + transform: Matrix4.translationValues(0.0, 30.0 * (1.0 - percentVisible), 0.0), + child: Padding( + padding: EdgeInsets.only(bottom: 10.0), + child: Text( + viewModel.title, + textAlign: TextAlign.center, + style: TextStyle( + height: 1.2, + color: Colors.white, + fontFamily: 'FlamanteRomaItalic', + fontSize: 18.0, + ), + ), + ), + ), + ], + ); + } +} + +class PageViewModel { + final Color color; + final String title; + + PageViewModel( + this.color, + this.title, + ); +} + +class PageDragger extends StatefulWidget { + final canDragLeftToRight; + final canDragRightToLeft; + + final StreamController slideUpdateStream; + + PageDragger({ + this.canDragLeftToRight, + this.canDragRightToLeft, + this.slideUpdateStream, + }); + + @override + _PageDraggerState createState() => _PageDraggerState(); +} + +class _PageDraggerState extends State { + static const FULL_TRANSTITION_PX = 300.0; + + Offset dragStart; + SlideDirection slideDirection; + double slidePercent = 0.0; + + onDragStart(DragStartDetails details) { + dragStart = details.globalPosition; + } + + onDragUpdate(DragUpdateDetails details) { + if (dragStart != null) { + final Position = details.globalPosition; + final dx = dragStart.dx - Position.dx; + + if (dx > 0 && widget.canDragRightToLeft) { + slideDirection = SlideDirection.rightToLeft; + } else if (dx < 0 && widget.canDragLeftToRight) { + slideDirection = SlideDirection.leftToRight; + } else { + slideDirection = SlideDirection.none; + } + + if (slideDirection != SlideDirection.none) { + slidePercent = (dx / FULL_TRANSTITION_PX).abs().clamp(0.0, 1.0); + } else { + slidePercent = 0.0; + } + widget.slideUpdateStream.add(SlideUpdate(UpdateType.dragging, slideDirection, slidePercent)); + } + } + + onDragEnd(DragEndDetails details) { + widget.slideUpdateStream.add(SlideUpdate( + UpdateType.doneDragging, + SlideDirection.none, + 0.0, + )); + + dragStart = null; + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onHorizontalDragStart: onDragStart, + onHorizontalDragUpdate: onDragUpdate, + onHorizontalDragEnd: onDragEnd, + ); + } +} + +class AnimatedPageDragger { + static const PERCENT_PER_MILLISECOND = 0.005; + + final slideDirection; + final transitionGoal; + + AnimationController completionAnimationController; + + AnimatedPageDragger({ + this.slideDirection, + this.transitionGoal, + slidePercent, + StreamController slideUpdateStream, + TickerProvider vsync, + }) { + final startSlidePercent = slidePercent; + var endSlidePercent; + var duration; + + if (transitionGoal == TransitionGoal.open) { + endSlidePercent = 1.0; + + final slideRemaining = 1.0 - slidePercent; + + duration = Duration(milliseconds: (slideRemaining / PERCENT_PER_MILLISECOND).round()); + } else { + endSlidePercent = 0.0; + duration = Duration(milliseconds: (slidePercent / PERCENT_PER_MILLISECOND).round()); + } + + completionAnimationController = AnimationController(duration: duration, vsync: vsync) + ..addListener(() { + slidePercent = lerpDouble(startSlidePercent, endSlidePercent, completionAnimationController.value); + + slideUpdateStream.add(SlideUpdate( + UpdateType.animating, + slideDirection, + slidePercent, + )); + }) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + slideUpdateStream.add(SlideUpdate( + UpdateType.doneAnimating, + slideDirection, + endSlidePercent, + )); + } + }); + } + + run() { + completionAnimationController.forward(from: 0.0); + } + + dispose() { + completionAnimationController.dispose(); + } +} + +enum TransitionGoal { + open, + close, +} + +enum SlideDirection { + leftToRight, + rightToLeft, + none, +} + +enum UpdateType { + dragging, + doneDragging, + animating, + doneAnimating, +} + +class SlideUpdate { + final updateType; + final direction; + final slidePercent; + + SlideUpdate(this.updateType, this.direction, this.slidePercent); +} + +class PagerIndicator extends StatelessWidget { + final PagerIndicatorViewModel viewModel; + + PagerIndicator({ + this.viewModel, + }); + + @override + Widget build(BuildContext context) { + List bubbles = []; + for (var i = 0; i < viewModel.pages.length; ++i) { + final page = viewModel.pages[i]; + + var percentActive; + + if (i == viewModel.activeIndex) { + percentActive = 1.0 - viewModel.slidePercent; + } else if (i == viewModel.activeIndex - 1 && viewModel.slideDirection == SlideDirection.leftToRight) { + percentActive = viewModel.slidePercent; + } else if (i == viewModel.activeIndex + 1 && viewModel.slideDirection == SlideDirection.rightToLeft) { + percentActive = viewModel.slidePercent; + } else { + percentActive = 0.0; + } + + bool isHollow = i > viewModel.activeIndex || + (i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight); + + bubbles.add( + PageBubble( + viewModel: PageBubbleViewModel( + page.color, + isHollow, + percentActive, + ), + ), + ); + } + + final bubbleWidth = 55.0; + final baseTranslation = ((viewModel.pages.length * bubbleWidth) / 2) - (bubbleWidth / 2); + var translation = baseTranslation - (viewModel.activeIndex * bubbleWidth); + + if (viewModel.slideDirection == SlideDirection.leftToRight) { + translation = bubbleWidth * viewModel.slidePercent + translation; + } else if (viewModel.slideDirection == SlideDirection.rightToLeft) { + translation = bubbleWidth * viewModel.slidePercent - translation; + } + + return Column( + children: [ + Expanded(child: Container()), + Transform( + transform: Matrix4.translationValues(0, 0.0, 0.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: bubbles, + ), + ), + ], + ); + } +} + +class PagerIndicatorViewModel { + final List pages; + final int activeIndex; + final SlideDirection slideDirection; + final double slidePercent; + + PagerIndicatorViewModel(this.pages, this.activeIndex, this.slideDirection, this.slidePercent); +} + +class PageBubble extends StatelessWidget { + final PageBubbleViewModel viewModel; + + PageBubble({this.viewModel}); + + @override + Widget build(BuildContext context) { + return Container( + width: 55.0, + height: 65.0, + child: Center( + child: Container( + width: lerpDouble(20.0, 45.0, viewModel.activePercent), + height: lerpDouble(20.0, 45.0, viewModel.activePercent), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: viewModel.isHollow + ? const Color(0x88FFFFFF).withAlpha(0x88 * viewModel.activePercent.round()) + : const Color(0x88FFFFFF), + border: Border.all( + color: viewModel.isHollow + ? const Color(0x88FFFFFF).withAlpha((0x88 * (1.0 - viewModel.activePercent)).round()) + : Colors.transparent, + width: 3.0, + ), + ), + ), + ), + ); + } +} + +class PageBubbleViewModel { + final Color color; + final bool isHollow; + final double activePercent; + + PageBubbleViewModel( + this.color, + this.isHollow, + this.activePercent, + ); +} diff --git a/lib/uikit/keyboard.dart b/lib/uikit/keyboard.dart index 0ad3828..5ce165c 100644 --- a/lib/uikit/keyboard.dart +++ b/lib/uikit/keyboard.dart @@ -2,7 +2,6 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_dojo/common/main_title_widget.dart'; -import 'package:flutter_dojo/common/subtitle_widget.dart'; class KeyboardWidget extends StatefulWidget { @override @@ -62,7 +61,6 @@ class _KeyboardWidgetState extends State with WidgetsBindingObse child: Text('Show keyboard'), ), MainTitleWidget('键盘是否弹起'), - SubtitleWidget('有bug'), Text('是否弹起:$isShowUp') ], ); diff --git a/lib/uikit/linemetrics.dart b/lib/uikit/linemetrics.dart new file mode 100644 index 0000000..354edd6 --- /dev/null +++ b/lib/uikit/linemetrics.dart @@ -0,0 +1,92 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +class LineMetricsWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + child: ShowTextAndPosition(), + ); + } +} + +class ShowTextAndPosition extends StatelessWidget { + final text = 'Show Text line metrics.\n很长很长,中文English、¥*()而且12345。\nΩß˙©†∆˚ø≤µ∫√ƒ®åß∂'; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final textPainter = TextPainter( + text: TextSpan( + text: text, + style: TextStyle( + color: Colors.black, + fontSize: 40, + ), + ), + textDirection: TextDirection.ltr, + ); + final width = constraints.maxWidth; + textPainter.layout( + minWidth: 20, + maxWidth: width, + ); + final height = textPainter.height; + + return Container( + width: width, + height: height, + child: CustomPaint(painter: MyPainter(textPainter)), + ); + }, + ); + } +} + +class MyPainter extends CustomPainter { + TextPainter textPainter; + + final baseLinePaint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke + ..strokeWidth = 1; + final textBoundsPaint = Paint() + ..color = Colors.red + ..style = PaintingStyle.stroke + ..strokeWidth = 1; + + MyPainter(this.textPainter); + + @override + void paint(Canvas canvas, Size size) { + canvas.drawRect(Offset(0, 0) & size, Paint()..color = Colors.grey.shade200); + textPainter.layout( + minWidth: 0, + maxWidth: size.width, + ); + final offset = Offset(0, 0); + textPainter.paint(canvas, offset); + List lines = textPainter.computeLineMetrics(); + for (ui.LineMetrics line in lines) { + final baseline = line.baseline; + final left = line.left; + final top = line.baseline - line.ascent; + final right = left + line.width; + final bottom = line.baseline + line.descent; + final rect = Rect.fromLTRB(left, top, right, bottom); + canvas.drawLine( + Offset(left, baseline), + Offset(right, baseline), + baseLinePaint, + ); + canvas.drawRect(rect, textBoundsPaint); + } + } + + @override + bool shouldRepaint(CustomPainter old) { + return false; + } +} diff --git a/lib/widgets/async/valuenotifier.dart b/lib/widgets/async/valuenotifier.dart new file mode 100644 index 0000000..d74f13c --- /dev/null +++ b/lib/widgets/async/valuenotifier.dart @@ -0,0 +1,61 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_dojo/common/main_title_widget.dart'; +import 'package:flutter_dojo/common/subtitle_widget.dart'; + +class ValueNotifierWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ValueNotifier valueNotifier = ValueNotifier('Init String Data'); + return Column( + children: [ + MainTitleWidget('ValueNotifier基本使用'), + SubtitleWidget('在需要响应的Widget中addListener之后,一旦ValueNotifier的值发生改变,就会触发通知'), + NotifierWidget(data: valueNotifier), + RaisedButton( + onPressed: () => valueNotifier.value = 'New Value ${Random().nextInt(100)}', + child: Text('Change'), + ), + ], + ); + } +} + +class NotifierWidget extends StatefulWidget { + final ValueNotifier data; + + NotifierWidget({this.data}); + + @override + _NotifierWidgetState createState() => _NotifierWidgetState(); +} + +class _NotifierWidgetState extends State { + String info; + + @override + initState() { + super.initState(); + widget.data.addListener(changeNotifier); + info = '${widget.data.value}'; + } + + void changeNotifier() { + setState(() => info = '${widget.data.value}'); + } + + @override + Widget build(BuildContext context) { + return Text( + info, + style: TextStyle(fontSize: 30), + ); + } + + @override + dispose() { + widget.data.removeListener(changeNotifier); + super.dispose(); + } +} diff --git a/lib/widgets/multichildlayout/custommultichildlayout.dart b/lib/widgets/multichildlayout/custommultichildlayout.dart index 23fb106..bd68bda 100644 --- a/lib/widgets/multichildlayout/custommultichildlayout.dart +++ b/lib/widgets/multichildlayout/custommultichildlayout.dart @@ -1,64 +1,44 @@ import 'package:flutter/material.dart'; +import 'package:flutter_dojo/common/main_title_widget.dart'; class CustomMultiChildLayoutWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return CustomMultiChildLayout( - delegate: _CustomLayout(margin: 5), + return Column( children: [ - LayoutId( - id: Custom.up, - child: SizedBox( - height: 50.0, - width: 50.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Colors.blue, - )), - ), - ), - LayoutId( - id: Custom.center, - child: SizedBox( - height: 50.0, - width: 50.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Colors.red, - )), - ), - ), - LayoutId( - id: Custom.down, - child: SizedBox( - height: 50.0, - width: 50.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Colors.green, - )), - ), - ), - LayoutId( - id: Custom.right, - child: SizedBox( - height: 50.0, - width: 50.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Colors.limeAccent, - )), - ), - ), - LayoutId( - id: Custom.left, - child: SizedBox( - height: 50.0, - width: 50.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Colors.deepOrange, - )), + MainTitleWidget('自定义视图示例'), + Container( + width: 300, + height: 200, + color: Colors.grey.shade200, + child: CustomMultiChildLayout( + delegate: CustomLayout(), + children: [ + LayoutId( + id: CustomID.topLeft, + child: Container( + height: 50, + width: 50, + color: Colors.blue, + ), + ), + LayoutId( + id: CustomID.center, + child: Container( + height: 50, + width: 50, + color: Colors.red, + ), + ), + LayoutId( + id: CustomID.bottomRight, + child: Container( + height: 50, + width: 50, + color: Colors.green, + ), + ), + ], ), ), ], @@ -66,62 +46,41 @@ class CustomMultiChildLayoutWidget extends StatelessWidget { } } -enum Custom { - up, +enum CustomID { + topLeft, center, - down, - left, - right, + bottomRight, } -class _CustomLayout extends MultiChildLayoutDelegate { - _CustomLayout({this.margin: 3.0}); - - //外边距 - final double margin; - - @override //性能布局 +class CustomLayout extends MultiChildLayoutDelegate { + @override void performLayout(Size size) { final BoxConstraints box = BoxConstraints.loose(size); - Size upSize; Size centerSize; - Size downSize; - Size leftSize; - Size rightSize; - if (hasChild(Custom.up)) { - upSize = layoutChild(Custom.up, box); - final dx = (size.width - upSize.width) / 2; - final dy = (size.height - upSize.height) / 2 - upSize.height - margin; - positionChild(Custom.up, Offset(dx, dy)); + Size bottomRightSize; + if (hasChild(CustomID.topLeft)) { + // 即使位置与元素尺寸无关,layoutChild也必须调用,用于获取元素尺寸 + layoutChild(CustomID.topLeft, box); + final dx = .0; + final dy = .0; + positionChild(CustomID.topLeft, Offset(dx, dy)); } - if (hasChild(Custom.center)) { - centerSize = layoutChild(Custom.center, box); + if (hasChild(CustomID.center)) { + centerSize = layoutChild(CustomID.center, box); final dx = (size.width - centerSize.width) / 2; final dy = (size.height - centerSize.height) / 2; - positionChild(Custom.center, Offset(dx, dy)); + positionChild(CustomID.center, Offset(dx, dy)); } - if (hasChild(Custom.down)) { - downSize = layoutChild(Custom.down, box); - final dx = (size.width - downSize.width) / 2; - final dy = (size.height - downSize.height) / 2 + downSize.height + margin; - positionChild(Custom.down, Offset(dx, dy)); - } - if (hasChild(Custom.left)) { - leftSize = layoutChild(Custom.left, box); - final dx = (size.width - leftSize.width) / 2 - leftSize.width - margin; - final dy = (size.height - leftSize.height) / 2; - positionChild(Custom.left, Offset(dx, dy)); - } - if (hasChild(Custom.right)) { - rightSize = layoutChild(Custom.right, box); - final dx = (size.width - rightSize.width) / 2 + rightSize.width + margin; - final dy = (size.height - rightSize.height) / 2; - positionChild(Custom.right, Offset(dx, dy)); + if (hasChild(CustomID.bottomRight)) { + bottomRightSize = layoutChild(CustomID.bottomRight, box); + final dx = size.width - bottomRightSize.width; + final dy = size.height - bottomRightSize.height; + positionChild(CustomID.bottomRight, Offset(dx, dy)); } } - @override //是否应该重绘 - bool shouldRelayout(_CustomLayout oldDelegate) { - return oldDelegate.margin != margin; + @override + bool shouldRelayout(CustomLayout oldDelegate) { + return false; } } diff --git a/lib/widgets/multichildlayout/flow.dart b/lib/widgets/multichildlayout/flow.dart index ef12d1c..e5911d2 100644 --- a/lib/widgets/multichildlayout/flow.dart +++ b/lib/widgets/multichildlayout/flow.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_dojo/common/main_title_widget.dart'; +import 'package:flutter_dojo/common/subtitle_widget.dart'; class FlowWidget extends StatefulWidget { @override @@ -16,10 +18,6 @@ class _FlowWidgetState extends State with SingleTickerProviderStateM Icons.menu, ]; - void _updateMenu(IconData icon) { - if (icon != Icons.menu) setState(() => lastTapped = icon); - } - @override void initState() { super.initState(); @@ -30,22 +28,21 @@ class _FlowWidgetState extends State with SingleTickerProviderStateM } Widget flowMenuItem(IconData icon) { - final double buttonDiameter = MediaQuery.of(context).size.width / menuItems.length; return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), - child: RawMaterialButton( - fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue, - splashColor: Colors.amber[100], - shape: CircleBorder(), - constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)), - onPressed: () { - _updateMenu(icon); + child: GestureDetector( + onTap: () { menuAnimation.status == AnimationStatus.completed ? menuAnimation.reverse() : menuAnimation.forward(); }, - child: Icon( - icon, - color: Colors.white, - size: 45.0, + child: Container( + width: 40, + height: 40, + color: Colors.blue, + child: Icon( + icon, + color: Colors.white, + size: 30, + ), ), ), ); @@ -53,11 +50,18 @@ class _FlowWidgetState extends State with SingleTickerProviderStateM @override Widget build(BuildContext context) { - return Container( - child: Flow( - delegate: FlowMenuDelegate(menuAnimation: menuAnimation), - children: menuItems.map((IconData icon) => flowMenuItem(icon)).toList(), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MainTitleWidget('Flow基本使用'), + SubtitleWidget('关键在于对Child的定位'), + Container( + child: Flow( + delegate: FlowMenuDelegate(menuAnimation: menuAnimation), + children: menuItems.map((IconData icon) => flowMenuItem(icon)).toList(), + ), + ), + ], ); } } @@ -72,16 +76,21 @@ class FlowMenuDelegate extends FlowDelegate { return menuAnimation != oldDelegate.menuAnimation; } + @override + Size getSize(BoxConstraints constraints) { + return Size(double.infinity, 400); + } + @override void paintChildren(FlowPaintingContext context) { - double dx = 0.0; + double dy = 0.0; for (int i = 0; i < context.childCount; ++i) { - dx = context.getChildSize(i).width * i; + dy = context.getChildSize(i).height * i; context.paintChild( i, transform: Matrix4.translationValues( - dx * menuAnimation.value, 0, + dy * menuAnimation.value, 0, ), ); diff --git a/lib/widgets/paintingeffect/shadermask.dart b/lib/widgets/paintingeffect/shadermask.dart index f10a7a8..c8e83e5 100644 --- a/lib/widgets/paintingeffect/shadermask.dart +++ b/lib/widgets/paintingeffect/shadermask.dart @@ -35,7 +35,7 @@ class _ShaderMaskWidgetState extends State { Text( 'Hello Gradients!', key: myTextKey, - style: new TextStyle( + style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.bold, foreground: Paint()..shader = getTextGradient(myTextRenderBox), diff --git a/lib/widgets/singlechildlayout/customsinglechildlayout.dart b/lib/widgets/singlechildlayout/customsinglechildlayout.dart new file mode 100644 index 0000000..717010d --- /dev/null +++ b/lib/widgets/singlechildlayout/customsinglechildlayout.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_dojo/common/main_title_widget.dart'; +import 'package:flutter_dojo/common/subtitle_widget.dart'; + +class CustomSingleChildLayoutWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + MainTitleWidget('CustomSingleChildLayout用于控制Child的布局行为'), + SubtitleWidget('虽然Child的尺寸是200x200,但是被限制为50x50'), + CustomSingleChildLayout( + delegate: FixedSizeDelegate(Size(50, 50)), + child: Container( + width: 200, + height: 200, + color: Colors.red, + ), + ), + ], + ); + } +} + +class FixedSizeDelegate extends SingleChildLayoutDelegate { + final Size size; + + FixedSizeDelegate(this.size); + + @override + BoxConstraints getConstraintsForChild(BoxConstraints constraints) { + return new BoxConstraints.tight(size); + } + + @override + Size getSize(BoxConstraints constraints) => size; + + @override + bool shouldRelayout(FixedSizeDelegate oldDelegate) { + return size != oldDelegate.size; + } +} diff --git a/lib/widgets/styling/mediaquery.dart b/lib/widgets/styling/mediaquery.dart index d3a0b70..6a2b147 100644 --- a/lib/widgets/styling/mediaquery.dart +++ b/lib/widgets/styling/mediaquery.dart @@ -13,6 +13,11 @@ class MediaQueryWidget extends StatelessWidget { Text('size.height : ${data.size.height}'), Text('devicePixelRatio : ${data.devicePixelRatio}'), Text('orientation : ${data.orientation}'), + MainTitleWidget('MediaQuery修改参数'), + MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: 2), + child: Container(child: Text('Change TextScaleFactor')), + ), ], ); } diff --git a/lib/widgets/text/richtext.dart b/lib/widgets/text/richtext.dart index 11000aa..2520e57 100644 --- a/lib/widgets/text/richtext.dart +++ b/lib/widgets/text/richtext.dart @@ -1,4 +1,4 @@ -import 'dart:ui' as ui show ParagraphBuilder, PlaceholderAlignment; +import 'dart:ui' as ui show PlaceholderAlignment; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/touchinteractions/listener.dart b/lib/widgets/touchinteractions/listener.dart index 91a9004..3f6eea2 100644 --- a/lib/widgets/touchinteractions/listener.dart +++ b/lib/widgets/touchinteractions/listener.dart @@ -19,7 +19,10 @@ class _ListenerWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ MainTitleWidget('Listener基本使用'), - SubtitleWidget('Container'), + SubtitleWidget('默认情况下,透明区域不响应点击操作'), + SubtitleWidget('默认为deferToChild,子Widget会依次进行命中测试'), + SubtitleWidget('opaque,在命中测试时,将当前组件当成不透明处理(即使本身是透明的),相当于当前Widget的整个区域都是点击区域'), + SubtitleWidget('translucent,即使透明区域也会响应点击操作'), Row( children: [ Text('Show Container Background Color'), diff --git a/pubspec.yaml b/pubspec.yaml index e3d581d..539b89f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -99,6 +99,7 @@ flutter: - lib/widgets/async/futurebuilder.dart - lib/widgets/async/inheritedmodel.dart - lib/widgets/async/inheritedwidget.dart + - lib/widgets/async/valuenotifier.dart - lib/widgets/basic/placeholder.dart - lib/widgets/basic/scaffold.dart @@ -217,6 +218,7 @@ flutter: - lib/widgets/singlechildlayout/sizedbox.dart - lib/widgets/singlechildlayout/sizedoverflowbox.dart - lib/widgets/singlechildlayout/transform.dart + - lib/widgets/singlechildlayout/customsinglechildlayout.dart - lib/widgets/styling/flexible.dart - lib/widgets/styling/spacer.dart @@ -259,6 +261,7 @@ flutter: - lib/pattern/display/autofold.dart - lib/pattern/display/popup.dart - lib/pattern/display/shadowmask.dart + - lib/pattern/display/rating.dart - lib/pattern/gesture/gesturescale.dart @@ -283,6 +286,9 @@ flutter: - lib/pattern/list/backdrop.dart - lib/pattern/list/tree.dart - lib/pattern/list/listdetail.dart + - lib/pattern/list/sliverheader.dart + - lib/pattern/list/sliverheaderwitheffect.dart + - lib/pattern/list/zoom.dart - lib/pattern/custompaint/custompaint1.dart @@ -305,6 +311,7 @@ flutter: - lib/uikit/visibility.dart - lib/uikit/curves.dart - lib/uikit/pullrefresh.dart + - lib/uikit/linemetrics.dart - lib/backend/provider.dart - lib/backend/stream.dart @@ -340,6 +347,7 @@ flutter: - lib/animation/animation/staggeranimation.dart - lib/animation/animation/wave.dart - lib/animation/animation/showup.dart + - lib/animation/animation/curveline.dart - lib/animation/loading/loading.dart - lib/animation/loading/customprogress.dart