ReorderableListView(
children: _createPreviewListContainer(),
onReorder: (int oldIndex, int newIndex) {
print("$oldIndex --- $newIndex");
_onSwapDsmViewPosition(oldIndex, newIndex);
},
)
void _onSwapDsmViewPosition(int oldIndex, int newIndex) {
///使用冒泡排一下序
quickSort(List list) {
Widget _tempItem = list[oldIndex];
list[oldIndex] = list[newIndex];
list[newIndex] = _tempItem;
}
quickSort(_rightDsmWidget);
....
....
}
上面是ReorderableListView基本使用方式. _createPreviewListContainer()
方法内要创建一个Widget集合,并且每个Widget都需要有一个Key. 我们来看下源码
class ReorderableListView extends StatefulWidget {
/// Creates a reorderable list.
ReorderableListView({
Key key,
this.header,
@required this.children,
@required this.onReorder,
this.scrollController,
this.scrollDirection = Axis.vertical,
this.padding,
this.reverse = false,
}) : assert(scrollDirection != null),
assert(onReorder != null),
assert(children != null),
assert(
children.every((Widget w) => w.key != null), ///会检查是否每个child都带有key!
'All children of this widget must have a key.',
),
super(key: key);
是继承自StatefulWidget的 State 为_ReorderableListViewState
class _ReorderableListViewState extends State<ReorderableListView> {
// We use an inner overlay so that the dragging list item doesn't draw outside of the list itself.
final GlobalKey _overlayKey = GlobalKey(debugLabel: '$ReorderableListView overlay key');
// This entry contains the scrolling list itself.
OverlayEntry _listOverlayEntry;
@override
void initState() {
super.initState();
_listOverlayEntry = OverlayEntry(
opaque: true,
builder: (BuildContext context) {
return _ReorderableListContent( ///这里用到另一个组件_ReorderableListContent
header: widget.header,
children: widget.children,
scrollController: widget.scrollController,
scrollDirection: widget.scrollDirection,
onReorder: widget.onReorder,
padding: widget.padding,
reverse: widget.reverse,
);
},
);
}
@override
Widget build(BuildContext context) {
return Overlay( ///返回的一个overlay,可以用于悬浮
key: _overlayKey,
initialEntries: <OverlayEntry>[
_listOverlayEntry,
]);
}
}
@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) {
const Key endWidgetKey = Key('DraggableList - End Widget');
Widget finalDropArea;
switch (widget.scrollDirection) {
case Axis.horizontal:
finalDropArea = SizedBox(
key: endWidgetKey,
width: _defaultDropAreaExtent,
height: constraints.maxHeight,
);
break;
case Axis.vertical:
default:
finalDropArea = SizedBox(
key: endWidgetKey,
height: _defaultDropAreaExtent,
width: constraints.maxWidth,
);
break;
}
// If the reorderable list only has one child element, reordering
// should not be allowed.
final bool hasMoreThanOneChildElement = widget.children.length > 1;
return SingleChildScrollView(
scrollDirection: widget.scrollDirection,
padding: widget.padding,
controller: _scrollController,
reverse: widget.reverse,
child: _buildContainerForScrollDirection(
children: <Widget>[
//如果reverse为true 翻转数据后,视图左边会产生Padding间距!
if (widget.reverse && hasMoreThanOneChildElement) _wrap(finalDropArea, widget.children.length, constraints),
if (widget.header != null) widget.header,
for (int i = 0; i < widget.children.length; i += 1) _wrap(widget.children[i], i, constraints), ///每个item都用_wrap包裹起来
// TODO 右边的Padding是否要预留
if (!widget.reverse && hasMoreThanOneChildElement) _wrap(finalDropArea, widget.children.length, constraints),
],
),
);
});
}
我们看下_warp 方法
Widget _wrap(Widget toWrap, int index, BoxConstraints constraints) {
assert(toWrap.key != null);
final _ReorderableListViewChildGlobalKey keyIndexGlobalKey = _ReorderableListViewChildGlobalKey(toWrap.key, this);
// 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.
// Starts dragging toWrap.
void onDragStarted() {
setState(() {
_dragging = toWrap.key;
_dragStartIndex = index;
_ghostIndex = index;
_currentIndex = index;
_entranceController.value = 1.0;
_draggingFeedbackSize = keyIndexGlobalKey.currentContext.size;
});
}
// Places the value from startIndex one space before the element at endIndex.
void reorder(int startIndex, int endIndex) {
setState(() {
if (startIndex != endIndex) widget.onReorder(startIndex, endIndex);
// Animates leftover space in the drop area closed.
_ghostController.reverse(from: 0.1);
_entranceController.reverse(from: 0.1);
_dragging = null;
});
}
// Drops toWrap into the last position it was hovering over.
void onDragEnded() {
reorder(_dragStartIndex, _currentIndex);
}
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);
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.children.length - 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 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 KeyedSubtree(
key: keyIndexGlobalKey,
child: MergeSemantics(
child: Semantics(
customSemanticsActions: semanticsActions,
child: toWrap,
),
),
);
}
Widget buildDragTarget(BuildContext context, List<Key> acceptedCandidates, List<dynamic> rejectedCandidates) {
final Widget toWrapWithSemantics = wrapWithSemantics();
// We build the draggable inside of a layout builder so that we can
// constrain the size of the feedback dragging widget.
Widget child = LongPressDraggable<Key>(
maxSimultaneousDrags: 1,
axis: widget.scrollDirection,
data: toWrap.key,
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,
color: Colors.transparent,
child: toWrapWithSemantics,
),
),
child: _dragging == toWrap.key ? const SizedBox() : toWrapWithSemantics,
childWhenDragging: const SizedBox(),
dragAnchor: DragAnchor.child,
onDragStarted: onDragStarted,
// 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.scrollDirection) {
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) {
return _buildContainerForScrollDirection(children: <Widget>[
SizeTransition(
sizeFactor: _entranceController,
axis: widget.scrollDirection,
child: spacing,
),
child,
]);
}
// We close up the space under where the dragging widget previously was
// with the ghostController animation.
if (_ghostIndex == index) {
return _buildContainerForScrollDirection(children: <Widget>[
SizeTransition(
sizeFactor: _ghostController,
axis: widget.scrollDirection,
child: spacing,
),
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) {
return DragTarget<Key>( ///使用的Drag 组件
builder: buildDragTarget,
onWillAccept: (Key toAccept) {
setState(() {
_nextIndex = index;
_requestAnimationToNextIndex();
});
_scrollTo(context);
// If the target is not the original starting point, then we will accept the drop.
return _dragging == toAccept && toAccept != toWrap.key;
},
onAccept: (Key accepted) {},
onLeave: (Object leaving) {},
);
});
}
所以我们知道它也就是Overlay + SingleChildScrollView + Row | Colum + DragTarget 来实现的
在长按ReorderableListView的时候,会有一个白色的底, 只需要添加buildDragTarget方法里面的Material 属性color: Colors.transparent,即可!
网友评论