您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
425 行
16 KiB
425 行
16 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using RSG;
|
|
using Unity.UIWidgets.animation;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.scheduler;
|
|
using Unity.UIWidgets.ui;
|
|
using UnityEngine;
|
|
|
|
namespace Unity.UIWidgets.widgets {
|
|
public delegate Widget ViewportBuilder(BuildContext context, ViewportOffset position);
|
|
|
|
public class Scrollable : StatefulWidget {
|
|
public Scrollable(
|
|
Key key = null,
|
|
AxisDirection axisDirection = AxisDirection.down,
|
|
ScrollController controller = null,
|
|
ScrollPhysics physics = null,
|
|
ViewportBuilder viewportBuilder = null,
|
|
DragStartBehavior dragStartBehavior = DragStartBehavior.start
|
|
) : base(key: key) {
|
|
D.assert(viewportBuilder != null);
|
|
|
|
this.axisDirection = axisDirection;
|
|
this.controller = controller;
|
|
this.physics = physics;
|
|
this.viewportBuilder = viewportBuilder;
|
|
this.dragStartBehavior = dragStartBehavior;
|
|
}
|
|
|
|
public readonly AxisDirection axisDirection;
|
|
|
|
public readonly ScrollController controller;
|
|
|
|
public readonly ScrollPhysics physics;
|
|
|
|
public readonly ViewportBuilder viewportBuilder;
|
|
|
|
public readonly DragStartBehavior dragStartBehavior;
|
|
|
|
public Axis axis {
|
|
get { return AxisUtils.axisDirectionToAxis(this.axisDirection); }
|
|
}
|
|
|
|
public override State createState() {
|
|
return new ScrollableState();
|
|
}
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
base.debugFillProperties(properties);
|
|
properties.add(new EnumProperty<AxisDirection>("axisDirection", this.axisDirection));
|
|
properties.add(new DiagnosticsProperty<ScrollPhysics>("physics", this.physics));
|
|
}
|
|
|
|
public static ScrollableState of(BuildContext context) {
|
|
_ScrollableScope widget = (_ScrollableScope) context.inheritFromWidgetOfExactType(typeof(_ScrollableScope));
|
|
return widget == null ? null : widget.scrollable;
|
|
}
|
|
|
|
public static IPromise ensureVisible(BuildContext context,
|
|
float alignment = 0.0f,
|
|
TimeSpan? duration = null,
|
|
Curve curve = null
|
|
) {
|
|
duration = duration ?? TimeSpan.Zero;
|
|
curve = curve ?? Curves.ease;
|
|
List<IPromise> futures = new List<IPromise>();
|
|
|
|
ScrollableState scrollable = of(context);
|
|
while (scrollable != null) {
|
|
futures.Add(scrollable.position.ensureVisible(
|
|
context.findRenderObject(),
|
|
alignment: alignment,
|
|
duration: duration,
|
|
curve: curve
|
|
));
|
|
context = scrollable.context;
|
|
scrollable = of(context);
|
|
}
|
|
|
|
if (futures.isEmpty() || duration == TimeSpan.Zero) {
|
|
return Promise.Resolved();
|
|
}
|
|
|
|
if (futures.Count == 1) {
|
|
return futures.Single();
|
|
}
|
|
|
|
return Promise.All(futures);
|
|
}
|
|
}
|
|
|
|
class _ScrollableScope : InheritedWidget {
|
|
internal _ScrollableScope(
|
|
Key key = null,
|
|
ScrollableState scrollable = null,
|
|
ScrollPosition position = null,
|
|
Widget child = null
|
|
) : base(key: key, child: child) {
|
|
D.assert(scrollable != null);
|
|
D.assert(child != null);
|
|
this.scrollable = scrollable;
|
|
this.position = position;
|
|
}
|
|
|
|
public readonly ScrollableState scrollable;
|
|
|
|
public readonly ScrollPosition position;
|
|
|
|
public override bool updateShouldNotify(InheritedWidget old) {
|
|
return this.position != ((_ScrollableScope) old).position;
|
|
}
|
|
}
|
|
|
|
public class ScrollableState : TickerProviderStateMixin<Scrollable>, ScrollContext {
|
|
public ScrollPosition position {
|
|
get { return this._position; }
|
|
}
|
|
|
|
ScrollPosition _position;
|
|
|
|
public AxisDirection axisDirection {
|
|
get { return this.widget.axisDirection; }
|
|
}
|
|
|
|
ScrollBehavior _configuration;
|
|
|
|
ScrollPhysics _physics;
|
|
|
|
void _updatePosition() {
|
|
this._configuration = ScrollConfiguration.of(this.context);
|
|
this._physics = this._configuration.getScrollPhysics(this.context);
|
|
if (this.widget.physics != null) {
|
|
this._physics = this.widget.physics.applyTo(this._physics);
|
|
}
|
|
|
|
ScrollController controller = this.widget.controller;
|
|
ScrollPosition oldPosition = this.position;
|
|
if (oldPosition != null) {
|
|
if (controller != null) {
|
|
controller.detach(oldPosition);
|
|
}
|
|
|
|
Window.instance.scheduleMicrotask(oldPosition.dispose);
|
|
}
|
|
|
|
this._position = controller == null
|
|
? null
|
|
: controller.createScrollPosition(this._physics, this, oldPosition);
|
|
this._position = this._position
|
|
?? new ScrollPositionWithSingleContext(physics: this._physics, context: this,
|
|
oldPosition: oldPosition);
|
|
D.assert(this.position != null);
|
|
if (controller != null) {
|
|
controller.attach(this.position);
|
|
}
|
|
}
|
|
|
|
public override void didChangeDependencies() {
|
|
base.didChangeDependencies();
|
|
this._updatePosition();
|
|
}
|
|
|
|
bool _shouldUpdatePosition(Scrollable oldWidget) {
|
|
ScrollPhysics newPhysics = this.widget.physics;
|
|
ScrollPhysics oldPhysics = oldWidget.physics;
|
|
do {
|
|
Type newPhysicsType = newPhysics != null ? newPhysics.GetType() : null;
|
|
Type oldPhysicsType = oldPhysics != null ? oldPhysics.GetType() : null;
|
|
|
|
if (newPhysicsType != oldPhysicsType) {
|
|
return true;
|
|
}
|
|
|
|
if (newPhysics != null) {
|
|
newPhysics = newPhysics.parent;
|
|
}
|
|
|
|
if (oldPhysics != null) {
|
|
oldPhysics = oldPhysics.parent;
|
|
}
|
|
} while (newPhysics != null || oldPhysics != null);
|
|
|
|
Type controllerType = this.widget.controller == null ? null : this.widget.controller.GetType();
|
|
Type oldControllerType = oldWidget.controller == null ? null : oldWidget.controller.GetType();
|
|
return controllerType != oldControllerType;
|
|
}
|
|
|
|
public override void didUpdateWidget(StatefulWidget oldWidgetRaw) {
|
|
Scrollable oldWidget = (Scrollable) oldWidgetRaw;
|
|
base.didUpdateWidget(oldWidget);
|
|
|
|
if (this.widget.controller != oldWidget.controller) {
|
|
if (oldWidget.controller != null) {
|
|
oldWidget.controller.detach(this.position);
|
|
}
|
|
|
|
if (this.widget.controller != null) {
|
|
this.widget.controller.attach(this.position);
|
|
}
|
|
}
|
|
|
|
if (this._shouldUpdatePosition(oldWidget)) {
|
|
this._updatePosition();
|
|
}
|
|
}
|
|
|
|
public override void dispose() {
|
|
if (this.widget.controller != null) {
|
|
this.widget.controller.detach(this.position);
|
|
}
|
|
|
|
this.position.dispose();
|
|
base.dispose();
|
|
}
|
|
|
|
readonly GlobalKey<RawGestureDetectorState> _gestureDetectorKey = GlobalKey<RawGestureDetectorState>.key();
|
|
readonly GlobalKey _ignorePointerKey = GlobalKey.key();
|
|
|
|
Dictionary<Type, GestureRecognizerFactory> _gestureRecognizers =
|
|
new Dictionary<Type, GestureRecognizerFactory>();
|
|
|
|
bool _shouldIgnorePointer = false;
|
|
|
|
bool _lastCanDrag;
|
|
Axis _lastAxisDirection;
|
|
|
|
public void setCanDrag(bool canDrag) {
|
|
if (canDrag == this._lastCanDrag && (!canDrag || this.widget.axis == this._lastAxisDirection)) {
|
|
return;
|
|
}
|
|
|
|
if (!canDrag) {
|
|
this._gestureRecognizers = new Dictionary<Type, GestureRecognizerFactory>();
|
|
}
|
|
else {
|
|
switch (this.widget.axis) {
|
|
case Axis.vertical:
|
|
this._gestureRecognizers = new Dictionary<Type, GestureRecognizerFactory>();
|
|
this._gestureRecognizers.Add(typeof(VerticalDragGestureRecognizer),
|
|
new GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
|
|
() => new VerticalDragGestureRecognizer(),
|
|
instance => {
|
|
instance.onDown = this._handleDragDown;
|
|
instance.onStart = this._handleDragStart;
|
|
instance.onUpdate = this._handleDragUpdate;
|
|
instance.onEnd = this._handleDragEnd;
|
|
instance.onCancel = this._handleDragCancel;
|
|
instance.minFlingDistance =
|
|
this._physics == null ? (float?) null : this._physics.minFlingDistance;
|
|
instance.minFlingVelocity =
|
|
this._physics == null ? (float?) null : this._physics.minFlingVelocity;
|
|
instance.maxFlingVelocity =
|
|
this._physics == null ? (float?) null : this._physics.maxFlingVelocity;
|
|
instance.dragStartBehavior = this.widget.dragStartBehavior;
|
|
}
|
|
));
|
|
break;
|
|
case Axis.horizontal:
|
|
this._gestureRecognizers = new Dictionary<Type, GestureRecognizerFactory>();
|
|
this._gestureRecognizers.Add(typeof(HorizontalDragGestureRecognizer),
|
|
new GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
|
|
() => new HorizontalDragGestureRecognizer(),
|
|
instance => {
|
|
instance.onDown = this._handleDragDown;
|
|
instance.onStart = this._handleDragStart;
|
|
instance.onUpdate = this._handleDragUpdate;
|
|
instance.onEnd = this._handleDragEnd;
|
|
instance.onCancel = this._handleDragCancel;
|
|
instance.minFlingDistance =
|
|
this._physics == null ? (float?) null : this._physics.minFlingDistance;
|
|
instance.minFlingVelocity =
|
|
this._physics == null ? (float?) null : this._physics.minFlingVelocity;
|
|
instance.maxFlingVelocity =
|
|
this._physics == null ? (float?) null : this._physics.maxFlingVelocity;
|
|
instance.dragStartBehavior = this.widget.dragStartBehavior;
|
|
}
|
|
));
|
|
break;
|
|
}
|
|
}
|
|
|
|
this._lastCanDrag = canDrag;
|
|
this._lastAxisDirection = this.widget.axis;
|
|
if (this._gestureDetectorKey.currentState != null) {
|
|
this._gestureDetectorKey.currentState.replaceGestureRecognizers(this._gestureRecognizers);
|
|
}
|
|
}
|
|
|
|
public TickerProvider vsync {
|
|
get { return this; }
|
|
}
|
|
|
|
public void setIgnorePointer(bool value) {
|
|
if (this._shouldIgnorePointer == value) {
|
|
return;
|
|
}
|
|
|
|
this._shouldIgnorePointer = value;
|
|
if (this._ignorePointerKey.currentContext != null) {
|
|
var renderBox = (RenderIgnorePointer) this._ignorePointerKey.currentContext.findRenderObject();
|
|
renderBox.ignoring = this._shouldIgnorePointer;
|
|
}
|
|
}
|
|
|
|
public BuildContext notificationContext {
|
|
get { return this._gestureDetectorKey.currentContext; }
|
|
}
|
|
|
|
public BuildContext storageContext {
|
|
get { return this.context; }
|
|
}
|
|
|
|
Drag _drag;
|
|
|
|
ScrollHoldController _hold;
|
|
|
|
void _handleDragDown(DragDownDetails details) {
|
|
D.assert(this._drag == null);
|
|
D.assert(this._hold == null);
|
|
this._hold = this.position.hold(this._disposeHold);
|
|
}
|
|
|
|
void _handleDragStart(DragStartDetails details) {
|
|
D.assert(this._drag == null);
|
|
this._drag = this.position.drag(details, this._disposeDrag);
|
|
D.assert(this._drag != null);
|
|
D.assert(this._hold == null);
|
|
}
|
|
|
|
void _handleDragUpdate(DragUpdateDetails details) {
|
|
D.assert(this._hold == null || this._drag == null);
|
|
if (this._drag != null) {
|
|
this._drag.update(details);
|
|
}
|
|
}
|
|
|
|
void _handleDragEnd(DragEndDetails details) {
|
|
D.assert(this._hold == null || this._drag == null);
|
|
if (this._drag != null) {
|
|
this._drag.end(details);
|
|
}
|
|
|
|
D.assert(this._drag == null);
|
|
}
|
|
|
|
void _handleDragCancel() {
|
|
D.assert(this._hold == null || this._drag == null);
|
|
if (this._hold != null) {
|
|
this._hold.cancel();
|
|
}
|
|
|
|
if (this._drag != null) {
|
|
this._drag.cancel();
|
|
}
|
|
|
|
D.assert(this._hold == null);
|
|
D.assert(this._drag == null);
|
|
}
|
|
|
|
float _targetScrollOffsetForPointerScroll(PointerScrollEvent e) {
|
|
float delta = this.widget.axis == Axis.horizontal ? e.delta.dx : e.delta.dy;
|
|
return Mathf.Min(Mathf.Max(this.position.pixels + delta, this.position.minScrollExtent),
|
|
this.position.maxScrollExtent);
|
|
}
|
|
|
|
void _receivedPointerSignal(PointerSignalEvent e) {
|
|
if (e is PointerScrollEvent && this.position != null) {
|
|
float targetScrollOffset = this._targetScrollOffsetForPointerScroll(e as PointerScrollEvent);
|
|
if (targetScrollOffset != this.position.pixels) {
|
|
GestureBinding.instance.pointerSignalResolver.register(e, this._handlePointerScroll);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _handlePointerScroll(PointerEvent e) {
|
|
D.assert(e is PointerScrollEvent);
|
|
float targetScrollOffset = this._targetScrollOffsetForPointerScroll(e as PointerScrollEvent);
|
|
if (targetScrollOffset != this.position.pixels) {
|
|
this.position.jumpTo(targetScrollOffset);
|
|
}
|
|
}
|
|
|
|
void _disposeHold() {
|
|
this._hold = null;
|
|
}
|
|
|
|
void _disposeDrag() {
|
|
this._drag = null;
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
D.assert(this.position != null);
|
|
|
|
Widget result = new _ScrollableScope(
|
|
scrollable: this,
|
|
position: this.position,
|
|
child: new Listener(
|
|
onPointerSignal: this._receivedPointerSignal,
|
|
child: new RawGestureDetector(
|
|
key: this._gestureDetectorKey,
|
|
gestures: this._gestureRecognizers,
|
|
behavior: HitTestBehavior.opaque,
|
|
child: new IgnorePointer(
|
|
key: this._ignorePointerKey,
|
|
ignoring: this._shouldIgnorePointer,
|
|
child: this.widget.viewportBuilder(context, this.position)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
return this._configuration.buildViewportChrome(context, result, this.widget.axisDirection);
|
|
}
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
base.debugFillProperties(properties);
|
|
properties.add(new DiagnosticsProperty<ScrollPosition>("position", this.position));
|
|
}
|
|
}
|
|
}
|