Close keyboard when scrolling on search results. Add ScrollDecorator.onInit.

This commit is contained in:
Shawn 2022-10-04 09:10:31 -06:00
parent fdaa834f0c
commit 0103ff16e8
2 changed files with 73 additions and 35 deletions

View File

@ -4,12 +4,17 @@ import 'package:flutter/material.dart';
/// Easily add visual decorations to a scrolling widget based on the state of its controller. /// Easily add visual decorations to a scrolling widget based on the state of its controller.
class ScrollDecorator extends StatefulWidget { class ScrollDecorator extends StatefulWidget {
/// Creates a widget that builds foreground and/or background decorations for a scrolling widget based on the state of /// Creates a widget that builds foreground and/or background decorations for a scrolling widget based on the state of
/// its ScrollController. /// its ScrollController.
// ignore: prefer_const_constructors_in_immutables // ignore: prefer_const_constructors_in_immutables
ScrollDecorator({Key? key, required this.builder, this.foregroundBuilder, this.backgroundBuilder, this.controller}) ScrollDecorator({
: super(key: key); Key? key,
required this.builder,
this.fgBuilder,
this.bgBuilder,
this.controller,
this.onInit,
}) : super(key: key);
/// Creates a ScrollDecorator that fades a widget in at the begin or end of the scrolling widget based on the scroll /// Creates a ScrollDecorator that fades a widget in at the begin or end of the scrolling widget based on the scroll
/// position. For example on a vertical list, it would fade in the `begin` widget when the list is not scrolled to the /// position. For example on a vertical list, it would fade in the `begin` widget when the list is not scrolled to the
@ -18,9 +23,10 @@ class ScrollDecorator extends StatefulWidget {
Key? key, Key? key,
required this.builder, required this.builder,
this.controller, this.controller,
this.onInit,
Widget? begin, Widget? begin,
Widget? end, Widget? end,
bool background = false, bool bg = false,
Axis direction = Axis.vertical, Axis direction = Axis.vertical,
Duration duration = const Duration(milliseconds: 150), Duration duration = const Duration(milliseconds: 150),
}) : super(key: key) { }) : super(key: key) {
@ -45,8 +51,8 @@ class ScrollDecorator extends StatefulWidget {
); );
} }
backgroundBuilder = background ? flexBuilder : null; bgBuilder = bg ? flexBuilder : null;
foregroundBuilder = !background ? flexBuilder : null; fgBuilder = !bg ? flexBuilder : null;
} }
/// Creates an ScrollDecorator that adds a shadow to the top of a vertical list when it is scrolled down. /// Creates an ScrollDecorator that adds a shadow to the top of a vertical list when it is scrolled down.
@ -54,10 +60,11 @@ class ScrollDecorator extends StatefulWidget {
Key? key, Key? key,
required this.builder, required this.builder,
this.controller, this.controller,
this.onInit,
Color color = Colors.black54, Color color = Colors.black54,
}) : super(key: key) { }) : super(key: key) {
backgroundBuilder = null; bgBuilder = null;
foregroundBuilder = (controller) { fgBuilder = (controller) {
final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0; final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0;
return IgnorePointer( return IgnorePointer(
child: Container( child: Container(
@ -84,11 +91,13 @@ class ScrollDecorator extends StatefulWidget {
/// Builder to create the decoration widget that will be layered in front of the scrolling widget. It should use the /// Builder to create the decoration widget that will be layered in front of the scrolling widget. It should use the
/// provided ScrollController to adjust its output as appropriate. /// provided ScrollController to adjust its output as appropriate.
late final ScrollBuilder? foregroundBuilder; late final ScrollBuilder? fgBuilder;
/// Builder to create the decoration widget that will be layered behind the scrolling widget. It should use the /// Builder to create the decoration widget that will be layered behind the scrolling widget. It should use the
/// provided ScrollController to adjust its output as appropriate. /// provided ScrollController to adjust its output as appropriate.
late final ScrollBuilder? backgroundBuilder; late final ScrollBuilder? bgBuilder;
final void Function(ScrollController controller)? onInit;
@override @override
State<ScrollDecorator> createState() => _ScrollDecoratorState(); State<ScrollDecorator> createState() => _ScrollDecoratorState();
@ -98,9 +107,12 @@ class _ScrollDecoratorState extends State<ScrollDecorator> {
ScrollController? _controller; ScrollController? _controller;
late Widget content; late Widget content;
ScrollController get currentController => (widget.controller ?? _controller)!;
@override @override
void initState() { void initState() {
if (widget.controller == null) _controller = ScrollController(); if (widget.controller == null) _controller = ScrollController();
widget.onInit?.call(currentController);
super.initState(); super.initState();
} }
@ -112,17 +124,19 @@ class _ScrollDecoratorState extends State<ScrollDecorator> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ScrollController controller = (widget.controller ?? _controller)!; content = widget.builder(currentController);
content = widget.builder(controller);
return AnimatedBuilder( return AnimatedBuilder(
animation: controller, animation: currentController,
builder: (_, __) { builder: (_, __) {
return Stack( return Stack(
children: [ children: [
if (widget.backgroundBuilder != null) widget.backgroundBuilder!(controller), if (widget.bgBuilder != null) ...[
widget.bgBuilder!(currentController),
],
content, content,
if (widget.foregroundBuilder != null) widget.foregroundBuilder!(controller), if (widget.fgBuilder != null) ...[
widget.fgBuilder!(currentController),
],
], ],
); );
}); });

View File

@ -1,32 +1,56 @@
part of '../artifact_search_screen.dart'; part of '../artifact_search_screen.dart';
/// Staggered Masonry styled grid for displaying two columns of different aspect-ratio images. /// Staggered Masonry styled grid for displaying two columns of different aspect-ratio images.
class _ResultsGrid extends StatelessWidget { class _ResultsGrid extends StatefulWidget {
const _ResultsGrid({Key? key, required this.searchResults, required this.onPressed}) : super(key: key); _ResultsGrid({Key? key, required this.searchResults, required this.onPressed}) : super(key: key);
final void Function(SearchData) onPressed; final void Function(SearchData) onPressed;
final List<SearchData> searchResults; final List<SearchData> searchResults;
@override
State<_ResultsGrid> createState() => _ResultsGridState();
}
class _ResultsGridState extends State<_ResultsGrid> {
late ScrollController _controller;
double _prevVel = -1;
void _handleResultsScrolled() {
// Hide the keyboard if the list is scrolled manually by the pointer, ignoring velocity based scroll changes
// ignore: INVALID_USE_OF_PROTECTED_MEMBER, INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER
final vel = _controller.position.activity?.velocity;
if (vel == 0 && _prevVel == 0) {
FocusManager.instance.primaryFocus?.unfocus();
}
_prevVel = vel ?? _prevVel;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ScrollDecorator.shadow( return ScrollDecorator.shadow(
builder: (controller) => CustomScrollView( onInit: (controller) => controller.addListener(_handleResultsScrolled),
controller: controller, builder: (controller) {
scrollBehavior: const ScrollBehavior().copyWith(scrollbars: false), _controller = controller;
clipBehavior: Clip.hardEdge, return CustomScrollView(
slivers: [ controller: controller,
SliverToBoxAdapter(child: _buildLanguageMessage(context)), scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
SliverPadding( clipBehavior: Clip.hardEdge,
padding: EdgeInsets.all($styles.insets.sm).copyWith(bottom: $styles.insets.offset * 1.5), slivers: [
sliver: SliverMasonryGrid.count( SliverToBoxAdapter(child: _buildLanguageMessage(context)),
crossAxisCount: 2, SliverPadding(
mainAxisSpacing: $styles.insets.sm, padding: EdgeInsets.all($styles.insets.sm).copyWith(bottom: $styles.insets.offset * 1.5),
crossAxisSpacing: $styles.insets.sm, sliver: SliverMasonryGrid.count(
childCount: searchResults.length, crossAxisCount: 2,
itemBuilder: (context, index) => _ResultTile(onPressed: onPressed, data: searchResults[index]), mainAxisSpacing: $styles.insets.sm,
crossAxisSpacing: $styles.insets.sm,
childCount: widget.searchResults.length,
itemBuilder: (context, index) =>
_ResultTile(onPressed: widget.onPressed, data: widget.searchResults[index]),
),
), ),
), ],
], );
), },
); );
} }