adding packages
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'ui/sliver_search_bar.dart';
|
||||
export 'ui/sliver_search_bar.dart';
|
||||
|
||||
class FloatingSearchBar extends StatelessWidget {
|
||||
FloatingSearchBar({
|
||||
this.body,
|
||||
this.drawer,
|
||||
this.trailing,
|
||||
this.leading,
|
||||
this.endDrawer,
|
||||
this.controller,
|
||||
this.onChanged,
|
||||
this.title,
|
||||
this.decoration,
|
||||
this.onTap,
|
||||
this.padding = EdgeInsets.zero,
|
||||
this.pinned = false,
|
||||
@required List<Widget> children,
|
||||
}) : _childDelagate = SliverChildListDelegate(
|
||||
children,
|
||||
);
|
||||
|
||||
FloatingSearchBar.builder({
|
||||
this.body,
|
||||
this.drawer,
|
||||
this.endDrawer,
|
||||
this.trailing,
|
||||
this.leading,
|
||||
this.controller,
|
||||
this.onChanged,
|
||||
this.title,
|
||||
this.onTap,
|
||||
this.decoration,
|
||||
this.padding = EdgeInsets.zero,
|
||||
this.pinned = false,
|
||||
@required IndexedWidgetBuilder itemBuilder,
|
||||
@required int itemCount,
|
||||
}) : _childDelagate = SliverChildBuilderDelegate(
|
||||
itemBuilder,
|
||||
childCount: itemCount,
|
||||
);
|
||||
|
||||
final Widget leading, trailing, body, drawer, endDrawer;
|
||||
|
||||
final SliverChildDelegate _childDelagate;
|
||||
|
||||
final TextEditingController controller;
|
||||
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
final InputDecoration decoration;
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
/// Override the search field
|
||||
final Widget title;
|
||||
|
||||
final bool pinned;
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
drawer: drawer,
|
||||
endDrawer: endDrawer,
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (context, enabled) {
|
||||
return [
|
||||
SliverPadding(
|
||||
padding: padding,
|
||||
sliver: SliverFloatingBar(
|
||||
leading: leading,
|
||||
floating: !pinned,
|
||||
pinned: pinned,
|
||||
title: title ??
|
||||
TextField(
|
||||
controller: controller,
|
||||
decoration: decoration ??
|
||||
InputDecoration.collapsed(
|
||||
hintText: "Search...",
|
||||
),
|
||||
autofocus: false,
|
||||
onChanged: onChanged,
|
||||
onTap: onTap,
|
||||
),
|
||||
trailing: trailing,
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: ListView.custom(childrenDelegate: _childDelagate),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class SliverFloatingBar extends StatefulWidget {
|
||||
/// Creates a material design app bar that can be placed in a [CustomScrollView].
|
||||
///
|
||||
/// The arguments [forceElevated], [primary], [floating], [pinned], [snap]
|
||||
/// and [automaticallyImplyLeading] must not be null.
|
||||
const SliverFloatingBar({
|
||||
Key key,
|
||||
this.leading,
|
||||
this.automaticallyImplyLeading = true,
|
||||
this.title,
|
||||
this.trailing,
|
||||
this.elevation = 5.0,
|
||||
this.backgroundColor,
|
||||
this.floating = false,
|
||||
this.pinned = false,
|
||||
this.snap = false,
|
||||
}) : assert(automaticallyImplyLeading != null),
|
||||
assert(floating != null),
|
||||
assert(pinned != null),
|
||||
assert(snap != null),
|
||||
assert(floating || !snap,
|
||||
'The "snap" argument only makes sense for floating app bars.'),
|
||||
super(key: key);
|
||||
|
||||
/// A widget to display before the [title].
|
||||
///
|
||||
/// If this is null and [automaticallyImplyLeading] is set to true, the [AppBar] will
|
||||
/// imply an appropriate widget. For example, if the [AppBar] is in a [Scaffold]
|
||||
/// that also has a [Drawer], the [Scaffold] will fill this widget with an
|
||||
/// [IconButton] that opens the drawer. If there's no [Drawer] and the parent
|
||||
/// [Navigator] can go back, the [AppBar] will use a [BackButton] that calls
|
||||
/// [Navigator.maybePop].
|
||||
final Widget leading;
|
||||
|
||||
/// Controls whether we should try to imply the leading widget if null.
|
||||
///
|
||||
/// If true and [leading] is null, automatically try to deduce what the leading
|
||||
/// widget should be. If false and [leading] is null, leading space is given to [title].
|
||||
/// If leading widget is not null, this parameter has no effect.
|
||||
final bool automaticallyImplyLeading;
|
||||
|
||||
/// The primary widget displayed in the appbar.
|
||||
///
|
||||
/// Typically a [Text] widget containing a description of the current contents
|
||||
/// of the app.
|
||||
final Widget title;
|
||||
|
||||
final Widget trailing;
|
||||
|
||||
/// The z-coordinate at which to place this app bar when it is above other
|
||||
/// content. This controls the size of the shadow below the app bar.
|
||||
///
|
||||
/// Defaults to 4, the appropriate elevation for app bars.
|
||||
///
|
||||
/// If [forceElevated] is false, the elevation is ignored when the app bar has
|
||||
/// no content underneath it. For example, if the app bar is [pinned] but no
|
||||
/// content is scrolled under it, or if it scrolls with the content, then no
|
||||
/// shadow is drawn, regardless of the value of [elevation].
|
||||
final double elevation;
|
||||
|
||||
/// The color to use for the app bar's material. Typically this should be set
|
||||
/// along with [brightness], [iconTheme], [textTheme].
|
||||
///
|
||||
/// Defaults to [ThemeData.primaryColor].
|
||||
final Color backgroundColor;
|
||||
|
||||
/// Whether the app bar should become visible as soon as the user scrolls
|
||||
/// towards the app bar.
|
||||
///
|
||||
/// Otherwise, the user will need to scroll near the top of the scroll view to
|
||||
/// reveal the app bar.
|
||||
///
|
||||
/// If [snap] is true then a scroll that exposes the app bar will trigger an
|
||||
/// animation that slides the entire app bar into view. Similarly if a scroll
|
||||
/// dismisses the app bar, the animation will slide it completely out of view.
|
||||
///
|
||||
/// ## Animated Examples
|
||||
///
|
||||
/// The following animations show how the app bar changes its scrolling
|
||||
/// behavior based on the value of this property.
|
||||
///
|
||||
/// * App bar with [floating] set to false:
|
||||
/// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
|
||||
/// * App bar with [floating] set to true:
|
||||
/// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverAppBar] for more animated examples of how this property changes the
|
||||
/// behavior of the app bar in combination with [pinned] and [snap].
|
||||
final bool floating;
|
||||
|
||||
/// Whether the app bar should remain visible at the start of the scroll view.
|
||||
///
|
||||
/// The app bar can still expand and contract as the user scrolls, but it will
|
||||
/// remain visible rather than being scrolled out of view.
|
||||
///
|
||||
/// ## Animated Examples
|
||||
///
|
||||
/// The following animations show how the app bar changes its scrolling
|
||||
/// behavior based on the value of this property.
|
||||
///
|
||||
/// * App bar with [pinned] set to false:
|
||||
/// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
|
||||
/// * App bar with [pinned] set to true:
|
||||
/// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned.mp4}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverAppBar] for more animated examples of how this property changes the
|
||||
/// behavior of the app bar in combination with [floating].
|
||||
final bool pinned;
|
||||
|
||||
/// If [snap] and [floating] are true then the floating app bar will "snap"
|
||||
/// into view.
|
||||
///
|
||||
/// If [snap] is true then a scroll that exposes the floating app bar will
|
||||
/// trigger an animation that slides the entire app bar into view. Similarly if
|
||||
/// a scroll dismisses the app bar, the animation will slide the app bar
|
||||
/// completely out of view.
|
||||
///
|
||||
/// Snapping only applies when the app bar is floating, not when the appbar
|
||||
/// appears at the top of its scroll view.
|
||||
///
|
||||
/// ## Animated Examples
|
||||
///
|
||||
/// The following animations show how the app bar changes its scrolling
|
||||
/// behavior based on the value of this property.
|
||||
///
|
||||
/// * App bar with [snap] set to false:
|
||||
/// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
|
||||
/// * App bar with [snap] set to true:
|
||||
/// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating_snap.mp4}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverAppBar] for more animated examples of how this property changes the
|
||||
/// behavior of the app bar in combination with [pinned] and [floating].
|
||||
final bool snap;
|
||||
|
||||
@override
|
||||
_SliverFloatingBarState createState() => _SliverFloatingBarState();
|
||||
}
|
||||
|
||||
class _SliverFloatingBarState extends State<SliverFloatingBar>
|
||||
with TickerProviderStateMixin {
|
||||
FloatingHeaderSnapConfiguration _snapConfiguration;
|
||||
|
||||
void _updateSnapConfiguration() {
|
||||
if (widget.snap && widget.floating) {
|
||||
_snapConfiguration = FloatingHeaderSnapConfiguration(
|
||||
vsync: this,
|
||||
curve: Curves.easeOut,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} else {
|
||||
_snapConfiguration = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_updateSnapConfiguration();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(SliverFloatingBar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.snap != oldWidget.snap || widget.floating != oldWidget.floating)
|
||||
_updateSnapConfiguration();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double topPadding = MediaQuery.of(context).padding.top;
|
||||
final double collapsedHeight =
|
||||
(widget.pinned && widget.floating) ? topPadding : null;
|
||||
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeBottom: true,
|
||||
child: SliverPersistentHeader(
|
||||
floating: widget.floating,
|
||||
pinned: widget.pinned,
|
||||
delegate: _SliverAppBarDelegate(
|
||||
leading: widget.leading,
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
title: widget.title,
|
||||
trailing: widget.trailing,
|
||||
elevation: widget.elevation,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
floating: widget.floating,
|
||||
pinned: widget.pinned,
|
||||
snapConfiguration: _snapConfiguration,
|
||||
collapsedHeight: collapsedHeight,
|
||||
topPadding: topPadding,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FloatingAppBar extends StatefulWidget {
|
||||
const _FloatingAppBar({Key key, this.child}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
_FloatingAppBarState createState() => _FloatingAppBarState();
|
||||
}
|
||||
|
||||
// A wrapper for the widget created by _SliverAppBarDelegate that starts and
|
||||
/// stops the floating appbar's snap-into-view or snap-out-of-view animation.
|
||||
class _FloatingAppBarState extends State<_FloatingAppBar> {
|
||||
ScrollPosition _position;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (_position != null)
|
||||
_position.isScrollingNotifier.removeListener(_isScrollingListener);
|
||||
_position = Scrollable.of(context)?.position;
|
||||
if (_position != null)
|
||||
_position.isScrollingNotifier.addListener(_isScrollingListener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_position != null)
|
||||
_position.isScrollingNotifier.removeListener(_isScrollingListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
RenderSliverFloatingPersistentHeader _headerRenderer() {
|
||||
return context.ancestorRenderObjectOfType(
|
||||
const TypeMatcher<RenderSliverFloatingPersistentHeader>());
|
||||
}
|
||||
|
||||
void _isScrollingListener() {
|
||||
if (_position == null) return;
|
||||
|
||||
// When a scroll stops, then maybe snap the appbar into view.
|
||||
// Similarly, when a scroll starts, then maybe stop the snap animation.
|
||||
final RenderSliverFloatingPersistentHeader header = _headerRenderer();
|
||||
if (_position.isScrollingNotifier.value)
|
||||
header?.maybeStopSnapAnimation(_position.userScrollDirection);
|
||||
else
|
||||
header?.maybeStartSnapAnimation(_position.userScrollDirection);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
|
||||
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
||||
_SliverAppBarDelegate({
|
||||
@required this.leading,
|
||||
@required this.automaticallyImplyLeading,
|
||||
@required this.title,
|
||||
@required this.trailing,
|
||||
@required this.elevation,
|
||||
@required this.backgroundColor,
|
||||
@required this.floating,
|
||||
@required this.pinned,
|
||||
@required this.snapConfiguration,
|
||||
@required this.collapsedHeight,
|
||||
@required this.topPadding,
|
||||
});
|
||||
|
||||
final Widget trailing;
|
||||
final bool automaticallyImplyLeading;
|
||||
final Color backgroundColor;
|
||||
final double elevation;
|
||||
final bool floating;
|
||||
final Widget leading;
|
||||
final bool pinned;
|
||||
final Widget title;
|
||||
final double collapsedHeight;
|
||||
final double topPadding;
|
||||
|
||||
@override
|
||||
double get minExtent => collapsedHeight ?? (topPadding + kToolbarHeight);
|
||||
|
||||
@override
|
||||
final FloatingHeaderSnapConfiguration snapConfiguration;
|
||||
|
||||
@override
|
||||
double get maxExtent => math.max(topPadding + (kToolbarHeight), minExtent);
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant _SliverAppBarDelegate oldDelegate) {
|
||||
return leading != oldDelegate.leading ||
|
||||
automaticallyImplyLeading != oldDelegate.automaticallyImplyLeading ||
|
||||
title != oldDelegate.title ||
|
||||
trailing != oldDelegate.trailing ||
|
||||
elevation != oldDelegate.elevation ||
|
||||
topPadding != oldDelegate.topPadding ||
|
||||
collapsedHeight != oldDelegate.collapsedHeight ||
|
||||
backgroundColor != oldDelegate.backgroundColor ||
|
||||
pinned != oldDelegate.pinned ||
|
||||
floating != oldDelegate.floating ||
|
||||
snapConfiguration != oldDelegate.snapConfiguration;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
final double visibleMainHeight = maxExtent - shrinkOffset;
|
||||
|
||||
// // Truth table for `toolbarOpacity`:
|
||||
// // pinned | floating | bottom != null || opacity
|
||||
// // ----------------------------------------------
|
||||
// // 0 | 0 | 0 || fade
|
||||
// // 0 | 0 | 1 || fade
|
||||
// // 0 | 1 | 0 || fade
|
||||
// // 0 | 1 | 1 || fade
|
||||
// // 1 | 0 | 0 || 1.0
|
||||
// // 1 | 0 | 1 || 1.0
|
||||
// // 1 | 1 | 0 || 1.0
|
||||
// // 1 | 1 | 1 || fade
|
||||
final double toolbarOpacity = !pinned || (floating)
|
||||
? ((visibleMainHeight) / kToolbarHeight).clamp(0.0, 1.0)
|
||||
: 1.0;
|
||||
|
||||
final Widget appBar = FlexibleSpaceBar.createSettings(
|
||||
minExtent: minExtent,
|
||||
maxExtent: maxExtent,
|
||||
currentExtent: math.max(minExtent, maxExtent - shrinkOffset),
|
||||
toolbarOpacity: toolbarOpacity,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SafeArea(
|
||||
child: Material(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
elevation: elevation,
|
||||
child: ListTile(
|
||||
leading: leading ??
|
||||
(Scaffold.of(context).hasDrawer && automaticallyImplyLeading
|
||||
? IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
)
|
||||
: null),
|
||||
title: title,
|
||||
trailing: trailing ??
|
||||
(Scaffold.of(context).hasEndDrawer &&
|
||||
automaticallyImplyLeading
|
||||
? IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openEndDrawer();
|
||||
},
|
||||
)
|
||||
: null),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return Container(child: floating ? _FloatingAppBar(child: appBar) : appBar);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user