浏览代码

Merge pull request #164 from UnityTech/gesture

Gesture
/main
GitHub 6 年前
当前提交
90af3128
共有 6 个文件被更改,包括 558 次插入22 次删除
  1. 2
      Runtime/gestures/constants.cs
  2. 80
      Runtime/widgets/gesture_detector.cs
  3. 412
      Runtime/gestures/scale.cs
  4. 11
      Runtime/gestures/scale.cs.meta
  5. 64
      Samples/UIWidgetSample/ScaleGestureSample.cs
  6. 11
      Samples/UIWidgetSample/ScaleGestureSample.cs.meta

2
Runtime/gestures/constants.cs


public const float kPanSlop = kTouchSlop * 2.0f;
public const float kScaleSlop = kTouchSlop;
public static readonly TimeSpan kPressTimeout = new TimeSpan(0, 0, 0, 0, 100);
public static readonly TimeSpan kDoubleTapTimeout = new TimeSpan(0, 0, 0, 0, 300);

80
Runtime/widgets/gesture_detector.cs


GestureDragUpdateCallback onPanUpdate = null,
GestureDragEndCallback onPanEnd = null,
GestureDragCancelCallback onPanCancel = null,
GestureScaleStartCallback onScaleStart = null,
GestureScaleUpdateCallback onScaleUpdate = null,
GestureScaleEndCallback onScaleEnd = null,
HitTestBehavior behavior = HitTestBehavior.deferToChild,
DragStartBehavior dragStartBehavior = DragStartBehavior.down
) : base(key) {

onHorizontalDragStart != null || onHorizontalDragUpdate != null ||
onHorizontalDragEnd != null;
bool haveLongPress = onLongPress != null || onLongPressUp != null;
bool haveLongPressDrag = onLongPressDragStart != null || onLongPressDragUpdate != null || onLongPressDragUp != null;
bool haveLongPressDrag = onLongPressDragStart != null || onLongPressDragUpdate != null ||
onLongPressDragUp != null;
if (havePan) {
bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
if (havePan || haveScale) {
if (havePan && haveScale) {
throw new UIWidgetsError(
"Incorrect GestureDetector arguments.\n" +
"Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer."
);
}
string recognizer = havePan ? "pan" : "scale";
"Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a pan gesture recognizer " +
"will result in the pan gesture recognizer being ignored, since the other two will catch all drags."
$"Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a {recognizer} gesture recognizer " +
$"will result in the {recognizer} gesture recognizer being ignored, since the other two will catch all drags."
"Incorrect GestureDetector arguments.\n" +
"Having both a long press and a long press drag recognizer is " +
"redundant as the long press drag is a superset of long press. " +
"Except long press drag allows for drags after the long press is " +
"triggered."
);
"Incorrect GestureDetector arguments.\n" +
"Having both a long press and a long press drag recognizer is " +
"redundant as the long press drag is a superset of long press. " +
"Except long press drag allows for drags after the long press is " +
"triggered."
);
}
return true;

this.onPanUpdate = onPanUpdate;
this.onPanEnd = onPanEnd;
this.onPanCancel = onPanCancel;
this.onScaleStart = onScaleStart;
this.onScaleUpdate = onScaleUpdate;
this.onScaleEnd = onScaleEnd;
this.behavior = behavior;
this.dragStartBehavior = dragStartBehavior;
}

public readonly GestureDragUpdateCallback onPanUpdate;
public readonly GestureDragEndCallback onPanEnd;
public readonly GestureDragCancelCallback onPanCancel;
public readonly GestureScaleStartCallback onScaleStart;
public readonly GestureScaleUpdateCallback onScaleUpdate;
public readonly GestureScaleEndCallback onScaleEnd;
public readonly HitTestBehavior behavior;
public readonly DragStartBehavior dragStartBehavior;

instance => { instance.onLongPress = this.onLongPress; }
);
}
if (this.onLongPressDragStart != null || this.onLongPressDragUpdate != null || this.onLongPressDragUp != null) {
gestures[typeof(LongPressDragGestureRecognizer)] = new GestureRecognizerFactoryWithHandlers<LongPressDragGestureRecognizer>(
() => new LongPressDragGestureRecognizer(debugOwner: this),
(LongPressDragGestureRecognizer instance) => {
instance.onLongPressStart = this.onLongPressDragStart;
instance.onLongPressDragUpdate = this.onLongPressDragUpdate;
instance.onLongPressUp = this.onLongPressDragUp;
}
);
if (this.onLongPressDragStart != null || this.onLongPressDragUpdate != null ||
this.onLongPressDragUp != null) {
gestures[typeof(LongPressDragGestureRecognizer)] =
new GestureRecognizerFactoryWithHandlers<LongPressDragGestureRecognizer>(
() => new LongPressDragGestureRecognizer(debugOwner: this),
(LongPressDragGestureRecognizer instance) => {
instance.onLongPressStart = this.onLongPressDragStart;
instance.onLongPressDragUpdate = this.onLongPressDragUpdate;
instance.onLongPressUp = this.onLongPressDragUp;
}
);
}
if (this.onVerticalDragDown != null ||

instance.onEnd = this.onPanEnd;
instance.onCancel = this.onPanCancel;
instance.dragStartBehavior = this.dragStartBehavior;
}
);
}
if (this.onScaleStart != null ||
this.onScaleUpdate != null ||
this.onScaleEnd != null) {
gestures[typeof(ScaleGestureRecognizer)] =
new GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
() => new ScaleGestureRecognizer(debugOwner: this),
instance => {
instance.onStart = this.onScaleStart;
instance.onUpdate = this.onScaleUpdate;
instance.onEnd = this.onScaleEnd;
}
);
}

: gestures[type].constructorRaw();
D.assert(this._recognizers[type].GetType() == type,
() => "GestureRecognizerFactory of type " + type + " created a GestureRecognizer of type " +
this._recognizers[type].GetType() +
". The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.");
this._recognizers[type].GetType() +
". The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.");
gestures[type].initializerRaw(this._recognizers[type]);
}

412
Runtime/gestures/scale.cs


using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
using UnityEngine;
namespace Unity.UIWidgets.gestures {
enum _ScaleState {
ready,
possible,
accepted,
started
}
public class ScaleStartDetails {
public ScaleStartDetails(Offset focalPoint = null) {
this.focalPoint = focalPoint ?? Offset.zero;
}
public readonly Offset focalPoint;
public override string ToString() {
return $"ScaleStartDetails(focalPoint: {this.focalPoint}";
}
}
public class ScaleUpdateDetails {
public ScaleUpdateDetails(
Offset focalPoint = null,
float scale = 1.0f,
float horizontalScale = 1.0f,
float verticalScale = 1.0f,
float rotation = 0.0f
) {
focalPoint = focalPoint ?? Offset.zero;
D.assert(scale >= 0.0f);
D.assert(horizontalScale >= 0.0f);
D.assert(verticalScale >= 0.0f);
this.focalPoint = focalPoint;
this.scale = scale;
this.horizontalScale = horizontalScale;
this.verticalScale = verticalScale;
this.rotation = rotation;
}
public readonly Offset focalPoint;
public readonly float scale;
public readonly float horizontalScale;
public readonly float verticalScale;
public readonly float rotation;
public override string ToString() {
return
$"ScaleUpdateDetails(focalPoint: {this.focalPoint}, scale: {this.scale}, horizontalScale: {this.horizontalScale}, verticalScale: {this.verticalScale}, rotation: {this.rotation}";
}
}
public class ScaleEndDetails {
public ScaleEndDetails(Velocity velocity = null) {
this.velocity = velocity ?? Velocity.zero;
}
public readonly Velocity velocity;
public override string ToString() {
return $"ScaleEndDetails(velocity: {this.velocity}";
}
}
public delegate void GestureScaleStartCallback(ScaleStartDetails details);
public delegate void GestureScaleUpdateCallback(ScaleUpdateDetails details);
public delegate void GestureScaleEndCallback(ScaleEndDetails details);
static class _ScaleGestureUtils {
public static bool _isFlingGesture(Velocity velocity) {
D.assert(velocity != null);
float speedSquared = velocity.pixelsPerSecond.distanceSquared;
return speedSquared > Constants.kMinFlingVelocity * Constants.kMinFlingVelocity;
}
}
class _LineBetweenPointers {
public _LineBetweenPointers(
Offset pointerStartLocation = null,
int pointerStartId = 0,
Offset pointerEndLocation = null,
int pointerEndId = 1) {
pointerStartLocation = pointerEndLocation ?? Offset.zero;
pointerEndLocation = pointerEndLocation ?? Offset.zero;
D.assert(pointerStartId != pointerEndId);
this.pointerStartLocation = pointerStartLocation;
this.pointerStartId = pointerStartId;
this.pointerEndLocation = pointerEndLocation;
this.pointerEndId = pointerEndId;
}
public readonly Offset pointerStartLocation;
public readonly int pointerStartId;
public readonly Offset pointerEndLocation;
public readonly int pointerEndId;
}
public class ScaleGestureRecognizer : OneSequenceGestureRecognizer {
public ScaleGestureRecognizer(object debugOwner) : base(debugOwner: debugOwner) {
}
public GestureScaleStartCallback onStart;
public GestureScaleUpdateCallback onUpdate;
public GestureScaleEndCallback onEnd;
_ScaleState _state = _ScaleState.ready;
Offset _initialFocalPoint;
Offset _currentFocalPoint;
float _initialSpan;
float _currentSpan;
float _initialHorizontalSpan;
float _currentHorizontalSpan;
float _initialVerticalSpan;
float _currentVerticalSpan;
_LineBetweenPointers _initialLine;
_LineBetweenPointers _currentLine;
Dictionary<int, Offset> _pointerLocations;
List<int> _pointerQueue;
readonly Dictionary<int, VelocityTracker> _velocityTrackers = new Dictionary<int, VelocityTracker>();
float _scaleFactor {
get { return this._initialSpan > 0.0f ? this._currentSpan / this._initialSpan : 1.0f; }
}
float _horizontalScaleFactor {
get {
return this._initialHorizontalSpan > 0.0f
? this._currentHorizontalSpan / this._initialHorizontalSpan
: 1.0f;
}
}
float _verticalScaleFactor {
get {
return this._initialVerticalSpan > 0.0f ? this._currentVerticalSpan / this._initialVerticalSpan : 1.0f;
}
}
float _computeRotationFactor() {
if (this._initialLine == null || this._currentLine == null) {
return 0.0f;
}
float fx = this._initialLine.pointerStartLocation.dx;
float fy = this._initialLine.pointerStartLocation.dy;
float sx = this._initialLine.pointerEndLocation.dx;
float sy = this._initialLine.pointerEndLocation.dy;
float nfx = this._currentLine.pointerStartLocation.dx;
float nfy = this._currentLine.pointerStartLocation.dy;
float nsx = this._currentLine.pointerEndLocation.dx;
float nsy = this._currentLine.pointerEndLocation.dy;
float angle1 = Mathf.Atan2(fy - sy, fx - sx);
float angle2 = Mathf.Atan2(nfy - nsy, nfx - nsx);
return angle2 - angle1;
}
public override void addAllowedPointer(PointerDownEvent evt) {
this.startTrackingPointer(evt.pointer);
this._velocityTrackers[evt.pointer] = new VelocityTracker();
if (this._state == _ScaleState.ready) {
this._state = _ScaleState.possible;
this._initialSpan = 0.0f;
this._currentSpan = 0.0f;
this._initialHorizontalSpan = 0.0f;
this._currentHorizontalSpan = 0.0f;
this._initialVerticalSpan = 0.0f;
this._currentVerticalSpan = 0.0f;
this._pointerLocations = new Dictionary<int, Offset>();
this._pointerQueue = new List<int>();
}
}
protected override void handleEvent(PointerEvent evt) {
D.assert(this._state != _ScaleState.ready);
bool didChangeConfiguration = false;
bool shouldStartIfAccepted = false;
if (evt is PointerMoveEvent) {
VelocityTracker tracker = this._velocityTrackers[evt.pointer];
D.assert(tracker != null);
if (!evt.synthesized) {
tracker.addPosition(evt.timeStamp, evt.position);
}
this._pointerLocations[evt.pointer] = evt.position;
shouldStartIfAccepted = true;
}
else if (evt is PointerDownEvent) {
this._pointerLocations[evt.pointer] = evt.position;
this._pointerQueue.Add(evt.pointer);
didChangeConfiguration = true;
shouldStartIfAccepted = true;
}
else if (evt is PointerUpEvent || evt is PointerCancelEvent) {
this._pointerLocations.Remove(evt.pointer);
this._pointerQueue.Remove(evt.pointer);
didChangeConfiguration = true;
}
this._updateLines();
this._update();
if (!didChangeConfiguration || this._reconfigure(evt.pointer)) {
this._advanceStateMachine(shouldStartIfAccepted);
}
this.stopTrackingIfPointerNoLongerDown(evt);
}
void _update() {
int count = this._pointerLocations.Keys.Count;
Offset focalPoint = Offset.zero;
foreach (int pointer in this._pointerLocations.Keys) {
focalPoint += this._pointerLocations[pointer];
}
this._currentFocalPoint = count > 0 ? focalPoint / count : Offset.zero;
float totalDeviation = 0.0f;
float totalHorizontalDeviation = 0.0f;
float totalVerticalDeviation = 0.0f;
foreach (int pointer in this._pointerLocations.Keys) {
totalDeviation += (this._currentFocalPoint - this._pointerLocations[pointer]).distance;
totalHorizontalDeviation += (this._currentFocalPoint.dx - this._pointerLocations[pointer].dx).abs();
totalVerticalDeviation += (this._currentFocalPoint.dy - this._pointerLocations[pointer].dy).abs();
}
this._currentSpan = count > 0 ? totalDeviation / count : 0.0f;
this._currentHorizontalSpan = count > 0 ? totalHorizontalDeviation / count : 0.0f;
this._currentVerticalSpan = count > 0 ? totalVerticalDeviation / count : 0.0f;
}
void _updateLines() {
int count = this._pointerLocations.Keys.Count;
D.assert(this._pointerQueue.Count >= count);
if (count < 2) {
this._initialLine = this._currentLine;
}
else if (this._initialLine != null &&
this._initialLine.pointerStartId == this._pointerQueue[0] &&
this._initialLine.pointerEndId == this._pointerQueue[1]) {
this._currentLine = new _LineBetweenPointers(
pointerStartId: this._pointerQueue[0],
pointerStartLocation: this._pointerLocations[this._pointerQueue[0]],
pointerEndId: this._pointerQueue[1],
pointerEndLocation: this._pointerLocations[this._pointerQueue[1]]
);
}
else {
this._initialLine = new _LineBetweenPointers(
pointerStartId: this._pointerQueue[0],
pointerStartLocation: this._pointerLocations[this._pointerQueue[0]],
pointerEndId: this._pointerQueue[1],
pointerEndLocation: this._pointerLocations[this._pointerQueue[1]]
);
this._currentLine = null;
}
}
bool _reconfigure(int pointer) {
this._initialFocalPoint = this._currentFocalPoint;
this._initialSpan = this._currentSpan;
this._initialLine = this._currentLine;
this._initialHorizontalSpan = this._currentHorizontalSpan;
this._initialVerticalSpan = this._currentVerticalSpan;
if (this._state == _ScaleState.started) {
if (this.onEnd != null) {
VelocityTracker tracker = this._velocityTrackers[pointer];
D.assert(tracker != null);
Velocity velocity = tracker.getVelocity();
if (_ScaleGestureUtils._isFlingGesture(velocity)) {
Offset pixelsPerSecond = velocity.pixelsPerSecond;
if (pixelsPerSecond.distanceSquared >
Constants.kMaxFlingVelocity * Constants.kMaxFlingVelocity) {
velocity = new Velocity(
pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) *
Constants.kMaxFlingVelocity);
}
this.invokeCallback<object>("onEnd", () => {
this.onEnd(new ScaleEndDetails(velocity: velocity));
return null;
});
}
else {
this.invokeCallback<object>("onEnd", () => {
this.onEnd(new ScaleEndDetails(velocity: Velocity.zero));
return null;
});
}
}
this._state = _ScaleState.accepted;
return false;
}
return true;
}
void _advanceStateMachine(bool shouldStartIfAccepted) {
if (this._state == _ScaleState.ready) {
this._state = _ScaleState.possible;
}
if (this._state == _ScaleState.possible) {
float spanDelta = (this._currentSpan - this._initialSpan).abs();
float focalPointDelta = (this._currentFocalPoint - this._initialFocalPoint).distance;
if (spanDelta > Constants.kScaleSlop || focalPointDelta > Constants.kPanSlop) {
this.resolve(GestureDisposition.accepted);
}
}
else if (this._state >= _ScaleState.accepted) {
this.resolve(GestureDisposition.accepted);
}
if (this._state == _ScaleState.accepted && shouldStartIfAccepted) {
this._state = _ScaleState.started;
this._dispatchOnStartCallbackIfNeeded();
}
if (this._state == _ScaleState.started && this.onUpdate != null) {
this.invokeCallback<object>("onUpdate", () => {
this.onUpdate(new ScaleUpdateDetails(
scale: this._scaleFactor,
horizontalScale: this._horizontalScaleFactor,
verticalScale: this._verticalScaleFactor,
focalPoint: this._currentFocalPoint,
rotation: this._computeRotationFactor()
));
return null;
});
}
}
void _dispatchOnStartCallbackIfNeeded() {
D.assert(this._state == _ScaleState.started);
if (this.onStart != null) {
this.invokeCallback<object>("onStart", () => {
this.onStart(new ScaleStartDetails(focalPoint: this._currentFocalPoint));
return null;
});
}
}
public override void acceptGesture(int pointer) {
if (this._state == _ScaleState.possible) {
this._state = _ScaleState.started;
this._dispatchOnStartCallbackIfNeeded();
}
}
public override void rejectGesture(int pointer) {
this.stopTrackingPointer(pointer);
}
protected override void didStopTrackingLastPointer(int pointer) {
switch (this._state) {
case _ScaleState.possible:
this.resolve(GestureDisposition.rejected);
break;
case _ScaleState.ready:
D.assert(false);
break;
case _ScaleState.accepted:
break;
case _ScaleState.started:
D.assert(false);
break;
}
this._state = _ScaleState.ready;
}
public override void dispose() {
this._velocityTrackers.Clear();
base.dispose();
}
public override string debugDescription {
get { return "scale"; }
}
}
}

11
Runtime/gestures/scale.cs.meta


fileFormatVersion: 2
guid: 6408adf8aeb2a4ac38ee9f49dc748c2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

64
Samples/UIWidgetSample/ScaleGestureSample.cs


using System.Collections.Generic;
using Unity.UIWidgets.material;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
namespace UIWidgetsSample {
public class ScaleGestureSample : UIWidgetsSamplePanel {
protected override Widget createWidget() {
return new MaterialApp(
showPerformanceOverlay: false,
home: new ScaleGesturePanel()
);
}
protected override void OnEnable() {
FontManager.instance.addFont(Resources.Load<Font>(path: "MaterialIcons-Regular"), "Material Icons");
base.OnEnable();
}
}
class ScaleGesturePanel : StatefulWidget {
public override State createState() {
return new ScaleGesturePanelState();
}
}
class ScaleGesturePanelState : State<ScaleGesturePanel> {
float scaleValue = 1.0f;
public override Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Center(
child: new Text("Test Scale Gesture Widget")
)
),
body: new GestureDetector(
onScaleStart: scaleDetails => { Debug.Log("Scale Start !"); },
onScaleUpdate: scaleDetails => {
Debug.Log("Scale value = " + scaleDetails.scale);
this.setState(() => { this.scaleValue = scaleDetails.scale; });
},
onScaleEnd: scaleDetails => { Debug.Log("Scale End"); },
child: new Card(
color: Colors.white,
child: new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: new List<Widget> {
new Icon(Unity.UIWidgets.material.Icons.ac_unit, size: 128.0f, color: Colors.black),
new RaisedButton(
child: new Text("Scale: " + this.scaleValue),
onPressed: () => { Debug.Log("Button Pressed"); })
}
)
))
)
);
}
}
}

11
Samples/UIWidgetSample/ScaleGestureSample.cs.meta


fileFormatVersion: 2
guid: 38800d11bb7a9447582e892145b3bd82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存