| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107 |
- // Copyright 2018 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- import 'dart:math';
- import 'package:flutter/material.dart';
- import 'package:flutter/rendering.dart';
- import './passthrough_overlay.dart';
- import './reorderable_mixin.dart';
- import './reorderable_widget.dart';
- import './typedefs.dart';
- /// Reorderable (drag and drop) version of [Flex], a widget that displays its
- /// draggable children in a one-dimensional array.
- ///
- /// The [ReorderableFlex] widget has allows you to control the axis along which
- /// the children are placed (horizontal or vertical). This is referred to as the
- /// [direction]. If you know the main axis in advance, then consider using
- /// a [ReorderableRow] (if it's horizontal) or [ReorderableColumn] (if it's
- /// vertical) instead, because that will be less verbose.
- ///
- /// In addition to other parameters in [Flex]'s constructor, this widget also
- /// has [header] and [footer] for placing non-reorderable widgets at the
- /// top/left and bottom/right of the widget. If further control is needed, you
- /// can use [buildItemsContainer] to customize how each item is contained, or
- /// use [buildDraggableFeedback] to customize the [feedback] of the internal
- /// [LongPressDraggable]. Consider using [ReorderableRow] or [ReorderableColumn]
- /// instead using this widget directly.
- ///
- /// All [children] must have a key.
- ///
- /// See also:
- ///
- /// * [ReorderableRow], for a version of this widget that is always horizontal.
- /// * [ReorderableColumn], for a version of this widget that is always vertical.
- class ReorderableFlex extends StatefulWidget {
- /// Creates a reorderable list.
- ReorderableFlex({
- Key? key,
- this.header,
- this.footer,
- required this.children,
- required this.onReorder,
- required this.direction,
- this.scrollDirection = Axis.vertical,
- this.padding,
- this.buildItemsContainer,
- this.buildDraggableFeedback,
- this.mainAxisAlignment = MainAxisAlignment.start,
- this.onNoReorder,
- this.onReorderStarted,
- this.scrollController,
- this.needsLongPressDraggable = true,
- this.draggingWidgetOpacity = 0.2,
- this.reorderAnimationDuration,
- this.scrollAnimationDuration,
- this.draggedItemBuilder,
- this.ignorePrimaryScrollController = false,
- }) : assert(
- children.every((Widget w) => w.key != null),
- 'All children of this widget must have a key.',
- ),
- super(key: key);
- /// A non-reorderable header widget to show before the list.
- ///
- /// If null, no header will appear at the top/left of the widget.
- final Widget? header;
- final Widget Function(BuildContext context, int index)? draggedItemBuilder;
- /// A non-reorderable footer widget to show after the list.
- ///
- /// If null, no footer will appear at the bottom/right of the widget.
- final Widget? footer;
- /// The widgets to display.
- final List<Widget> children;
- /// The [Axis] along which the list scrolls.
- ///
- /// List [children] can only drag along this [Axis].
- final Axis direction;
- final Axis scrollDirection;
- final ScrollController? scrollController;
- /// The amount of space by which to inset the [children].
- final EdgeInsets? padding;
- /// Called when a child is dropped into a new position to shuffle the
- /// children.
- final ReorderCallback onReorder;
- final NoReorderCallback? onNoReorder;
- /// Called when the draggable starts being dragged.
- final ReorderStartedCallback? onReorderStarted;
- final BuildItemsContainer? buildItemsContainer;
- final BuildDraggableFeedback? buildDraggableFeedback;
- final MainAxisAlignment mainAxisAlignment;
- final bool needsLongPressDraggable;
- final double draggingWidgetOpacity;
- final Duration? reorderAnimationDuration;
- final Duration? scrollAnimationDuration;
- final bool ignorePrimaryScrollController;
- @override
- _ReorderableFlexState createState() => _ReorderableFlexState();
- }
- // This top-level state manages an Overlay that contains the list and
- // also any Draggables it creates.
- //
- // _ReorderableListContent manages the list itself and reorder operations.
- //
- // The Overlay doesn't properly keep state by building new overlay entries,
- // and so we cache a single OverlayEntry for use as the list layer.
- // That overlay entry then builds a _ReorderableListContent which may
- // insert Draggables into the Overlay above itself.
- class _ReorderableFlexState extends State<ReorderableFlex> {
- // We use an inner overlay so that the dragging list item doesn't draw outside of the list itself.
- final GlobalKey _overlayKey =
- GlobalKey(debugLabel: '$ReorderableFlex overlay key');
- // This entry contains the scrolling list itself.
- late PassthroughOverlayEntry _listOverlayEntry;
- @override
- void initState() {
- super.initState();
- _listOverlayEntry = PassthroughOverlayEntry(
- opaque: false,
- builder: (BuildContext context) {
- return _ReorderableFlexContent(
- header: widget.header,
- footer: widget.footer,
- children: widget.children,
- direction: widget.direction,
- scrollDirection: widget.scrollDirection,
- onReorder: widget.onReorder,
- onNoReorder: widget.onNoReorder,
- onReorderStarted: widget.onReorderStarted,
- padding: widget.padding,
- buildItemsContainer: widget.buildItemsContainer,
- buildDraggableFeedback: widget.buildDraggableFeedback,
- mainAxisAlignment: widget.mainAxisAlignment,
- scrollController: widget.scrollController,
- needsLongPressDraggable: widget.needsLongPressDraggable,
- draggingWidgetOpacity: widget.draggingWidgetOpacity,
- draggedItemBuilder: widget.draggedItemBuilder,
- reorderAnimationDuration: widget.reorderAnimationDuration ??
- const Duration(milliseconds: 200),
- scrollAnimationDuration: widget.scrollAnimationDuration ??
- const Duration(milliseconds: 200),
- );
- },
- );
- }
- @override
- Widget build(BuildContext context) {
- final PassthroughOverlay passthroughOverlay = PassthroughOverlay(
- key: _overlayKey,
- initialEntries: <PassthroughOverlayEntry>[
- _listOverlayEntry,
- ]);
- return widget.ignorePrimaryScrollController
- ? PrimaryScrollController.none(child: passthroughOverlay)
- : passthroughOverlay;
- }
- }
- // This widget is responsible for the inside of the Overlay in the
- // ReorderableFlex.
- class _ReorderableFlexContent extends StatefulWidget {
- const _ReorderableFlexContent({
- this.header,
- this.footer,
- required this.children,
- required this.direction,
- required this.scrollDirection,
- required this.onReorder,
- required this.onNoReorder,
- required this.onReorderStarted,
- required this.mainAxisAlignment,
- required this.scrollController,
- required this.needsLongPressDraggable,
- required this.draggingWidgetOpacity,
- required this.buildItemsContainer,
- required this.buildDraggableFeedback,
- required this.padding,
- this.draggedItemBuilder,
- this.reorderAnimationDuration = const Duration(milliseconds: 200),
- this.scrollAnimationDuration = const Duration(milliseconds: 200),
- });
- final Widget? header;
- final Widget? footer;
- final List<Widget> children;
- final Axis direction;
- final Axis scrollDirection;
- final ReorderCallback onReorder;
- final NoReorderCallback? onNoReorder;
- final ReorderStartedCallback? onReorderStarted;
- final BuildItemsContainer? buildItemsContainer;
- final BuildDraggableFeedback? buildDraggableFeedback;
- final ScrollController? scrollController;
- final EdgeInsets? padding;
- final Widget Function(BuildContext context, int index)? draggedItemBuilder;
- final MainAxisAlignment mainAxisAlignment;
- final bool needsLongPressDraggable;
- final double draggingWidgetOpacity;
- final Duration reorderAnimationDuration;
- final Duration scrollAnimationDuration;
- @override
- _ReorderableFlexContentState createState() => _ReorderableFlexContentState();
- }
- class _ReorderableFlexContentState extends State<_ReorderableFlexContent>
- with TickerProviderStateMixin<_ReorderableFlexContent>, ReorderableMixin {
- // The extent along the [widget.scrollDirection] axis to allow a child to
- // drop into when the user reorders list children.
- //
- // This value is used when the extents haven't yet been calculated from
- // the currently dragging widget, such as when it first builds.
- // static const double _defaultDropAreaExtent = 1.0;
- // The additional margin to place around a computed drop area.
- static const double _dropAreaMargin = 0.0;
- // How long an animation to reorder an element in the list takes.
- late Duration _reorderAnimationDuration;
- // How long an animation to scroll to an off-screen element in the
- // list takes.
- late Duration _scrollAnimationDuration;
- // Controls scrolls and measures scroll progress.
- late ScrollController _scrollController;
- ScrollPosition? _attachedScrollPosition;
- // This controls the entrance of the dragging widget into a new place.
- late AnimationController _entranceController;
- // This controls the 'ghost' of the dragging widget, which is left behind
- // where the widget used to be.
- late AnimationController _ghostController;
- // The member of widget.children currently being dragged.
- //
- // Null if no drag is underway.
- Widget? _draggingWidget;
- // The last computed size of the feedback widget being dragged.
- Size? _draggingFeedbackSize = Size(0, 0);
- // The location that the dragging widget occupied before it started to drag.
- int _dragStartIndex = -1;
- // The index that the dragging widget most recently left.
- // This is used to show an animation of the widget's position.
- int _ghostIndex = -1;
- // The index that the dragging widget currently occupies.
- int _currentIndex = -1;
- // The widget to move the dragging widget too after the current index.
- int _nextIndex = 0;
- // Whether or not we are currently scrolling this view to show a widget.
- bool _scrolling = false;
- // final GlobalKey _contentKey = GlobalKey(debugLabel: '$ReorderableFlex content key');
- Size get _dropAreaSize {
- if (_draggingFeedbackSize == null) {
- return Size(0, 0);
- }
- return _draggingFeedbackSize! + Offset(_dropAreaMargin, _dropAreaMargin);
- // double dropAreaWithoutMargin;
- // switch (widget.direction) {
- // case Axis.horizontal:
- // dropAreaWithoutMargin = _draggingFeedbackSize.width;
- // break;
- // case Axis.vertical:
- // default:
- // dropAreaWithoutMargin = _draggingFeedbackSize.height;
- // break;
- // }
- // return dropAreaWithoutMargin + _dropAreaMargin;
- }
- @override
- void initState() {
- super.initState();
- _reorderAnimationDuration = widget.reorderAnimationDuration;
- _scrollAnimationDuration = widget.scrollAnimationDuration;
- _entranceController = AnimationController(
- value: 1.0, vsync: this, duration: _reorderAnimationDuration);
- _ghostController = AnimationController(
- value: 0, vsync: this, duration: _reorderAnimationDuration);
- _entranceController.addStatusListener(_onEntranceStatusChanged);
- }
- @override
- void didChangeDependencies() {
- if (_attachedScrollPosition != null) {
- _scrollController.detach(_attachedScrollPosition!);
- _attachedScrollPosition = null;
- }
- _scrollController = widget.scrollController ??
- PrimaryScrollController.maybeOf(context) ??
- ScrollController();
- if (_scrollController.hasClients) {
- _attachedScrollPosition = Scrollable.maybeOf(context)?.position;
- } else {
- _attachedScrollPosition = null;
- }
- if (_attachedScrollPosition != null) {
- _scrollController.attach(_attachedScrollPosition!);
- }
- super.didChangeDependencies();
- }
- @override
- void dispose() {
- if (_attachedScrollPosition != null) {
- _scrollController.detach(_attachedScrollPosition!);
- _attachedScrollPosition = null;
- }
- _entranceController.dispose();
- _ghostController.dispose();
- super.dispose();
- }
- // Animates the droppable space from _currentIndex to _nextIndex.
- void _requestAnimationToNextIndex({bool isAcceptingNewTarget = false}) {
- // debugPrint('${DateTime.now().toString().substring(5, 22)} reorderable_flex.dart(285) $this._requestAnimationToNextIndex: '
- // '_dragStartIndex:$_dragStartIndex _ghostIndex:$_ghostIndex _currentIndex:$_currentIndex _nextIndex:$_nextIndex isAcceptingNewTarget:$isAcceptingNewTarget isCompleted:${_entranceController.isCompleted}');
- if (_entranceController.isCompleted) {
- _ghostIndex = _currentIndex;
- if (!isAcceptingNewTarget && _nextIndex == _currentIndex) {
- // && _dragStartIndex == _ghostIndex
- return;
- }
- _currentIndex = _nextIndex;
- _ghostController.reverse(from: 1.0);
- _entranceController.forward(from: 0.0);
- }
- }
- // Requests animation to the latest next index if it changes during an animation.
- void _onEntranceStatusChanged(AnimationStatus status) {
- if (status == AnimationStatus.completed) {
- setState(() {
- _requestAnimationToNextIndex();
- });
- }
- }
- // Scrolls to a target context if that context is not on the screen.
- void _scrollTo(BuildContext context) {
- if (_scrolling) return;
- final RenderObject contextObject = context.findRenderObject()!;
- final RenderAbstractViewport viewport =
- RenderAbstractViewport.of(contextObject);
- // If and only if the current scroll offset falls in-between the offsets
- // necessary to reveal the selected context at the top or bottom of the
- // screen, then it is already on-screen.
- final double margin = widget.direction == Axis.horizontal
- ? _dropAreaSize.width
- : _dropAreaSize.height;
- if (_scrollController.hasClients) {
- final double scrollOffset = _scrollController.offset;
- final double topOffset = max(
- _scrollController.position.minScrollExtent,
- viewport.getOffsetToReveal(contextObject, 0.0).offset - margin,
- );
- final double bottomOffset = min(
- _scrollController.position.maxScrollExtent,
- viewport.getOffsetToReveal(contextObject, 1.0).offset + margin,
- );
- final bool onScreen =
- scrollOffset <= topOffset && scrollOffset >= bottomOffset;
- // If the context is off screen, then we request a scroll to make it visible.
- if (!onScreen) {
- _scrolling = true;
- _scrollController.position
- .animateTo(
- scrollOffset < bottomOffset ? bottomOffset : topOffset,
- duration: _scrollAnimationDuration,
- curve: Curves.easeInOut,
- )
- .then((void value) {
- setState(() {
- _scrolling = false;
- });
- });
- }
- }
- }
- // Wraps children in Row or Column, so that the children flow in
- // the widget's scrollDirection.
- Widget _buildContainerForMainAxis({required List<Widget> children}) {
- switch (widget.direction) {
- case Axis.horizontal:
- return Row(
- mainAxisSize: MainAxisSize.min,
- children: children,
- mainAxisAlignment: widget.mainAxisAlignment);
- case Axis.vertical:
- default:
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: children,
- mainAxisAlignment: widget.mainAxisAlignment);
- }
- }
- // Wraps one of the widget's children in a DragTarget and Draggable.
- // Handles up the logic for dragging and reordering items in the list.
- Widget _wrap(Widget toWrap, int index) {
- assert(toWrap.key != null);
- final GlobalObjectKey keyIndexGlobalKey = GlobalObjectKey(toWrap.key!);
- // We pass the toWrapWithGlobalKey into the Draggable so that when a list
- // item gets dragged, the accessibility framework can preserve the selected
- // state of the dragging item.
- final draggedItem =
- widget.draggedItemBuilder?.call(context, index) ?? toWrap;
- // Starts dragging toWrap.
- void onDragStarted() {
- setState(() {
- _draggingWidget = draggedItem;
- _dragStartIndex = index;
- _ghostIndex = index;
- _currentIndex = index;
- _entranceController.value = 1.0;
- _draggingFeedbackSize = keyIndexGlobalKey.currentContext?.size;
- });
- widget.onReorderStarted?.call(index);
- }
- // Places the value from startIndex one space before the element at endIndex.
- void _reorder(int startIndex, int endIndex) {
- // debugPrint('startIndex:$startIndex endIndex:$endIndex');
- if (startIndex != endIndex)
- widget.onReorder(startIndex, endIndex);
- else if (widget.onNoReorder != null) widget.onNoReorder!(startIndex);
- // Animates leftover space in the drop area closed.
- // TODO(djshuckerow): bring the animation in line with the Material
- // specifications.
- _ghostController.reverse(from: 0.1);
- _entranceController.reverse(from: 0);
- }
- void reorder(int startIndex, int endIndex) {
- // debugPrint('startIndex:$startIndex endIndex:$endIndex');
- setState(() {
- _reorder(startIndex, endIndex);
- });
- }
- // Drops toWrap into the last position it was hovering over.
- void onDragEnded() {
- // reorder(_dragStartIndex, _currentIndex);
- setState(() {
- _reorder(_dragStartIndex, _currentIndex);
- _dragStartIndex = -1;
- _ghostIndex = -1;
- _currentIndex = -1;
- _draggingWidget = null;
- });
- }
- Widget wrapWithSemantics() {
- // 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.children.length - 1);
- 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 (index > 0) {
- semanticsActions[CustomSemanticsAction(
- label: localizations.reorderItemToStart)] = moveToStart;
- String reorderItemBefore = localizations.reorderItemUp;
- if (widget.direction == 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.children.length - 1) {
- String reorderItemAfter = localizations.reorderItemDown;
- if (widget.direction == 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 Draggable so that when a list
- // item 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: toWrap,
- ),
- );
- // return KeyedSubtree(
- // key: keyIndexGlobalKey,
- // child: MergeSemantics(
- // child: Semantics(
- // customSemanticsActions: semanticsActions,
- // child: toWrap,
- // ),
- // ),
- // );
- }
- Widget _makeAppearingWidget(Widget child) {
- return makeAppearingWidget(
- child,
- _entranceController,
- _draggingFeedbackSize,
- widget.direction,
- );
- }
- Widget _makeDisappearingWidget(Widget child) {
- return makeDisappearingWidget(
- child,
- _ghostController,
- _draggingFeedbackSize,
- widget.direction,
- );
- }
- Widget buildDragTarget(BuildContext context, List<int?> acceptedCandidates,
- List<dynamic> rejectedCandidates) {
- final Widget toWrapWithSemantics = wrapWithSemantics();
- Widget feedbackBuilder = Builder(builder: (BuildContext context) {
- // RenderRepaintBoundary renderObject = _contentKey.currentContext.findRenderObject();
- // BoxConstraints contentSizeConstraints = BoxConstraints.loose(renderObject.size);
- BoxConstraints contentSizeConstraints = BoxConstraints.loose(
- _draggingFeedbackSize!); //renderObject.constraints
- // debugPrint('${DateTime.now().toString().substring(5, 22)} reorderable_flex.dart(515) $this.buildDragTarget: contentConstraints:$contentSizeConstraints _draggingFeedbackSize:$_draggingFeedbackSize');
- return (widget.buildDraggableFeedback ?? defaultBuildDraggableFeedback)(
- context, contentSizeConstraints, draggedItem);
- });
- // We build the draggable inside of a layout builder so that we can
- // constrain the size of the feedback dragging widget.
- bool isReorderable = true;
- if (toWrap is ReorderableWidget) {
- isReorderable = toWrap.reorderable;
- }
- Widget child;
- if (!isReorderable) {
- child = toWrap;
- } else {
- child = widget.needsLongPressDraggable
- ? LongPressDraggable<int>(
- maxSimultaneousDrags: 1,
- axis: widget.direction,
- data: index,
- ignoringFeedbackSemantics: false,
- // feedback: Container(
- // alignment: Alignment.topLeft,
- // // These constraints will limit the cross axis of the drawn widget.
- // constraints: constraints,
- // child: Material(
- // elevation: 6.0,
- // child: IntrinsicWidth(child: toWrapWithSemantics),
- // ),
- // ),
- feedback: feedbackBuilder,
- // feedback: Transform(
- // transform: new Matrix4.rotationZ(0),
- // alignment: FractionalOffset.topLeft,
- // child: Material(
- // child: Card(child: ConstrainedBox(constraints: BoxConstraints.tightFor(width: 100), child: toWrapWithSemantics)),
- // elevation: 6.0,
- // color: Colors.transparent,
- // borderRadius: BorderRadius.zero,
- // ),
- // ),
- // Wrap toWrapWithSemantics with a widget that supports HitTestBehavior
- // to make sure the whole toWrapWithSemantics responds to pointer events, i.e. dragging
- child: MetaData(
- child: toWrapWithSemantics,
- behavior: HitTestBehavior.opaque),
- //toWrapWithSemantics,//_dragging == toWrap.key ? const SizedBox() : toWrapWithSemantics,
- childWhenDragging: IgnorePointer(
- ignoring: true,
- child: Opacity(
- opacity: 0,
- child: Container(width: 0, height: 0, child: toWrap))),
- onDragStarted: onDragStarted,
- dragAnchorStrategy: childDragAnchorStrategy,
- // When the drag ends inside a DragTarget widget, the drag
- // succeeds, and we reorder the widget into position appropriately.
- onDragCompleted: onDragEnded,
- // When the drag does not end inside a DragTarget widget, the
- // drag fails, but we still reorder the widget to the last position it
- // had been dragged to.
- onDraggableCanceled: (Velocity velocity, Offset offset) =>
- onDragEnded(),
- )
- : Draggable<int>(
- maxSimultaneousDrags: 1,
- axis: widget.direction,
- data: index,
- ignoringFeedbackSemantics: false,
- feedback: feedbackBuilder,
- // Wrap toWrapWithSemantics with a widget that supports HitTestBehavior
- // to make sure the whole toWrapWithSemantics responds to pointer events, i.e. dragging
- child: MetaData(
- child: toWrapWithSemantics,
- behavior: HitTestBehavior.opaque),
- childWhenDragging: IgnorePointer(
- ignoring: true,
- child: Opacity(
- opacity: 0,
- child: Container(width: 0, height: 0, child: toWrap))),
- onDragStarted: onDragStarted,
- dragAnchorStrategy: childDragAnchorStrategy,
- // When the drag ends inside a DragTarget widget, the drag
- // succeeds, and we reorder the widget into position appropriately.
- onDragCompleted: onDragEnded,
- // When the drag does not end inside a DragTarget widget, the
- // drag fails, but we still reorder the widget to the last position it
- // had been dragged to.
- onDraggableCanceled: (Velocity velocity, Offset offset) =>
- onDragEnded(),
- );
- }
- // The target for dropping at the end of the list doesn't need to be
- // draggable.
- if (index >= widget.children.length) {
- child = toWrap;
- }
- // // Determine the size of the drop area to show under the dragging widget.
- // Widget spacing;
- // switch (widget.direction) {
- // case Axis.horizontal:
- // spacing = SizedBox(width: _dropAreaExtent);
- // break;
- // case Axis.vertical:
- // default:
- // spacing = SizedBox(height: _dropAreaExtent);
- // break;
- // }
- // // We open up a space under where the dragging widget currently is to
- // // show it can be dropped.
- // if (_currentIndex == index) {
- // Widget entranceSpacing = SizeTransition(
- // sizeFactor: _entranceController,
- // axis: widget.direction,
- // child: Row(children: [spacing, Text('eeeeeeeeeeeee $index')])
- // );
- //
- // return _buildContainerForScrollDirection(children: _currentIndex > _ghostIndex ? [child, entranceSpacing] : [entranceSpacing, child]);
- // }
- // // We close up the space under where the dragging widget previously was
- // // with the ghostController animation.
- // if (_ghostIndex == index) {
- // Widget ghostSpacing = SizeTransition(
- // sizeFactor: _ghostController,
- // axis: widget.direction,
- // child: Row(children: [spacing, Text('gggggggg $index')]),
- // );
- // return _buildContainerForScrollDirection(children: _currentIndex > _ghostIndex ? [child, ghostSpacing] : [ghostSpacing, child]);
- // }
- return child;
- }
- // We wrap the drag target in a Builder so that we can scroll to its specific context.
- return Builder(builder: (BuildContext context) {
- Widget dragTarget = DragTarget<int>(
- builder: buildDragTarget,
- onWillAccept: (int? toAccept) {
- bool willAccept = _dragStartIndex == toAccept && toAccept != index;
- // debugPrint('${DateTime.now().toString().substring(5, 22)} reorderable_flex.dart(609) $this._wrap: '
- // 'onWillAccept: toAccept:$toAccept return:$willAccept _nextIndex:$_nextIndex index:$index _currentIndex:$_currentIndex _dragStartIndex:$_dragStartIndex');
- setState(() {
- if (willAccept) {
- int shiftedIndex = index;
- if (index == _dragStartIndex) {
- shiftedIndex = _ghostIndex;
- } else if (index > _dragStartIndex && index <= _ghostIndex) {
- shiftedIndex--;
- } else if (index < _dragStartIndex && index >= _ghostIndex) {
- shiftedIndex++;
- }
- _nextIndex = shiftedIndex;
- } else {
- _nextIndex = index;
- }
- _requestAnimationToNextIndex(isAcceptingNewTarget: true);
- });
- _scrollTo(context);
- // If the target is not the original starting point, then we will accept the drop.
- return willAccept; //_dragging == toAccept && toAccept != toWrap.key;
- },
- onAccept: (int accepted) {},
- onLeave: (Object? leaving) {},
- );
- dragTarget = KeyedSubtree(key: keyIndexGlobalKey, child: dragTarget);
- // Determine the size of the drop area to show under the dragging widget.
- Widget spacing = _draggingWidget == null
- ? SizedBox.fromSize(size: _dropAreaSize)
- : Opacity(
- opacity: widget.draggingWidgetOpacity, child: _draggingWidget);
- // Widget spacing = SizedBox.fromSize(
- // size: _dropAreaSize,
- // child: _draggingWidget != null ? Opacity(opacity: 0.2, child: _draggingWidget) : null,
- // );
- // We open up a space under where the dragging widget currently is to
- // show it can be dropped.
- int shiftedIndex = index;
- if (_currentIndex != _ghostIndex) {
- if (index == _dragStartIndex) {
- shiftedIndex = _ghostIndex;
- } else if (index > _dragStartIndex && index <= _ghostIndex) {
- shiftedIndex--;
- } else if (index < _dragStartIndex && index >= _ghostIndex) {
- shiftedIndex++;
- }
- }
- // debugPrint('${DateTime.now().toString().substring(5, 22)} reorderable_flex.dart(659) $this._wrap: '
- // 'index:$index shiftedIndex:$shiftedIndex _nextIndex:$_nextIndex _currentIndex:$_currentIndex _ghostIndex:$_ghostIndex _dragStartIndex:$_dragStartIndex');
- if (shiftedIndex == _currentIndex || index == _ghostIndex) {
- Widget entranceSpacing = _makeAppearingWidget(spacing);
- Widget ghostSpacing = _makeDisappearingWidget(spacing);
- if (_dragStartIndex == -1) {
- return _buildContainerForMainAxis(children: [dragTarget]);
- } else if (_currentIndex > _ghostIndex) {
- //the ghost is moving down, i.e. the tile below the ghost is moving up
- // debugPrint('index:$index item moving up / ghost moving down');
- if (shiftedIndex == _currentIndex && index == _ghostIndex) {
- return _buildContainerForMainAxis(
- children: [ghostSpacing, dragTarget, entranceSpacing]);
- } else if (shiftedIndex == _currentIndex) {
- return _buildContainerForMainAxis(
- children: [dragTarget, entranceSpacing]);
- } else if (index == _ghostIndex) {
- return _buildContainerForMainAxis(
- children: shiftedIndex <= index
- ? [dragTarget, ghostSpacing]
- : [ghostSpacing, dragTarget]);
- }
- } else if (_currentIndex < _ghostIndex) {
- //the ghost is moving up, i.e. the tile above the ghost is moving down
- // debugPrint('index:$index item moving down / ghost moving up');
- if (shiftedIndex == _currentIndex && index == _ghostIndex) {
- return _buildContainerForMainAxis(
- children: [entranceSpacing, dragTarget, ghostSpacing]);
- } else if (shiftedIndex == _currentIndex) {
- return _buildContainerForMainAxis(
- children: [entranceSpacing, dragTarget]);
- } else if (index == _ghostIndex) {
- return _buildContainerForMainAxis(
- children: shiftedIndex >= index
- ? [ghostSpacing, dragTarget]
- : [dragTarget, ghostSpacing]);
- }
- } else {
- // debugPrint('index:$index using _entranceController: spacing on top:${!(_dragStartIndex < _currentIndex)}');
- return _buildContainerForMainAxis(
- children: _dragStartIndex < _currentIndex
- ? [dragTarget, entranceSpacing]
- : [entranceSpacing, dragTarget]);
- }
- }
- //we still wrap dragTarget with a container so that widget's depths are the same and it prevent's layout alignment issue
- return _buildContainerForMainAxis(children: [dragTarget]);
- });
- }
- @override
- Widget build(BuildContext context) {
- // assert(debugCheckHasMaterialLocalizations(context));
- // We use the layout builder to constrain the cross-axis size of dragging child widgets.
- // return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
- final List<Widget> wrappedChildren = <Widget>[];
- if (widget.header != null) {
- wrappedChildren.add(widget.header!);
- }
- for (int i = 0; i < widget.children.length; i += 1) {
- wrappedChildren.add(_wrap(widget.children[i], i));
- }
- if (widget.footer != null) {
- wrappedChildren.add(widget.footer!);
- }
- // const Key endWidgetKey = Key('DraggableList - End Widget');
- // Widget finalDropArea;
- // switch (widget.direction) {
- // case Axis.horizontal:
- // finalDropArea = SizedBox(
- // key: endWidgetKey,
- // width: _defaultDropAreaExtent,
- // height: double.infinity,//constraints.maxHeight,
- // );
- // break;
- // case Axis.vertical:
- // default:
- // finalDropArea = SizedBox(
- // key: endWidgetKey,
- // height: _defaultDropAreaExtent,
- // width: double.infinity,//constraints.maxWidth,
- // );
- // break;
- // }
- // wrappedChildren.add(_wrap(
- // finalDropArea,
- // widget.children.length,
- // constraints),
- // );
- // return _buildContainerForScrollDirection(children: wrappedChildren);
- // return SingleChildScrollView(
- // scrollDirection: widget.direction,
- // child: _buildContainerForScrollDirection(children: wrappedChildren),
- // padding: widget.padding,
- // controller: _scrollController,
- // );
- if (widget.scrollController != null &&
- PrimaryScrollController.maybeOf(context) == null) {
- return (widget.buildItemsContainer ?? defaultBuildItemsContainer)(
- context, widget.direction, wrappedChildren);
- } else {
- return SingleChildScrollView(
- // key: _contentKey,
- scrollDirection: widget.scrollDirection,
- child: (widget.buildItemsContainer ?? defaultBuildItemsContainer)(
- context, widget.direction, wrappedChildren),
- padding: widget.padding,
- controller: _scrollController,
- );
- }
- // });
- }
- // @override
- // void afterFirstLayout(BuildContext context) {
- // // Calling the same function "after layout" to resolve the issue.
- //// showHelloWorld();
- // debugPrint('size:' + context.size.toString());
- // debugPrint('constraints:' + context.findRenderObject().constraints.toString());
- // context.visitChildElements((Element element) {
- // debugPrint('element $element size:' + element.size.toString());
- // debugPrint('element $element constraints:' + element.findRenderObject().constraints.toString());
- // });
- //// if (widget.header != null) {
- //// Row headerRow = widget.header;
- //// headerRow.
- //// }
- // }
- Widget defaultBuildItemsContainer(
- BuildContext context, Axis direction, List<Widget> children) {
- switch (direction) {
- case Axis.horizontal:
- return Row(children: children);
- case Axis.vertical:
- default:
- return Column(children: children);
- }
- }
- Widget defaultBuildDraggableFeedback(
- BuildContext context, BoxConstraints constraints, Widget child) {
- return Transform(
- transform: Matrix4.rotationZ(0),
- alignment: FractionalOffset.topLeft,
- child: Material(
- child:
- Card(child: ConstrainedBox(constraints: constraints, child: child)),
- elevation: 6.0,
- color: Colors.transparent,
- borderRadius: BorderRadius.zero,
- ),
- );
- }
- }
- /// Reorderable (drag and drop) version of [Row], a widget that displays its
- /// draggable children in horizontally.
- ///
- /// In addition to other parameters in [Row]'s constructor, this widget also
- /// has [header] and [footer] for placing non-reorderable widgets at the top and
- /// bottom of the widget, and [buildDraggableFeedback] to customize the
- /// [feedback] widget of the internal [LongPressDraggable].
- ///
- /// The [onReorder] function must be defined. A typical onReorder function looks
- /// like the following:
- ///
- /// ``` dart
- /// void _onReorder(int oldIndex, int newIndex) {
- /// setState(() {
- /// Widget row = _rows.removeAt(oldIndex);
- /// _rows.insert(newIndex, row);
- /// });
- /// }
- /// ```
- ///
- /// All [children] must have a key.
- ///
- /// See also:
- ///
- /// * [ReorderableColumn], for a version of this widget that is always vertical.
- class ReorderableRow extends ReorderableFlex {
- ReorderableRow({
- required ReorderCallback onReorder,
- Key? key,
- Widget? header,
- Widget? footer,
- EdgeInsets? padding,
- MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
- MainAxisSize mainAxisSize = MainAxisSize.max,
- CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
- TextDirection? textDirection,
- VerticalDirection verticalDirection = VerticalDirection.down,
- TextBaseline? textBaseline,
- List<Widget> children = const <Widget>[],
- BuildDraggableFeedback? buildDraggableFeedback,
- NoReorderCallback? onNoReorder,
- ReorderStartedCallback? onReorderStarted,
- ScrollController? scrollController,
- bool needsLongPressDraggable = true,
- double draggingWidgetOpacity = 0.2,
- Duration? reorderAnimationDuration,
- Duration? scrollAnimationDuration,
- Widget Function(BuildContext context, int index)? draggedItemBuilder,
- bool ignorePrimaryScrollController = false,
- }) : super(
- key: key,
- header: header,
- footer: footer,
- children: children,
- onReorder: onReorder,
- onNoReorder: onNoReorder,
- onReorderStarted: onReorderStarted,
- direction: Axis.horizontal,
- scrollDirection: Axis.horizontal,
- padding: padding,
- draggedItemBuilder: draggedItemBuilder,
- buildItemsContainer:
- (BuildContext context, Axis direction, List<Widget> children) {
- return Flex(
- direction: direction,
- mainAxisAlignment: mainAxisAlignment,
- mainAxisSize: mainAxisSize,
- crossAxisAlignment: crossAxisAlignment,
- textDirection: textDirection,
- verticalDirection: verticalDirection,
- textBaseline: textBaseline,
- children: children);
- },
- buildDraggableFeedback: buildDraggableFeedback,
- mainAxisAlignment: mainAxisAlignment,
- scrollController: scrollController,
- needsLongPressDraggable: needsLongPressDraggable,
- draggingWidgetOpacity: draggingWidgetOpacity,
- reorderAnimationDuration: reorderAnimationDuration,
- scrollAnimationDuration: scrollAnimationDuration,
- ignorePrimaryScrollController: ignorePrimaryScrollController);
- }
- /// Reorderable (drag and drop) version of [Column], a widget that displays its
- /// draggable children in horizontally.
- ///
- /// In addition to other parameters in [Column]'s constructor, this widget also
- /// has [header] and [footer] for placing non-reorderable widgets at the left and
- /// right of the widget, and [buildDraggableFeedback] to customize the
- /// [feedback] widget of the internal [LongPressDraggable].
- ///
- /// The [onReorder] function must be defined. A typical onReorder function looks
- /// like the following:
- ///
- /// ``` dart
- /// void _onReorder(int oldIndex, int newIndex) {
- /// setState(() {
- /// Widget row = _rows.removeAt(oldIndex);
- /// _rows.insert(newIndex, row);
- /// });
- /// }
- /// ```
- ///
- /// All [children] must have a key.
- ///
- /// See also:
- ///
- /// * [ReorderableRow], for a version of this widget that is always horizontal.
- class ReorderableColumn extends ReorderableFlex {
- ReorderableColumn({
- required ReorderCallback onReorder,
- Key? key,
- Widget? header,
- Widget? footer,
- EdgeInsets? padding,
- MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
- MainAxisSize mainAxisSize = MainAxisSize.max,
- CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
- TextDirection? textDirection,
- VerticalDirection verticalDirection = VerticalDirection.down,
- TextBaseline? textBaseline,
- List<Widget> children = const <Widget>[],
- BuildDraggableFeedback? buildDraggableFeedback,
- NoReorderCallback? onNoReorder,
- ReorderStartedCallback? onReorderStarted,
- ScrollController? scrollController,
- bool needsLongPressDraggable = true,
- double draggingWidgetOpacity = 0.2,
- Duration? reorderAnimationDuration,
- Duration? scrollAnimationDuration,
- Widget Function(BuildContext context, int index)? draggedItemBuilder,
- bool ignorePrimaryScrollController = false,
- }) : super(
- key: key,
- header: header,
- footer: footer,
- children: children,
- onReorder: onReorder,
- onNoReorder: onNoReorder,
- onReorderStarted: onReorderStarted,
- direction: Axis.vertical,
- padding: padding,
- buildItemsContainer:
- (BuildContext context, Axis direction, List<Widget> children) {
- return Flex(
- direction: direction,
- mainAxisAlignment: mainAxisAlignment,
- mainAxisSize: mainAxisSize,
- crossAxisAlignment: crossAxisAlignment,
- textDirection: textDirection,
- verticalDirection: verticalDirection,
- textBaseline: textBaseline,
- children: children);
- },
- buildDraggableFeedback: buildDraggableFeedback,
- mainAxisAlignment: mainAxisAlignment,
- scrollController: scrollController,
- needsLongPressDraggable: needsLongPressDraggable,
- draggingWidgetOpacity: draggingWidgetOpacity,
- reorderAnimationDuration: reorderAnimationDuration,
- scrollAnimationDuration: scrollAnimationDuration,
- draggedItemBuilder: draggedItemBuilder,
- ignorePrimaryScrollController: ignorePrimaryScrollController);
- }
|