您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
530 行
16 KiB
530 行
16 KiB
using System;
|
|
using Unity.UIWidgets.animation;
|
|
using Unity.UIWidgets.async;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.physics;
|
|
using Unity.UIWidgets.scheduler;
|
|
using Unity.UIWidgets.ui;
|
|
using UnityEngine;
|
|
|
|
namespace Unity.UIWidgets.widgets {
|
|
public interface ScrollActivityDelegate {
|
|
AxisDirection axisDirection { get; }
|
|
|
|
float setPixels(float pixels);
|
|
|
|
void applyUserOffset(float delta);
|
|
|
|
void applyUserScrollOffset(float delta);
|
|
|
|
void goIdle();
|
|
|
|
void goBallistic(float velocity);
|
|
}
|
|
|
|
public abstract class ScrollActivity {
|
|
public ScrollActivity(ScrollActivityDelegate del) {
|
|
_del = del;
|
|
}
|
|
|
|
public ScrollActivityDelegate del {
|
|
get { return _del; }
|
|
}
|
|
|
|
ScrollActivityDelegate _del;
|
|
|
|
public void updateDelegate(ScrollActivityDelegate value) {
|
|
D.assert(_del != value);
|
|
_del = value;
|
|
}
|
|
|
|
public virtual void resetActivity() {
|
|
}
|
|
|
|
public virtual void dispatchScrollStartNotification(ScrollMetrics metrics, BuildContext context) {
|
|
new ScrollStartNotification(metrics: metrics, context: context).dispatch(context);
|
|
}
|
|
|
|
public virtual void dispatchScrollUpdateNotification(ScrollMetrics metrics, BuildContext context,
|
|
float scrollDelta) {
|
|
new ScrollUpdateNotification(metrics: metrics, context: context, scrollDelta: scrollDelta)
|
|
.dispatch(context);
|
|
}
|
|
|
|
public virtual void dispatchOverscrollNotification(ScrollMetrics metrics, BuildContext context,
|
|
float overscroll) {
|
|
new OverscrollNotification(metrics: metrics, context: context, overscroll: overscroll).dispatch(context);
|
|
}
|
|
|
|
public virtual void dispatchScrollEndNotification(ScrollMetrics metrics, BuildContext context) {
|
|
new ScrollEndNotification(metrics: metrics, context: context).dispatch(context);
|
|
}
|
|
|
|
public virtual void applyNewDimensions() {
|
|
}
|
|
|
|
public abstract bool shouldIgnorePointer { get; }
|
|
|
|
public abstract bool isScrolling { get; }
|
|
|
|
public abstract float velocity { get; }
|
|
|
|
public virtual void dispose() {
|
|
_del = null;
|
|
}
|
|
|
|
public override string ToString() {
|
|
return foundation_.describeIdentity(this);
|
|
}
|
|
}
|
|
|
|
public class IdleScrollActivity : ScrollActivity {
|
|
public IdleScrollActivity(ScrollActivityDelegate del) : base(del) {
|
|
}
|
|
|
|
public override void applyNewDimensions() {
|
|
del.goBallistic(0.0f);
|
|
}
|
|
|
|
public override bool shouldIgnorePointer {
|
|
get { return false; }
|
|
}
|
|
|
|
public override bool isScrolling {
|
|
get { return false; }
|
|
}
|
|
|
|
public override float velocity {
|
|
get { return 0.0f; }
|
|
}
|
|
}
|
|
|
|
public interface ScrollHoldController {
|
|
void cancel();
|
|
}
|
|
|
|
public class HoldScrollActivity : ScrollActivity, ScrollHoldController {
|
|
public HoldScrollActivity(
|
|
ScrollActivityDelegate del = null,
|
|
VoidCallback onHoldCanceled = null
|
|
) : base(del) {
|
|
this.onHoldCanceled = onHoldCanceled;
|
|
}
|
|
|
|
public readonly VoidCallback onHoldCanceled;
|
|
|
|
public override bool shouldIgnorePointer {
|
|
get { return false; }
|
|
}
|
|
|
|
public override bool isScrolling {
|
|
get { return false; }
|
|
}
|
|
|
|
public override float velocity {
|
|
get { return 0.0f; }
|
|
}
|
|
|
|
public void cancel() {
|
|
del.goBallistic(0.0f);
|
|
}
|
|
|
|
public override void dispose() {
|
|
if (onHoldCanceled != null) {
|
|
onHoldCanceled();
|
|
}
|
|
|
|
base.dispose();
|
|
}
|
|
}
|
|
|
|
public class ScrollDragController : Drag {
|
|
public ScrollDragController(
|
|
ScrollActivityDelegate del = null,
|
|
DragStartDetails details = null,
|
|
VoidCallback onDragCanceled = null,
|
|
float? carriedVelocity = null,
|
|
float? motionStartDistanceThreshold = null
|
|
) {
|
|
D.assert(del != null);
|
|
D.assert(details != null);
|
|
D.assert(
|
|
motionStartDistanceThreshold == null || motionStartDistanceThreshold > 0.0,
|
|
() => "motionStartDistanceThreshold must be a positive number or null"
|
|
);
|
|
|
|
_del = del;
|
|
_lastDetails = details;
|
|
_retainMomentum = carriedVelocity != null && carriedVelocity != 0.0;
|
|
_lastNonStationaryTimestamp = details.sourceTimeStamp;
|
|
_offsetSinceLastStop = motionStartDistanceThreshold == null ? (float?) null : 0.0f;
|
|
|
|
this.onDragCanceled = onDragCanceled;
|
|
this.carriedVelocity = carriedVelocity;
|
|
this.motionStartDistanceThreshold = motionStartDistanceThreshold;
|
|
}
|
|
|
|
public ScrollActivityDelegate del {
|
|
get { return _del; }
|
|
}
|
|
|
|
ScrollActivityDelegate _del;
|
|
|
|
public readonly VoidCallback onDragCanceled;
|
|
|
|
public readonly float? carriedVelocity;
|
|
|
|
public readonly float? motionStartDistanceThreshold;
|
|
|
|
TimeSpan? _lastNonStationaryTimestamp;
|
|
|
|
bool _retainMomentum;
|
|
|
|
float? _offsetSinceLastStop;
|
|
|
|
public static readonly TimeSpan momentumRetainStationaryDurationThreshold = new TimeSpan(0, 0, 0, 0, 20);
|
|
|
|
public static readonly TimeSpan motionStoppedDurationThreshold = new TimeSpan(0, 0, 0, 0, 50);
|
|
|
|
const float _bigThresholdBreakDistance = 24.0f;
|
|
|
|
bool _reversed {
|
|
get { return AxisUtils.axisDirectionIsReversed(del.axisDirection); }
|
|
}
|
|
|
|
public void updateDelegate(ScrollActivityDelegate value) {
|
|
D.assert(_del != value);
|
|
_del = value;
|
|
}
|
|
|
|
void _maybeLoseMomentum(float offset, TimeSpan? timestamp) {
|
|
if (_retainMomentum &&
|
|
offset == 0.0 &&
|
|
(timestamp == null ||
|
|
timestamp - _lastNonStationaryTimestamp >
|
|
momentumRetainStationaryDurationThreshold)) {
|
|
_retainMomentum = false;
|
|
}
|
|
}
|
|
|
|
float _adjustForScrollStartThreshold(float offset, TimeSpan? timestamp) {
|
|
if (timestamp == null) {
|
|
return offset;
|
|
}
|
|
|
|
if (offset == 0.0) {
|
|
if (motionStartDistanceThreshold != null &&
|
|
_offsetSinceLastStop == null &&
|
|
timestamp - _lastNonStationaryTimestamp >
|
|
motionStoppedDurationThreshold) {
|
|
_offsetSinceLastStop = 0.0f;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
else {
|
|
if (_offsetSinceLastStop == null) {
|
|
return offset;
|
|
}
|
|
else {
|
|
_offsetSinceLastStop += offset;
|
|
if (_offsetSinceLastStop.Value.abs() > motionStartDistanceThreshold) {
|
|
_offsetSinceLastStop = null;
|
|
if (offset.abs() > _bigThresholdBreakDistance) {
|
|
return offset;
|
|
}
|
|
else {
|
|
return Mathf.Min(
|
|
motionStartDistanceThreshold.Value / 3.0f,
|
|
offset.abs()
|
|
) * offset.sign();
|
|
}
|
|
}
|
|
else {
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void update(DragUpdateDetails details) {
|
|
D.assert(details.primaryDelta != null);
|
|
_lastDetails = details;
|
|
float offset = details.primaryDelta.Value;
|
|
|
|
if (details.isScroll) {
|
|
if (offset == 0.0) {
|
|
return;
|
|
}
|
|
|
|
if (_reversed) {
|
|
offset = -offset;
|
|
}
|
|
|
|
|
|
del.applyUserScrollOffset(offset);
|
|
return;
|
|
}
|
|
|
|
|
|
if (offset != 0.0) {
|
|
_lastNonStationaryTimestamp = details.sourceTimeStamp;
|
|
}
|
|
|
|
_maybeLoseMomentum(offset, details.sourceTimeStamp);
|
|
offset = _adjustForScrollStartThreshold(offset, details.sourceTimeStamp);
|
|
if (offset == 0.0) {
|
|
return;
|
|
}
|
|
|
|
if (_reversed) {
|
|
offset = -offset;
|
|
}
|
|
|
|
del.applyUserOffset(offset);
|
|
}
|
|
|
|
public void end(DragEndDetails details) {
|
|
D.assert(details.primaryVelocity != null);
|
|
float velocity = -details.primaryVelocity.Value;
|
|
if (_reversed) {
|
|
velocity = -velocity;
|
|
}
|
|
|
|
_lastDetails = details;
|
|
|
|
if (_retainMomentum && velocity.sign() == carriedVelocity.Value.sign()) {
|
|
velocity += carriedVelocity.Value;
|
|
}
|
|
|
|
del.goBallistic(velocity);
|
|
}
|
|
|
|
public void cancel() {
|
|
del.goBallistic(0.0f);
|
|
}
|
|
|
|
public virtual void dispose() {
|
|
_lastDetails = null;
|
|
if (onDragCanceled != null) {
|
|
onDragCanceled();
|
|
}
|
|
}
|
|
|
|
public object lastDetails {
|
|
get { return _lastDetails; }
|
|
}
|
|
|
|
object _lastDetails;
|
|
|
|
public override string ToString() {
|
|
return foundation_.describeIdentity(this);
|
|
}
|
|
}
|
|
|
|
public class DragScrollActivity : ScrollActivity {
|
|
public DragScrollActivity(
|
|
ScrollActivityDelegate del,
|
|
ScrollDragController controller
|
|
) : base(del) {
|
|
_controller = controller;
|
|
}
|
|
|
|
ScrollDragController _controller;
|
|
|
|
public override void dispatchScrollStartNotification(ScrollMetrics metrics, BuildContext context) {
|
|
object lastDetails = _controller.lastDetails;
|
|
D.assert(lastDetails is DragStartDetails);
|
|
new ScrollStartNotification(metrics: metrics, context: context, dragDetails: (DragStartDetails) lastDetails)
|
|
.dispatch(context);
|
|
}
|
|
|
|
public override void dispatchScrollUpdateNotification(ScrollMetrics metrics, BuildContext context,
|
|
float scrollDelta) {
|
|
object lastDetails = _controller.lastDetails;
|
|
D.assert(lastDetails is DragUpdateDetails);
|
|
new ScrollUpdateNotification(metrics: metrics, context: context, scrollDelta: scrollDelta,
|
|
dragDetails: (DragUpdateDetails) lastDetails).dispatch(context);
|
|
}
|
|
|
|
public override void dispatchOverscrollNotification(ScrollMetrics metrics, BuildContext context,
|
|
float overscroll) {
|
|
object lastDetails = _controller.lastDetails;
|
|
D.assert(lastDetails is DragUpdateDetails);
|
|
new OverscrollNotification(metrics: metrics, context: context, overscroll: overscroll,
|
|
dragDetails: (DragUpdateDetails) lastDetails).dispatch(context);
|
|
}
|
|
|
|
public override void dispatchScrollEndNotification(ScrollMetrics metrics, BuildContext context) {
|
|
object lastDetails = _controller.lastDetails;
|
|
new ScrollEndNotification(
|
|
metrics: metrics,
|
|
context: context,
|
|
dragDetails: lastDetails as DragEndDetails
|
|
).dispatch(context);
|
|
}
|
|
|
|
public override bool shouldIgnorePointer {
|
|
get { return true; }
|
|
}
|
|
|
|
public override bool isScrolling {
|
|
get { return true; }
|
|
}
|
|
|
|
public override float velocity {
|
|
get { return 0.0f; }
|
|
}
|
|
|
|
public override void dispose() {
|
|
_controller = null;
|
|
base.dispose();
|
|
}
|
|
|
|
public override string ToString() {
|
|
return $"{foundation_.describeIdentity(this)}({_controller})";
|
|
}
|
|
}
|
|
|
|
public class BallisticScrollActivity : ScrollActivity {
|
|
public BallisticScrollActivity(
|
|
ScrollActivityDelegate del,
|
|
Simulation simulation,
|
|
TickerProvider vsync
|
|
) : base(del) {
|
|
_controller = AnimationController.unbounded(
|
|
debugLabel: GetType().ToString(),
|
|
vsync: vsync
|
|
);
|
|
|
|
_controller.addListener(_tick);
|
|
_controller.animateWith(simulation).then(o => _end());
|
|
}
|
|
|
|
public override float velocity {
|
|
get { return _controller.velocity; }
|
|
}
|
|
|
|
readonly AnimationController _controller;
|
|
|
|
public override void resetActivity() {
|
|
del.goBallistic(velocity);
|
|
}
|
|
|
|
public override void applyNewDimensions() {
|
|
del.goBallistic(velocity);
|
|
}
|
|
|
|
void _tick() {
|
|
if (!applyMoveTo(_controller.value)) {
|
|
del.goIdle();
|
|
}
|
|
}
|
|
|
|
protected virtual bool applyMoveTo(float value) {
|
|
return del.setPixels(value) == 0.0;
|
|
}
|
|
|
|
void _end() {
|
|
if (del != null) {
|
|
del.goBallistic(0.0f);
|
|
}
|
|
}
|
|
|
|
public override void dispatchOverscrollNotification(
|
|
ScrollMetrics metrics, BuildContext context, float overscroll) {
|
|
new OverscrollNotification(metrics: metrics, context: context, overscroll: overscroll,
|
|
velocity: velocity).dispatch(context);
|
|
}
|
|
|
|
public override bool shouldIgnorePointer {
|
|
get { return true; }
|
|
}
|
|
|
|
public override bool isScrolling {
|
|
get { return true; }
|
|
}
|
|
|
|
public override void dispose() {
|
|
_controller.dispose();
|
|
base.dispose();
|
|
}
|
|
|
|
public override string ToString() {
|
|
return $"{foundation_.describeIdentity(this)}({_controller})";
|
|
}
|
|
}
|
|
|
|
public class DrivenScrollActivity : ScrollActivity {
|
|
public DrivenScrollActivity(
|
|
ScrollActivityDelegate del,
|
|
float from,
|
|
float to,
|
|
TimeSpan duration,
|
|
Curve curve,
|
|
TickerProvider vsync
|
|
) : base(del) {
|
|
D.assert(duration > TimeSpan.Zero);
|
|
D.assert(curve != null);
|
|
|
|
_completer = Completer.create();
|
|
_controller = AnimationController.unbounded(
|
|
value: from,
|
|
debugLabel: GetType().ToString(),
|
|
vsync: vsync
|
|
);
|
|
_controller.addListener(_tick);
|
|
_controller.animateTo(to, duration: duration, curve: curve)
|
|
.then(o => _end());
|
|
}
|
|
|
|
readonly Completer _completer;
|
|
readonly AnimationController _controller;
|
|
|
|
public Future done {
|
|
get { return _completer.future; }
|
|
}
|
|
|
|
public override float velocity {
|
|
get { return _controller.velocity; }
|
|
}
|
|
|
|
void _tick() {
|
|
if (del.setPixels(_controller.value) != 0.0) {
|
|
del.goIdle();
|
|
}
|
|
}
|
|
|
|
void _end() {
|
|
if (del != null) {
|
|
del.goBallistic(velocity);
|
|
}
|
|
}
|
|
|
|
public override void dispatchOverscrollNotification(
|
|
ScrollMetrics metrics, BuildContext context, float overscroll) {
|
|
new OverscrollNotification(metrics: metrics, context: context, overscroll: overscroll,
|
|
velocity: velocity).dispatch(context);
|
|
}
|
|
|
|
public override bool shouldIgnorePointer {
|
|
get { return true; }
|
|
}
|
|
|
|
public override bool isScrolling {
|
|
get { return true; }
|
|
}
|
|
|
|
public override void dispose() {
|
|
_completer.complete();
|
|
_controller.dispose();
|
|
base.dispose();
|
|
}
|
|
|
|
public override string ToString() {
|
|
return $"{foundation_.describeIdentity(this)}({_controller})";
|
|
}
|
|
}
|
|
}
|