Yuncong
6 年前
当前提交
3f57f07d
共有 3 个文件被更改,包括 710 次插入 和 1 次删除
-
329Runtime/widgets/scroll_view.cs
-
379Runtime/material/reorderable_list.cs
-
3Runtime/material/reorderable_list.cs.meta
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.gestures; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.UIWidgets.material { |
|||
public delegate void ReorderCallback(int oldIndex, int newIndex); |
|||
|
|||
public class ReorderableListView : StatefulWidget { |
|||
ReorderableListView( |
|||
Widget header = null, |
|||
List<Widget> children = null, |
|||
ReorderCallback onReorder = null, |
|||
Axis scrollDirection = Axis.vertical, |
|||
EdgeInsets padding = null |
|||
) { |
|||
D.assert(onReorder != null); |
|||
D.assert(children != null); |
|||
D.assert( |
|||
children.All((Widget w) => w.key != null), |
|||
"All children of this widget must have a key." |
|||
); |
|||
this.header = header; |
|||
this.children = children; |
|||
this.scrollDirection = scrollDirection; |
|||
this.padding = padding; |
|||
this.onReorder = onReorder; |
|||
} |
|||
|
|||
public readonly Widget header; |
|||
|
|||
public readonly List<Widget> children; |
|||
|
|||
public readonly Axis scrollDirection; |
|||
|
|||
public readonly EdgeInsets padding; |
|||
|
|||
public readonly ReorderCallback onReorder; |
|||
|
|||
public override State createState() { |
|||
return new _ReorderableListViewState(); |
|||
} |
|||
} |
|||
|
|||
class _ReorderableListViewState : State<ReorderableListView> { |
|||
GlobalKey _overlayKey = GlobalKey.key(debugLabel: "$ReorderableListView overlay key"); |
|||
|
|||
OverlayEntry _listOverlayEntry; |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
this._listOverlayEntry = new OverlayEntry( |
|||
opaque: true, |
|||
builder: (BuildContext context) => { |
|||
return new _ReorderableListContent( |
|||
header: this.widget.header, |
|||
children: this.widget.children, |
|||
scrollDirection: this.widget.scrollDirection, |
|||
onReorder: this.widget.onReorder, |
|||
padding: this.widget.padding |
|||
); |
|||
} |
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new Overlay( |
|||
key: this._overlayKey, |
|||
initialEntries: new List<OverlayEntry> { |
|||
this._listOverlayEntry |
|||
}); |
|||
} |
|||
} |
|||
|
|||
class _ReorderableListContent : StatefulWidget { |
|||
public _ReorderableListContent( |
|||
Widget header, |
|||
List<Widget> children, |
|||
Axis scrollDirection, |
|||
EdgeInsets padding, |
|||
ReorderCallback onReorder |
|||
) { |
|||
this.header = header; |
|||
this.children = children; |
|||
this.scrollDirection = scrollDirection; |
|||
this.padding = padding; |
|||
this.onReorder = onReorder; |
|||
} |
|||
|
|||
public readonly Widget header; |
|||
public readonly List<Widget> children; |
|||
public readonly Axis scrollDirection; |
|||
public readonly EdgeInsets padding; |
|||
public readonly ReorderCallback onReorder; |
|||
|
|||
public override State createState() { |
|||
return new _ReorderableListContentState(); |
|||
} |
|||
} |
|||
|
|||
class _ReorderableListContentState : TickerProviderStateMixin<_ReorderableListContent> { |
|||
const float _defaultDropAreaExtent = 100.0f; |
|||
|
|||
const float _dropAreaMargin = 8.0f; |
|||
|
|||
readonly TimeSpan _reorderAnimationDuration = new TimeSpan(0, 0, 0, 0, 200); |
|||
|
|||
readonly TimeSpan _scrollAnimationDuration = new TimeSpan(0, 0, 0, 0, 200); |
|||
|
|||
ScrollController _scrollController; |
|||
|
|||
AnimationController _entranceController; |
|||
|
|||
AnimationController _ghostController; |
|||
|
|||
Key _dragging; |
|||
|
|||
Size _draggingFeedbackSize; |
|||
|
|||
int _dragStartIndex = 0; |
|||
|
|||
int _ghostIndex = 0; |
|||
|
|||
int _currentIndex = 0; |
|||
|
|||
int _nextIndex = 0; |
|||
|
|||
bool _scrolling = false; |
|||
|
|||
float _dropAreaExtent { |
|||
get { |
|||
if (this._draggingFeedbackSize == null) { |
|||
return _defaultDropAreaExtent; |
|||
} |
|||
|
|||
float dropAreaWithoutMargin; |
|||
switch (this.widget.scrollDirection) { |
|||
case Axis.horizontal: |
|||
dropAreaWithoutMargin = this._draggingFeedbackSize.width; |
|||
break; |
|||
case Axis.vertical: |
|||
default: |
|||
dropAreaWithoutMargin = this._draggingFeedbackSize.height; |
|||
break; |
|||
} |
|||
|
|||
return dropAreaWithoutMargin + _dropAreaMargin; |
|||
} |
|||
} |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
this._entranceController = new AnimationController(vsync: this, duration: this._reorderAnimationDuration); |
|||
this._ghostController = new AnimationController(vsync: this, duration: this._reorderAnimationDuration); |
|||
this._entranceController.addStatusListener(this._onEntranceStatusChanged); |
|||
} |
|||
|
|||
public override void didChangeDependencies() { |
|||
this._scrollController = PrimaryScrollController.of(this.context) ?? new ScrollController(); |
|||
base.didChangeDependencies(); |
|||
} |
|||
|
|||
public override void dispose() { |
|||
this._entranceController.dispose(); |
|||
this._ghostController.dispose(); |
|||
base.dispose(); |
|||
} |
|||
|
|||
void _requestAnimationToNextIndex() { |
|||
if (this._entranceController.isCompleted) { |
|||
this._ghostIndex = this._currentIndex; |
|||
if (this._nextIndex == this._currentIndex) { |
|||
return; |
|||
} |
|||
|
|||
this._currentIndex = this._nextIndex; |
|||
this._ghostController.reverse(from: 1.0f); |
|||
this._entranceController.forward(from: 0.0f); |
|||
} |
|||
} |
|||
|
|||
void _onEntranceStatusChanged(AnimationStatus status) { |
|||
if (status == AnimationStatus.completed) { |
|||
this.setState(() => { this._requestAnimationToNextIndex(); }); |
|||
} |
|||
} |
|||
|
|||
void _scrollTo(BuildContext context) { |
|||
if (this._scrolling) { |
|||
return; |
|||
} |
|||
|
|||
RenderObject contextObject = context.findRenderObject(); |
|||
RenderAbstractViewport viewport = RenderViewportUtils.of(contextObject); |
|||
D.assert(viewport != null); |
|||
float margin = this._dropAreaExtent; |
|||
float scrollOffset = this._scrollController.offset; |
|||
float topOffset = Mathf.Max(this._scrollController.position.minScrollExtent, |
|||
viewport.getOffsetToReveal(contextObject, 0.0f).offset - margin |
|||
); |
|||
float bottomOffset = Mathf.Min(this._scrollController.position.maxScrollExtent, |
|||
viewport.getOffsetToReveal(contextObject, 1.0f).offset + margin |
|||
); |
|||
bool onScreen = scrollOffset <= topOffset && scrollOffset >= bottomOffset; |
|||
|
|||
if (!onScreen) { |
|||
this._scrolling = true; |
|||
this._scrollController.position.animateTo( |
|||
scrollOffset < bottomOffset ? bottomOffset : topOffset, |
|||
duration: this._scrollAnimationDuration, |
|||
curve: Curves.easeInOut |
|||
).Then(() => { this.setState(() => { this._scrolling = false; }); }); |
|||
} |
|||
} |
|||
|
|||
Widget _buildContainerForScrollDirection(List<Widget> children = null) { |
|||
switch (this.widget.scrollDirection) { |
|||
case Axis.horizontal: |
|||
return new Row(children: children); |
|||
case Axis.vertical: |
|||
default: |
|||
return new Column(children: children); |
|||
} |
|||
} |
|||
|
|||
Widget _wrap(Widget toWrap, int index, BoxConstraints constraints) { |
|||
D.assert(toWrap.key != null); |
|||
GlobalObjectKey<State> keyIndexGlobalKey = new GlobalObjectKey<State>(toWrap.key); |
|||
|
|||
void onDragStarted() { |
|||
this.setState(() => { |
|||
this._dragging = toWrap.key; |
|||
this._dragStartIndex = index; |
|||
this._ghostIndex = index; |
|||
this._currentIndex = index; |
|||
this._entranceController.setValue(1.0f); |
|||
this._draggingFeedbackSize = keyIndexGlobalKey.currentContext.size; |
|||
}); |
|||
} |
|||
|
|||
void reorder(int startIndex, int endIndex) { |
|||
this.setState(() => { |
|||
if (startIndex != endIndex) { |
|||
this.widget.onReorder(startIndex, endIndex); |
|||
} |
|||
|
|||
this._ghostController.reverse(from: 0.1f); |
|||
this._entranceController.reverse(from: 0.1f); |
|||
this._dragging = null; |
|||
}); |
|||
} |
|||
|
|||
void onDragEnded() { |
|||
reorder(this._dragStartIndex, this._currentIndex); |
|||
} |
|||
|
|||
Widget buildDragTarget(BuildContext context, List<Key> acceptedCandidates, List<Key> rejectedCandidates) { |
|||
Widget child = new LongPressDraggable<Key>( |
|||
maxSimultaneousDrags: 1, |
|||
axis: this.widget.scrollDirection, |
|||
data: toWrap.key, |
|||
child: new SizedBox(), |
|||
childWhenDragging: new SizedBox(), |
|||
dragAnchor: DragAnchor.child, |
|||
onDragStarted: onDragStarted, |
|||
onDragCompleted: onDragEnded, |
|||
onDraggableCanceled: (Velocity velocity, Offset offset) => { onDragEnded(); } |
|||
); |
|||
|
|||
if (index >= this.widget.children.Count) { |
|||
child = toWrap; |
|||
} |
|||
|
|||
Widget spacing; |
|||
switch (this.widget.scrollDirection) { |
|||
case Axis.horizontal: |
|||
spacing = new SizedBox(width: this._dropAreaExtent); |
|||
break; |
|||
case Axis.vertical: |
|||
default: |
|||
spacing = new SizedBox(height: this._dropAreaExtent); |
|||
break; |
|||
} |
|||
|
|||
if (this._currentIndex == index) { |
|||
return this._buildContainerForScrollDirection(children: new List<Widget> { |
|||
new SizeTransition( |
|||
sizeFactor: this._entranceController, |
|||
axis: this.widget.scrollDirection, |
|||
child: spacing |
|||
), |
|||
child |
|||
}); |
|||
} |
|||
|
|||
if (this._ghostIndex == index) { |
|||
return this._buildContainerForScrollDirection(children: new List<Widget> { |
|||
new SizeTransition( |
|||
sizeFactor: this._ghostController, |
|||
axis: this.widget.scrollDirection, |
|||
child: spacing |
|||
), |
|||
child |
|||
}); |
|||
} |
|||
|
|||
return child; |
|||
} |
|||
|
|||
return new Builder(builder: (BuildContext context) => { |
|||
return new DragTarget<Key>( |
|||
builder: buildDragTarget, |
|||
onWillAccept: (Key toAccept) => { |
|||
this.setState(() => { |
|||
this._nextIndex = index; |
|||
this._requestAnimationToNextIndex(); |
|||
}); |
|||
this._scrollTo(this.context); |
|||
return this._dragging == toAccept && toAccept != toWrap.key; |
|||
}, |
|||
onAccept: (Key accepted) => { }, |
|||
onLeave: (Key leaving) => { } |
|||
); |
|||
}); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
D.assert(MaterialD.debugCheckHasMaterialLocalizations(context)); |
|||
return new LayoutBuilder(builder: (BuildContext _, BoxConstraints constraints) => { |
|||
List<Widget> wrappedChildren = new List<Widget> { }; |
|||
if (this.widget.header != null) { |
|||
wrappedChildren.Add(this.widget.header); |
|||
} |
|||
|
|||
for (int i = 0; i < this.widget.children.Count; i += 1) { |
|||
wrappedChildren.Add(this._wrap(this.widget.children[i], i, constraints)); |
|||
} |
|||
|
|||
Key endWidgetKey = Key.key("DraggableList - End Widget"); |
|||
Widget finalDropArea; |
|||
switch (this.widget.scrollDirection) { |
|||
case Axis.horizontal: |
|||
finalDropArea = new SizedBox( |
|||
key: endWidgetKey, |
|||
width: _defaultDropAreaExtent, |
|||
height: constraints.maxHeight |
|||
); |
|||
break; |
|||
case Axis.vertical: |
|||
default: |
|||
finalDropArea = new SizedBox( |
|||
key: endWidgetKey, |
|||
height: _defaultDropAreaExtent, |
|||
width: constraints.maxWidth |
|||
); |
|||
break; |
|||
} |
|||
|
|||
wrappedChildren.Add(this._wrap( |
|||
finalDropArea, this.widget.children.Count, |
|||
constraints) |
|||
); |
|||
return new SingleChildScrollView( |
|||
scrollDirection: this.widget.scrollDirection, |
|||
child: this._buildContainerForScrollDirection(children: wrappedChildren), |
|||
padding: this.widget.padding, |
|||
controller: this._scrollController |
|||
); |
|||
}); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 730da7185538491389639e2cc7099a21 |
|||
timeCreated: 1554301017 |
撰写
预览
正在加载...
取消
保存
Reference in new issue