|
|
|
|
@@ -1,550 +0,0 @@
|
|
|
|
|
/// This is a modified version of [ReorderableListView] that was originally pulled from Flutter stable v2.0.4.
|
|
|
|
|
///
|
|
|
|
|
/// Change includes ensuring that the `padding` is pulled and applied to the inner padding/list padding, and not applied to the list externally.
|
|
|
|
|
/// This fixes the bug that caused the list to get clipped from the top padding.
|
|
|
|
|
|
|
|
|
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
// ignore_for_file: unnecessary_null_comparison
|
|
|
|
|
|
|
|
|
|
import 'dart:ui' show lerpDouble;
|
|
|
|
|
|
|
|
|
|
import 'package:flutter/gestures.dart';
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
|
|
|
|
|
|
/// A list whose items the user can interactively reorder by dragging.
|
|
|
|
|
///
|
|
|
|
|
/// This class is appropriate for views with a small number of
|
|
|
|
|
/// children because constructing the [List] requires doing work for every
|
|
|
|
|
/// child that could possibly be displayed in the list view instead of just
|
|
|
|
|
/// those children that are actually visible.
|
|
|
|
|
///
|
|
|
|
|
/// All list items must have a key.
|
|
|
|
|
///
|
|
|
|
|
/// {@youtube 560 315 https://www.youtube.com/watch?v=3fB1mxOsqJE}
|
|
|
|
|
///
|
|
|
|
|
/// This sample shows by dragging the user can reorder the items of the list.
|
|
|
|
|
/// The [onReorder] parameter is required and will be called when a child
|
|
|
|
|
/// widget is dragged to a new position.
|
|
|
|
|
///
|
|
|
|
|
/// {@tool dartpad --template=stateful_widget_scaffold}
|
|
|
|
|
///
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// final List<int> _items = List<int>.generate(50, (int index) => index);
|
|
|
|
|
///
|
|
|
|
|
/// @override
|
|
|
|
|
/// Widget build(BuildContext context){
|
|
|
|
|
/// final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
|
|
|
|
/// final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
|
|
|
|
|
/// final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
|
|
|
|
|
///
|
|
|
|
|
/// return ReorderableListView(
|
|
|
|
|
/// padding: const EdgeInsets.symmetric(horizontal: 40),
|
|
|
|
|
/// children: <Widget>[
|
|
|
|
|
/// for (int index = 0; index < _items.length; index++)
|
|
|
|
|
/// ListTile(
|
|
|
|
|
/// key: Key('$index'),
|
|
|
|
|
/// tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
|
|
|
|
|
/// title: Text('Item ${_items[index]}'),
|
|
|
|
|
/// ),
|
|
|
|
|
/// ],
|
|
|
|
|
/// onReorder: (int oldIndex, int newIndex) {
|
|
|
|
|
/// setState(() {
|
|
|
|
|
/// if (oldIndex < newIndex) {
|
|
|
|
|
/// newIndex -= 1;
|
|
|
|
|
/// }
|
|
|
|
|
/// final int item = _items.removeAt(oldIndex);
|
|
|
|
|
/// _items.insert(newIndex, item);
|
|
|
|
|
/// });
|
|
|
|
|
/// },
|
|
|
|
|
/// );
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
///{@end-tool}
|
|
|
|
|
class FlutterReorderableListView extends StatefulWidget {
|
|
|
|
|
/// Creates a reorderable list from a pre-built list of widgets.
|
|
|
|
|
///
|
|
|
|
|
/// See also:
|
|
|
|
|
///
|
|
|
|
|
/// * [ReorderableListView.builder], which allows you to build a reorderable
|
|
|
|
|
/// list where the items are built as needed when scrolling the list.
|
|
|
|
|
FlutterReorderableListView({
|
|
|
|
|
Key? key,
|
|
|
|
|
required List<Widget> children,
|
|
|
|
|
required this.onReorder,
|
|
|
|
|
this.proxyDecorator,
|
|
|
|
|
this.buildDefaultDragHandles = true,
|
|
|
|
|
this.padding,
|
|
|
|
|
this.header,
|
|
|
|
|
this.scrollDirection = Axis.vertical,
|
|
|
|
|
this.reverse = false,
|
|
|
|
|
this.scrollController,
|
|
|
|
|
this.primary,
|
|
|
|
|
this.physics,
|
|
|
|
|
this.shrinkWrap = false,
|
|
|
|
|
this.anchor = 0.0,
|
|
|
|
|
this.cacheExtent,
|
|
|
|
|
this.dragStartBehavior = DragStartBehavior.start,
|
|
|
|
|
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
|
|
|
|
|
this.restorationId,
|
|
|
|
|
this.clipBehavior = Clip.hardEdge,
|
|
|
|
|
}) : assert(scrollDirection != null),
|
|
|
|
|
assert(onReorder != null),
|
|
|
|
|
assert(children != null),
|
|
|
|
|
assert(
|
|
|
|
|
children.every((Widget w) => w.key != null),
|
|
|
|
|
'All children of this widget must have a key.',
|
|
|
|
|
),
|
|
|
|
|
assert(buildDefaultDragHandles != null),
|
|
|
|
|
itemBuilder = ((BuildContext context, int index) => children[index]),
|
|
|
|
|
itemCount = children.length,
|
|
|
|
|
super(key: key);
|
|
|
|
|
|
|
|
|
|
/// Creates a reorderable list from widget items that are created on demand.
|
|
|
|
|
///
|
|
|
|
|
/// This constructor is appropriate for list views with a large number of
|
|
|
|
|
/// children because the builder is called only for those children
|
|
|
|
|
/// that are actually visible.
|
|
|
|
|
///
|
|
|
|
|
/// The `itemBuilder` callback will be called only with indices greater than
|
|
|
|
|
/// or equal to zero and less than `itemCount`.
|
|
|
|
|
///
|
|
|
|
|
/// The `itemBuilder` should always return a non-null widget, and actually
|
|
|
|
|
/// create the widget instances when called. Avoid using a builder that
|
|
|
|
|
/// returns a previously-constructed widget; if the list view's children are
|
|
|
|
|
/// created in advance, or all at once when the [ReorderableListView] itself
|
|
|
|
|
/// is created, it is more efficient to use the [ReorderableListView]
|
|
|
|
|
/// constructor. Even more efficient, however, is to create the instances
|
|
|
|
|
/// on demand using this constructor's `itemBuilder` callback.
|
|
|
|
|
///
|
|
|
|
|
/// See also:
|
|
|
|
|
///
|
|
|
|
|
/// * [ReorderableListView], which allows you to build a reorderable
|
|
|
|
|
/// list with all the items passed into the constructor.
|
|
|
|
|
const FlutterReorderableListView.builder({
|
|
|
|
|
Key? key,
|
|
|
|
|
required this.itemBuilder,
|
|
|
|
|
required this.itemCount,
|
|
|
|
|
required this.onReorder,
|
|
|
|
|
this.proxyDecorator,
|
|
|
|
|
this.buildDefaultDragHandles = true,
|
|
|
|
|
this.padding,
|
|
|
|
|
this.header,
|
|
|
|
|
this.scrollDirection = Axis.vertical,
|
|
|
|
|
this.reverse = false,
|
|
|
|
|
this.scrollController,
|
|
|
|
|
this.primary,
|
|
|
|
|
this.physics,
|
|
|
|
|
this.shrinkWrap = false,
|
|
|
|
|
this.anchor = 0.0,
|
|
|
|
|
this.cacheExtent,
|
|
|
|
|
this.dragStartBehavior = DragStartBehavior.start,
|
|
|
|
|
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
|
|
|
|
|
this.restorationId,
|
|
|
|
|
this.clipBehavior = Clip.hardEdge,
|
|
|
|
|
}) : assert(scrollDirection != null),
|
|
|
|
|
assert(itemCount >= 0),
|
|
|
|
|
assert(onReorder != null),
|
|
|
|
|
assert(buildDefaultDragHandles != null),
|
|
|
|
|
super(key: key);
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.reorderable_list.itemBuilder}
|
|
|
|
|
final IndexedWidgetBuilder itemBuilder;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.reorderable_list.itemCount}
|
|
|
|
|
final int itemCount;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.reorderable_list.onReorder}
|
|
|
|
|
final ReorderCallback onReorder;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.reorderable_list.proxyDecorator}
|
|
|
|
|
final ReorderItemProxyDecorator? proxyDecorator;
|
|
|
|
|
|
|
|
|
|
/// If true: on desktop platforms, a drag handle is stacked over the
|
|
|
|
|
/// center of each item's trailing edge; on mobile platforms, a long
|
|
|
|
|
/// press anywhere on the item starts a drag.
|
|
|
|
|
///
|
|
|
|
|
/// The default desktop drag handle is just an [Icons.drag_handle]
|
|
|
|
|
/// wrapped by a [ReorderableDragStartListener]. On mobile
|
|
|
|
|
/// platforms, the entire item is wrapped with a
|
|
|
|
|
/// [ReorderableDelayedDragStartListener].
|
|
|
|
|
///
|
|
|
|
|
/// To change the appearance or the layout of the drag handles, make
|
|
|
|
|
/// this parameter false and wrap each list item, or a widget within
|
|
|
|
|
/// each list item, with [ReorderableDragStartListener] or
|
|
|
|
|
/// [ReorderableDelayedDragStartListener], or a custom subclass
|
|
|
|
|
/// of [ReorderableDragStartListener].
|
|
|
|
|
///
|
|
|
|
|
/// The following sample specifies `buildDefaultDragHandles: false`, and
|
|
|
|
|
/// uses a [Card] at the leading edge of each item for the item's drag handle.
|
|
|
|
|
///
|
|
|
|
|
/// {@tool dartpad --template=stateful_widget_scaffold}
|
|
|
|
|
///
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// final List<int> _items = List<int>.generate(50, (int index) => index);
|
|
|
|
|
///
|
|
|
|
|
/// @override
|
|
|
|
|
/// Widget build(BuildContext context){
|
|
|
|
|
/// final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
|
|
|
|
/// final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
|
|
|
|
|
/// final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
|
|
|
|
|
///
|
|
|
|
|
/// return ReorderableListView(
|
|
|
|
|
/// buildDefaultDragHandles: false,
|
|
|
|
|
/// children: <Widget>[
|
|
|
|
|
/// for (int index = 0; index < _items.length; index++)
|
|
|
|
|
/// Container(
|
|
|
|
|
/// key: Key('$index'),
|
|
|
|
|
/// color: _items[index].isOdd ? oddItemColor : evenItemColor,
|
|
|
|
|
/// child: Row(
|
|
|
|
|
/// children: <Widget>[
|
|
|
|
|
/// Container(
|
|
|
|
|
/// width: 64,
|
|
|
|
|
/// height: 64,
|
|
|
|
|
/// padding: const EdgeInsets.all(8),
|
|
|
|
|
/// child: ReorderableDragStartListener(
|
|
|
|
|
/// index: index,
|
|
|
|
|
/// child: Card(
|
|
|
|
|
/// color: colorScheme.primary,
|
|
|
|
|
/// elevation: 2,
|
|
|
|
|
/// ),
|
|
|
|
|
/// ),
|
|
|
|
|
/// ),
|
|
|
|
|
/// Text('Item ${_items[index]}'),
|
|
|
|
|
/// ],
|
|
|
|
|
/// ),
|
|
|
|
|
/// ),
|
|
|
|
|
/// ],
|
|
|
|
|
/// onReorder: (int oldIndex, int newIndex) {
|
|
|
|
|
/// setState(() {
|
|
|
|
|
/// if (oldIndex < newIndex) {
|
|
|
|
|
/// newIndex -= 1;
|
|
|
|
|
/// }
|
|
|
|
|
/// final int item = _items.removeAt(oldIndex);
|
|
|
|
|
/// _items.insert(newIndex, item);
|
|
|
|
|
/// });
|
|
|
|
|
/// },
|
|
|
|
|
/// );
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
///{@end-tool}
|
|
|
|
|
final bool buildDefaultDragHandles;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.reorderable_list.padding}
|
|
|
|
|
final EdgeInsets? padding;
|
|
|
|
|
|
|
|
|
|
/// A non-reorderable header item to show before the items of the list.
|
|
|
|
|
///
|
|
|
|
|
/// If null, no header will appear before the list.
|
|
|
|
|
final Widget? header;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scroll_view.scrollDirection}
|
|
|
|
|
final Axis scrollDirection;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scroll_view.reverse}
|
|
|
|
|
final bool reverse;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scroll_view.controller}
|
|
|
|
|
final ScrollController? scrollController;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scroll_view.primary}
|
|
|
|
|
|
|
|
|
|
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
|
|
|
|
|
/// [scrollController] is null.
|
|
|
|
|
final bool? primary;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scroll_view.physics}
|
|
|
|
|
final ScrollPhysics? physics;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scroll_view.shrinkWrap}
|
|
|
|
|
final bool shrinkWrap;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scroll_view.anchor}
|
|
|
|
|
final double anchor;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
|
|
|
|
|
final double? cacheExtent;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
|
|
|
|
|
final DragStartBehavior dragStartBehavior;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
|
|
|
|
|
///
|
|
|
|
|
/// The default is [ScrollViewKeyboardDismissBehavior.manual]
|
|
|
|
|
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.scrollable.restorationId}
|
|
|
|
|
final String? restorationId;
|
|
|
|
|
|
|
|
|
|
/// {@macro flutter.material.Material.clipBehavior}
|
|
|
|
|
///
|
|
|
|
|
/// Defaults to [Clip.hardEdge].
|
|
|
|
|
final Clip clipBehavior;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
_ReorderableListViewState createState() => _ReorderableListViewState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _ReorderableListViewState extends State<FlutterReorderableListView> {
|
|
|
|
|
Widget _wrapWithSemantics(Widget child, int index) {
|
|
|
|
|
void reorder(int startIndex, int endIndex) {
|
|
|
|
|
if (startIndex != endIndex) widget.onReorder(startIndex, endIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// First, determine which semantics actions apply.
|
|
|
|
|
final Map<CustomSemanticsAction, VoidCallback> semanticsActions =
|
|
|
|
|
<CustomSemanticsAction, VoidCallback>{};
|
|
|
|
|
|
|
|
|
|
// Create the appropriate semantics actions.
|
|
|
|
|
void moveToStart() => reorder(index, 0);
|
|
|
|
|
void moveToEnd() => reorder(index, widget.itemCount);
|
|
|
|
|
void moveBefore() => reorder(index, index - 1);
|
|
|
|
|
// To move after, we go to index+2 because we are moving it to the space
|
|
|
|
|
// before index+2, which is after the space at index+1.
|
|
|
|
|
void moveAfter() => reorder(index, index + 2);
|
|
|
|
|
|
|
|
|
|
final MaterialLocalizations localizations =
|
|
|
|
|
MaterialLocalizations.of(context);
|
|
|
|
|
|
|
|
|
|
// If the item can move to before its current position in the list.
|
|
|
|
|
if (index > 0) {
|
|
|
|
|
semanticsActions[
|
|
|
|
|
CustomSemanticsAction(label: localizations.reorderItemToStart)] =
|
|
|
|
|
moveToStart;
|
|
|
|
|
String reorderItemBefore = localizations.reorderItemUp;
|
|
|
|
|
if (widget.scrollDirection == Axis.horizontal) {
|
|
|
|
|
reorderItemBefore = Directionality.of(context) == TextDirection.ltr
|
|
|
|
|
? localizations.reorderItemLeft
|
|
|
|
|
: localizations.reorderItemRight;
|
|
|
|
|
}
|
|
|
|
|
semanticsActions[CustomSemanticsAction(label: reorderItemBefore)] =
|
|
|
|
|
moveBefore;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the item can move to after its current position in the list.
|
|
|
|
|
if (index < widget.itemCount - 1) {
|
|
|
|
|
String reorderItemAfter = localizations.reorderItemDown;
|
|
|
|
|
if (widget.scrollDirection == Axis.horizontal) {
|
|
|
|
|
reorderItemAfter = Directionality.of(context) == TextDirection.ltr
|
|
|
|
|
? localizations.reorderItemRight
|
|
|
|
|
: localizations.reorderItemLeft;
|
|
|
|
|
}
|
|
|
|
|
semanticsActions[CustomSemanticsAction(label: reorderItemAfter)] =
|
|
|
|
|
moveAfter;
|
|
|
|
|
semanticsActions[
|
|
|
|
|
CustomSemanticsAction(label: localizations.reorderItemToEnd)] =
|
|
|
|
|
moveToEnd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We pass toWrap with a GlobalKey into the item so that when it
|
|
|
|
|
// gets dragged, the accessibility framework can preserve the selected
|
|
|
|
|
// state of the dragging item.
|
|
|
|
|
//
|
|
|
|
|
// We also apply the relevant custom accessibility actions for moving the item
|
|
|
|
|
// up, down, to the start, and to the end of the list.
|
|
|
|
|
return MergeSemantics(
|
|
|
|
|
child: Semantics(
|
|
|
|
|
customSemanticsActions: semanticsActions,
|
|
|
|
|
child: child,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _itemBuilder(BuildContext context, int index) {
|
|
|
|
|
final Widget item = widget.itemBuilder(context, index);
|
|
|
|
|
assert(() {
|
|
|
|
|
if (item.key == null) {
|
|
|
|
|
throw FlutterError(
|
|
|
|
|
'Every item of ReorderableListView must have a key.',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}());
|
|
|
|
|
|
|
|
|
|
// _ReorderableItem so the widget versions can have them as well.
|
|
|
|
|
final Widget itemWithSemantics = _wrapWithSemantics(item, index);
|
|
|
|
|
final Key itemGlobalKey =
|
|
|
|
|
_ReorderableListViewChildGlobalKey(item.key!, this);
|
|
|
|
|
|
|
|
|
|
if (widget.buildDefaultDragHandles) {
|
|
|
|
|
switch (Theme.of(context).platform) {
|
|
|
|
|
case TargetPlatform.fuchsia:
|
|
|
|
|
case TargetPlatform.linux:
|
|
|
|
|
case TargetPlatform.windows:
|
|
|
|
|
case TargetPlatform.macOS:
|
|
|
|
|
switch (widget.scrollDirection) {
|
|
|
|
|
case Axis.horizontal:
|
|
|
|
|
return Stack(
|
|
|
|
|
key: itemGlobalKey,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
itemWithSemantics,
|
|
|
|
|
Positioned.directional(
|
|
|
|
|
textDirection: Directionality.of(context),
|
|
|
|
|
start: 0,
|
|
|
|
|
end: 0,
|
|
|
|
|
bottom: 8,
|
|
|
|
|
child: Align(
|
|
|
|
|
alignment: AlignmentDirectional.bottomCenter,
|
|
|
|
|
child: ReorderableDragStartListener(
|
|
|
|
|
index: index,
|
|
|
|
|
child: const Icon(Icons.drag_handle),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
case Axis.vertical:
|
|
|
|
|
return Stack(
|
|
|
|
|
key: itemGlobalKey,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
itemWithSemantics,
|
|
|
|
|
Positioned.directional(
|
|
|
|
|
textDirection: Directionality.of(context),
|
|
|
|
|
top: 0,
|
|
|
|
|
bottom: 0,
|
|
|
|
|
end: 8,
|
|
|
|
|
child: Align(
|
|
|
|
|
alignment: AlignmentDirectional.centerEnd,
|
|
|
|
|
child: ReorderableDragStartListener(
|
|
|
|
|
index: index,
|
|
|
|
|
child: const Icon(Icons.drag_handle),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
case TargetPlatform.iOS:
|
|
|
|
|
case TargetPlatform.android:
|
|
|
|
|
return ReorderableDelayedDragStartListener(
|
|
|
|
|
key: itemGlobalKey,
|
|
|
|
|
index: index,
|
|
|
|
|
child: itemWithSemantics,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return KeyedSubtree(
|
|
|
|
|
key: itemGlobalKey,
|
|
|
|
|
child: itemWithSemantics,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _proxyDecorator(Widget child, int index, Animation<double> animation) {
|
|
|
|
|
return AnimatedBuilder(
|
|
|
|
|
animation: animation,
|
|
|
|
|
builder: (BuildContext context, Widget? child) {
|
|
|
|
|
final double animValue = Curves.easeInOut.transform(animation.value);
|
|
|
|
|
final double elevation = lerpDouble(0, 6, animValue)!;
|
|
|
|
|
return Material(
|
|
|
|
|
child: child,
|
|
|
|
|
elevation: elevation,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
child: child,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
assert(debugCheckHasMaterialLocalizations(context));
|
|
|
|
|
assert(debugCheckHasOverlay(context));
|
|
|
|
|
|
|
|
|
|
// If there is a header we can't just apply the padding to the list,
|
|
|
|
|
// so if header is not present, we wrap the CustomScrollView in the padding form the left and right
|
|
|
|
|
// and add the padding from the bottom and top to the sliver list. If header if present
|
|
|
|
|
// we only padd top of the header and bottom of the sliver list (or the equivalent for other axis directions).
|
|
|
|
|
final EdgeInsets padding = widget.padding ?? EdgeInsets.zero;
|
|
|
|
|
late EdgeInsets outerPadding;
|
|
|
|
|
late EdgeInsets listPadding;
|
|
|
|
|
switch (widget.scrollDirection) {
|
|
|
|
|
case Axis.horizontal:
|
|
|
|
|
if (widget.reverse) {
|
|
|
|
|
outerPadding = EdgeInsets.fromLTRB(
|
|
|
|
|
0, padding.top, padding.right, padding.bottom);
|
|
|
|
|
listPadding = EdgeInsets.fromLTRB(padding.left, 0, 0, 0);
|
|
|
|
|
} else {
|
|
|
|
|
outerPadding =
|
|
|
|
|
EdgeInsets.fromLTRB(padding.left, padding.top, 0, padding.bottom);
|
|
|
|
|
listPadding = EdgeInsets.fromLTRB(0, 0, padding.right, 0);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Axis.vertical:
|
|
|
|
|
if (widget.reverse) {
|
|
|
|
|
outerPadding = EdgeInsets.fromLTRB(padding.left, 0, padding.right, 0);
|
|
|
|
|
listPadding = EdgeInsets.fromLTRB(
|
|
|
|
|
0, padding.top, 0, widget.header != null ? 0 : padding.bottom);
|
|
|
|
|
} else {
|
|
|
|
|
outerPadding = EdgeInsets.fromLTRB(padding.left, 0, padding.right, 0);
|
|
|
|
|
listPadding = EdgeInsets.fromLTRB(
|
|
|
|
|
0, widget.header != null ? 0 : padding.top, 0, padding.bottom);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Padding(
|
|
|
|
|
padding: outerPadding,
|
|
|
|
|
child: CustomScrollView(
|
|
|
|
|
scrollDirection: widget.scrollDirection,
|
|
|
|
|
reverse: widget.reverse,
|
|
|
|
|
controller: widget.scrollController,
|
|
|
|
|
primary: widget.primary,
|
|
|
|
|
physics: widget.physics,
|
|
|
|
|
shrinkWrap: widget.shrinkWrap,
|
|
|
|
|
anchor: widget.anchor,
|
|
|
|
|
cacheExtent: widget.cacheExtent,
|
|
|
|
|
dragStartBehavior: widget.dragStartBehavior,
|
|
|
|
|
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
|
|
|
|
restorationId: widget.restorationId,
|
|
|
|
|
clipBehavior: widget.clipBehavior,
|
|
|
|
|
slivers: <Widget>[
|
|
|
|
|
if (widget.header != null)
|
|
|
|
|
SliverPadding(
|
|
|
|
|
padding: EdgeInsets.fromLTRB(0, widget.reverse ? 0 : padding.top,
|
|
|
|
|
0, widget.reverse ? padding.bottom : 0),
|
|
|
|
|
sliver: SliverToBoxAdapter(child: widget.header),
|
|
|
|
|
),
|
|
|
|
|
SliverPadding(
|
|
|
|
|
padding: listPadding,
|
|
|
|
|
sliver: SliverReorderableList(
|
|
|
|
|
itemBuilder: _itemBuilder,
|
|
|
|
|
itemCount: widget.itemCount,
|
|
|
|
|
onReorder: widget.onReorder,
|
|
|
|
|
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A global key that takes its identity from the object and uses a value of a
|
|
|
|
|
// particular type to identify itself.
|
|
|
|
|
//
|
|
|
|
|
// The difference with GlobalObjectKey is that it uses [==] instead of [identical]
|
|
|
|
|
// of the objects used to generate widgets.
|
|
|
|
|
@optionalTypeArgs
|
|
|
|
|
class _ReorderableListViewChildGlobalKey extends GlobalObjectKey {
|
|
|
|
|
const _ReorderableListViewChildGlobalKey(this.subKey, this.state)
|
|
|
|
|
: super(subKey);
|
|
|
|
|
|
|
|
|
|
final Key subKey;
|
|
|
|
|
final State state;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
bool operator ==(Object other) {
|
|
|
|
|
if (other.runtimeType != runtimeType) return false;
|
|
|
|
|
return other is _ReorderableListViewChildGlobalKey &&
|
|
|
|
|
other.subKey == subKey &&
|
|
|
|
|
other.state == state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
int get hashCode => hashValues(subKey, state);
|
|
|
|
|
}
|