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.
class ScrollDecorator extends StatefulWidget {
/// Creates a widget that builds foreground and/or background decorations for a scrolling widget based on the state of
/// its ScrollController.
// ignore: prefer_const_constructors_in_immutables
ScrollDecorator({Key? key, required this.builder, this.foregroundBuilder, this.backgroundBuilder, this.controller})
: super(key: key);
ScrollDecorator({
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
/// 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,
required this.builder,
this.controller,
this.onInit,
Widget? begin,
Widget? end,
bool background = false,
bool bg = false,
Axis direction = Axis.vertical,
Duration duration = const Duration(milliseconds: 150),
}) : super(key: key) {
@ -45,8 +51,8 @@ class ScrollDecorator extends StatefulWidget {
);
}
backgroundBuilder = background ? flexBuilder : null;
foregroundBuilder = !background ? flexBuilder : null;
bgBuilder = bg ? 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.
@ -54,10 +60,11 @@ class ScrollDecorator extends StatefulWidget {
Key? key,
required this.builder,
this.controller,
this.onInit,
Color color = Colors.black54,
}) : super(key: key) {
backgroundBuilder = null;
foregroundBuilder = (controller) {
bgBuilder = null;
fgBuilder = (controller) {
final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0;
return IgnorePointer(
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
/// 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
/// provided ScrollController to adjust its output as appropriate.
late final ScrollBuilder? backgroundBuilder;
late final ScrollBuilder? bgBuilder;
final void Function(ScrollController controller)? onInit;
@override
State<ScrollDecorator> createState() => _ScrollDecoratorState();
@ -98,9 +107,12 @@ class _ScrollDecoratorState extends State<ScrollDecorator> {
ScrollController? _controller;
late Widget content;
ScrollController get currentController => (widget.controller ?? _controller)!;
@override
void initState() {
if (widget.controller == null) _controller = ScrollController();
widget.onInit?.call(currentController);
super.initState();
}
@ -112,17 +124,19 @@ class _ScrollDecoratorState extends State<ScrollDecorator> {
@override
Widget build(BuildContext context) {
ScrollController controller = (widget.controller ?? _controller)!;
content = widget.builder(controller);
content = widget.builder(currentController);
return AnimatedBuilder(
animation: controller,
animation: currentController,
builder: (_, __) {
return Stack(
children: [
if (widget.backgroundBuilder != null) widget.backgroundBuilder!(controller),
if (widget.bgBuilder != null) ...[
widget.bgBuilder!(currentController),
],
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';
/// Staggered Masonry styled grid for displaying two columns of different aspect-ratio images.
class _ResultsGrid extends StatelessWidget {
const _ResultsGrid({Key? key, required this.searchResults, required this.onPressed}) : super(key: key);
class _ResultsGrid extends StatefulWidget {
_ResultsGrid({Key? key, required this.searchResults, required this.onPressed}) : super(key: key);
final void Function(SearchData) onPressed;
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
Widget build(BuildContext context) {
return ScrollDecorator.shadow(
builder: (controller) => CustomScrollView(
controller: controller,
scrollBehavior: const ScrollBehavior().copyWith(scrollbars: false),
clipBehavior: Clip.hardEdge,
slivers: [
SliverToBoxAdapter(child: _buildLanguageMessage(context)),
SliverPadding(
padding: EdgeInsets.all($styles.insets.sm).copyWith(bottom: $styles.insets.offset * 1.5),
sliver: SliverMasonryGrid.count(
crossAxisCount: 2,
mainAxisSpacing: $styles.insets.sm,
crossAxisSpacing: $styles.insets.sm,
childCount: searchResults.length,
itemBuilder: (context, index) => _ResultTile(onPressed: onPressed, data: searchResults[index]),
onInit: (controller) => controller.addListener(_handleResultsScrolled),
builder: (controller) {
_controller = controller;
return CustomScrollView(
controller: controller,
scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
clipBehavior: Clip.hardEdge,
slivers: [
SliverToBoxAdapter(child: _buildLanguageMessage(context)),
SliverPadding(
padding: EdgeInsets.all($styles.insets.sm).copyWith(bottom: $styles.insets.offset * 1.5),
sliver: SliverMasonryGrid.count(
crossAxisCount: 2,
mainAxisSpacing: $styles.insets.sm,
crossAxisSpacing: $styles.insets.sm,
childCount: widget.searchResults.length,
itemBuilder: (context, index) =>
_ResultTile(onPressed: widget.onPressed, data: widget.searchResults[index]),
),
),
),
],
),
],
);
},
);
}