diff --git a/packages/auto_animated/CHANGELOG.md b/packages/auto_animated/CHANGELOG.md index 28adac52..c33f137f 100644 --- a/packages/auto_animated/CHANGELOG.md +++ b/packages/auto_animated/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.4.0-dev.1 + +* Added `AnimateOnVisibilityChange` & `AnimateOnVisibilityWrapper` + ## 1.3.0 * Fixed lexical error in `AutoAnimatedIconButton` (`firstToolip` & `secondToolip` renamed to `firstTooltip` & secondTooltip) diff --git a/packages/auto_animated/README.md b/packages/auto_animated/README.md index 155797c4..b9bef614 100644 --- a/packages/auto_animated/README.md +++ b/packages/auto_animated/README.md @@ -7,6 +7,7 @@ Already added: - `AutoAnimatedGrid` - `AutoAnimatedSliverGrid` - `AutoAnimatedIconButton` +- `AnimateOnVisibilityChange` ## Screenshots

@@ -21,7 +22,7 @@ In your flutter project add the dependency: [![pub package](https://img.shields.io/pub/v/auto_animated.svg)](https://pub.dartlang.org/packages/auto_animated) -```dart +```yaml dependencies: ... auto_animated: any diff --git a/packages/auto_animated/example/lib/main.dart b/packages/auto_animated/example/lib/main.dart index 45130893..c0e712f9 100644 --- a/packages/auto_animated/example/lib/main.dart +++ b/packages/auto_animated/example/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:auto_animated_example/screens/animate_on_visibility.dart'; import 'package:auto_animated_example/screens/grid.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -29,6 +30,7 @@ class _MyAppState extends State { AutoAnimatedGridExample(), SliverExample(), AutoAnimatedIconButtonExample(), + AnimateOnVisibilityExample(), ]; void _onItemTapped(int index) { @@ -66,6 +68,10 @@ class _MyAppState extends State { icon: Icon(Icons.check_circle), title: Text('IconButton'), ), + BottomNavigationBarItem( + icon: Icon(Icons.remove_red_eye), + title: Text('On visibility'), + ), ], currentIndex: _selectedIndex, // selectedItemColor: Colors.amber[800], diff --git a/packages/auto_animated/example/lib/screens/animate_on_visibility.dart b/packages/auto_animated/example/lib/screens/animate_on_visibility.dart new file mode 100644 index 00000000..2f5f4da1 --- /dev/null +++ b/packages/auto_animated/example/lib/screens/animate_on_visibility.dart @@ -0,0 +1,42 @@ +import 'package:auto_animated_example/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:auto_animated/auto_animated.dart'; + +class AnimateOnVisibilityExample extends StatelessWidget { + @override + Widget build(BuildContext context) { + final textStyle = + Theme.of(context).textTheme.title.copyWith(color: Colors.black); + + return Scaffold( + body: SafeArea( + // Wrapper before Scroll view! + child: AnimateOnVisibilityWrapper( + showItemInterval: Duration(milliseconds: 150), + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 16), + Text('Animate elements on visibility', style: textStyle), + for (int i = 0; i < 20; i++) + AnimateOnVisibilityChange( + key: Key('$i'), + builder: animationBuilder( + SizedBox( + width: double.infinity, + height: 200, + child: HorizontalItem( + title: '$i', + ), + ), + xOffset: i.isEven ? 0.15 : -0.15, + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/packages/auto_animated/example/lib/utils.dart b/packages/auto_animated/example/lib/utils.dart index 6a022325..46628972 100644 --- a/packages/auto_animated/example/lib/utils.dart +++ b/packages/auto_animated/example/lib/utils.dart @@ -81,3 +81,27 @@ Widget Function( ), ), ); + +Widget Function( + BuildContext context, + Animation animation, +) animationBuilder(Widget child, {double xOffset = 0}) => ( + BuildContext context, + Animation animation, + ) => + FadeTransition( + opacity: Tween( + begin: 0, + end: 1, + ).animate(animation), + child: SlideTransition( + position: Tween( + begin: Offset(xOffset, 0.1), + end: Offset.zero, + ).animate(animation), + child: Padding( + padding: EdgeInsets.all(16), + child: child, + ), + ), + ); diff --git a/packages/auto_animated/example/pubspec.lock b/packages/auto_animated/example/pubspec.lock index b91e3f3d..b601101e 100644 --- a/packages/auto_animated/example/pubspec.lock +++ b/packages/auto_animated/example/pubspec.lock @@ -14,7 +14,7 @@ packages: path: ".." relative: true source: path - version: "1.3.0" + version: "1.4.0-dev.1" boolean_selector: dependency: transitive description: @@ -36,6 +36,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" cupertino_icons: dependency: "direct main" description: @@ -53,6 +60,20 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_widgets: + dependency: transitive + description: + name: flutter_widgets + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.7+1" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+3" matcher: dependency: transitive description: @@ -150,4 +171,5 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.2.2 <3.0.0" + dart: ">=2.3.0 <3.0.0" + flutter: ">=1.9.1 <2.0.0" diff --git a/packages/auto_animated/example/pubspec.yaml b/packages/auto_animated/example/pubspec.yaml index 0c57674c..075b3b0e 100644 --- a/packages/auto_animated/example/pubspec.yaml +++ b/packages/auto_animated/example/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.0.0 publish_to: 'none' environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.3.0 <3.0.0" dependencies: flutter: diff --git a/packages/auto_animated/lib/auto_animated.dart b/packages/auto_animated/lib/auto_animated.dart index 2b51b474..567102b4 100644 --- a/packages/auto_animated/lib/auto_animated.dart +++ b/packages/auto_animated/lib/auto_animated.dart @@ -3,5 +3,6 @@ export 'src/icon_button.dart'; export 'src/list.dart'; export 'src/list_animation.dart' show AutoAnimatedListItemBuilder, AutoAnimatedListRemovedItemBuilder; +export 'src/on_visibility_change.dart'; export 'src/sliver_grid.dart'; export 'src/sliver_list.dart'; diff --git a/packages/auto_animated/lib/src/on_visibility_change.dart b/packages/auto_animated/lib/src/on_visibility_change.dart new file mode 100644 index 00000000..532cb69e --- /dev/null +++ b/packages/auto_animated/lib/src/on_visibility_change.dart @@ -0,0 +1,219 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_widgets/flutter_widgets.dart'; + +typedef AutoAnimatedBuilder = Widget Function( + BuildContext context, + Animation animation, +); + +class AnimateOnVisibilityChange extends StatefulWidget { + const AnimateOnVisibilityChange({ + @required Key key, + @required this.builder, + this.duration = const Duration(milliseconds: 300), + this.delay = Duration.zero, + }) : super(key: key); + + final AutoAnimatedBuilder builder; + final Duration duration, delay; + + @override + _AnimateOnVisibilityChangeState createState() => + _AnimateOnVisibilityChangeState(); +} + +class _AnimateOnVisibilityChangeState extends State + with SingleTickerProviderStateMixin { + AnimationController _controller; + _VisibilityStackProvider _wrapper; + + @override + void initState() { + _wrapper = _VisibilityStackProvider.of(context); + _controller = AnimationController( + duration: widget.duration, + vsync: this, + ); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => VisibilityDetector( + key: widget.key, + child: widget.builder( + context, + _controller.view, + ), + onVisibilityChanged: _visibilityChanged, + ); + + void _visibilityChanged(VisibilityInfo info) { + if (_controller.isAnimating) { + return; + } + if (info.visibleFraction > 0.025 && !_controller.isCompleted) { + Future.delayed(widget.delay, () { + if (!mounted) { + return; + } + if (_wrapper != null) { + _wrapper.stack.add(() { + _controller.forward(); + }); + } else { + _controller.forward(); + } + }); + } else if (info.visibleFraction <= 0.025 && mounted) { + _controller.reverse(); + } + } +} + +class AnimateOnVisibilityWrapper extends StatefulWidget { + const AnimateOnVisibilityWrapper({ + @required this.child, + this.showItemInterval = const Duration(milliseconds: 150), + Key key, + }) : super(key: key); + + final Widget child; + final Duration showItemInterval; + + @override + _AnimateOnVisibilityWrapperState createState() => + _AnimateOnVisibilityWrapperState(); +} + +class _AnimateOnVisibilityWrapperState + extends State { + _VisibilityStack _stack; + double _lastScrollExtend = 0; + + @override + void initState() { + _stack = _VisibilityStack(widget.showItemInterval); + super.initState(); + } + + @override + void dispose() { + _stack.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(AnimateOnVisibilityWrapper oldWidget) { + if (oldWidget.showItemInterval != widget.showItemInterval) { + _stack.showItemInterval = widget.showItemInterval; + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) => _VisibilityStackProvider( + stack: _stack, + child: NotificationListener( + child: widget.child, + onNotification: _onScroll, + ), + ); + + bool _onScroll(ScrollNotification scrollInfo) { + if (scrollInfo.metrics.pixels > _lastScrollExtend + 2.5) { + // to end + _stack.direction = AnimationDirection.toEnd; + } else if (scrollInfo.metrics.pixels < _lastScrollExtend - 2.5) { + // to start + _stack.direction = AnimationDirection.toStart; + } + _lastScrollExtend = scrollInfo.metrics.pixels; + return true; + } +} + +class _VisibilityStackProvider extends InheritedWidget { + _VisibilityStackProvider({ + @required Widget child, + @required this.stack, + Key key, + }) : assert(child != null), + super(key: key, child: child); + + final _VisibilityStack stack; + + static _VisibilityStackProvider of(BuildContext context) => context + .ancestorInheritedElementForWidgetOfExactType(_VisibilityStackProvider) + ?.widget; + + @override + bool updateShouldNotify(_VisibilityStackProvider oldWidget) => + oldWidget.stack != stack; +} + +class _VisibilityStack { + _VisibilityStack(this.showItemInterval); + + Duration showItemInterval; + AnimationDirection direction = AnimationDirection.toEnd; + bool _isAnimated = false; + + final List _stack = []; + + void add(VoidCallback callback) { + _stack.add(callback); + _animate(); + } + + void _show() { + if (direction == AnimationDirection.toEnd) { + _stack + ..first.call() + ..removeAt(0); + } else { + _stack + ..last.call() + ..removeLast(); + } + } + + void _animate() { + if (_isAnimated) { + return; + } + _isAnimated = true; + + Future.delayed(showItemInterval, () { + if (_stack.isNotEmpty) { + _show(); + _isAnimated = false; + _animate(); + } else { + _isAnimated = false; + } + }); + } + + void dispose() {} + + @override + bool operator ==(Object o) => + o is _VisibilityStack && showItemInterval == o.showItemInterval; + + @override + int get hashCode => showItemInterval.hashCode; +} + +enum AnimationDirection { + toStart, + toEnd, +} diff --git a/packages/auto_animated/pubspec.lock b/packages/auto_animated/pubspec.lock index aebdafa4..3e26d855 100644 --- a/packages/auto_animated/pubspec.lock +++ b/packages/auto_animated/pubspec.lock @@ -29,6 +29,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" flutter: dependency: "direct main" description: flutter @@ -39,6 +46,20 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_widgets: + dependency: "direct main" + description: + name: flutter_widgets + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.7+1" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+3" matcher: dependency: transitive description: @@ -136,4 +157,5 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.2.2 <3.0.0" + dart: ">=2.3.0 <3.0.0" + flutter: ">=1.9.1 <2.0.0" diff --git a/packages/auto_animated/pubspec.yaml b/packages/auto_animated/pubspec.yaml index 6a5486b5..a665a61c 100644 --- a/packages/auto_animated/pubspec.yaml +++ b/packages/auto_animated/pubspec.yaml @@ -1,6 +1,6 @@ name: auto_animated description: Widgets starting auto play animation when mounted. It is already possible to animate the list and icons. -version: 1.3.0 +version: 1.4.0-dev.1 author: Serge Shkurko homepage: https://github.com/rbcprolabs/packages.flutter/tree/master/packages/auto_animated @@ -10,6 +10,7 @@ environment: dependencies: flutter: sdk: flutter + flutter_widgets: ^0.1.7+1 dev_dependencies: flutter_test: diff --git a/packages/native_pdf_renderer/README.md b/packages/native_pdf_renderer/README.md index ab8872cb..dc861a76 100644 --- a/packages/native_pdf_renderer/README.md +++ b/packages/native_pdf_renderer/README.md @@ -9,7 +9,7 @@ In your flutter project add the dependency: [![pub package](https://img.shields.io/pub/v/native_pdf_renderer.svg)](https://pub.dartlang.org/packages/native_pdf_renderer) -```dart +```yaml dependencies: ... native_pdf_renderer: any