Close keyboard when scrolling on search results. Add ScrollDecorator.onInit.
This commit is contained in:
parent
fdaa834f0c
commit
0103ff16e8
@ -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),
|
||||
],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
@ -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]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user