您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
343 行
14 KiB
343 行
14 KiB
using System.Collections.Generic;
|
|
using Unity.UIWidgets.animation;
|
|
using Unity.UIWidgets.async;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.scheduler;
|
|
using Unity.UIWidgets.ui;
|
|
using Unity.UIWidgets.widgets;
|
|
using UnityEngine;
|
|
using Color = Unity.UIWidgets.ui.Color;
|
|
|
|
namespace Unity.UIWidgets.cupertino {
|
|
class _CupertinoSliverRefresh : SingleChildRenderObjectWidget {
|
|
public _CupertinoSliverRefresh(
|
|
Key key = null,
|
|
float refreshIndicatorLayoutExtent = 0.0f,
|
|
bool hasLayoutExtent = false,
|
|
Widget child = null
|
|
) : base(key: key, child: child) {
|
|
D.assert(refreshIndicatorLayoutExtent != null);
|
|
D.assert(refreshIndicatorLayoutExtent >= 0.0);
|
|
D.assert(hasLayoutExtent != null);
|
|
this.refreshIndicatorLayoutExtent = refreshIndicatorLayoutExtent;
|
|
this.hasLayoutExtent = hasLayoutExtent;
|
|
}
|
|
public readonly float refreshIndicatorLayoutExtent;
|
|
public readonly bool hasLayoutExtent;
|
|
|
|
public override RenderObject createRenderObject(BuildContext context) {
|
|
return new _RenderCupertinoSliverRefresh(
|
|
refreshIndicatorExtent: refreshIndicatorLayoutExtent,
|
|
hasLayoutExtent: hasLayoutExtent
|
|
);
|
|
}
|
|
public override void updateRenderObject(BuildContext context, RenderObject renderObject) {
|
|
renderObject = (_RenderCupertinoSliverRefresh)renderObject;
|
|
((_RenderCupertinoSliverRefresh) renderObject).refreshIndicatorLayoutExtent = refreshIndicatorLayoutExtent;
|
|
((_RenderCupertinoSliverRefresh) renderObject).hasLayoutExtent = hasLayoutExtent;
|
|
}
|
|
}
|
|
class _RenderCupertinoSliverRefresh : RenderObjectWithChildMixinRenderSliver<RenderBox> {
|
|
public _RenderCupertinoSliverRefresh(
|
|
float refreshIndicatorExtent,
|
|
bool hasLayoutExtent = false,
|
|
RenderBox child = null) {
|
|
|
|
D.assert(refreshIndicatorExtent >= 0.0f);
|
|
_refreshIndicatorExtent = refreshIndicatorExtent;
|
|
_hasLayoutExtent = hasLayoutExtent;
|
|
this.child = child;
|
|
}
|
|
|
|
|
|
public float refreshIndicatorLayoutExtent {
|
|
get { return _refreshIndicatorExtent; }
|
|
set {
|
|
D.assert(value != null);
|
|
D.assert(value >= 0.0);
|
|
if (value == _refreshIndicatorExtent)
|
|
return;
|
|
_refreshIndicatorExtent = value;
|
|
markNeedsLayout();
|
|
}
|
|
}
|
|
|
|
float _refreshIndicatorExtent;
|
|
|
|
public bool hasLayoutExtent {
|
|
get { return _hasLayoutExtent; }
|
|
set {
|
|
D.assert(value != null);
|
|
if (value == _hasLayoutExtent)
|
|
return;
|
|
_hasLayoutExtent = value;
|
|
markNeedsLayout();
|
|
}
|
|
}
|
|
|
|
bool _hasLayoutExtent;
|
|
float layoutExtentOffsetCompensation = 0.0f;
|
|
|
|
protected override void performLayout() {
|
|
SliverConstraints constraints = this.constraints;
|
|
D.assert(constraints.axisDirection == AxisDirection.down);
|
|
D.assert(constraints.growthDirection == GrowthDirection.forward);
|
|
float layoutExtent =
|
|
(_hasLayoutExtent ? 1.0f : 0.0f) * _refreshIndicatorExtent;
|
|
if (layoutExtent != layoutExtentOffsetCompensation) {
|
|
geometry = new SliverGeometry(
|
|
scrollOffsetCorrection: layoutExtent - layoutExtentOffsetCompensation
|
|
);
|
|
layoutExtentOffsetCompensation = layoutExtent;
|
|
|
|
}
|
|
bool active = constraints.overlap < 0.0 || layoutExtent > 0.0;
|
|
float overscrolledExtent =
|
|
constraints.overlap < 0.0f ? constraints.overlap.abs() : 0.0f;
|
|
child.layout(
|
|
constraints.asBoxConstraints(
|
|
maxExtent: layoutExtent + overscrolledExtent
|
|
),
|
|
parentUsesSize: true
|
|
);
|
|
if (active) {
|
|
geometry = new SliverGeometry(
|
|
scrollExtent: layoutExtent,
|
|
paintOrigin: -overscrolledExtent - constraints.scrollOffset,
|
|
paintExtent: Mathf.Max(
|
|
Mathf.Max(child.size.height, layoutExtent) - constraints.scrollOffset, 0.0f),
|
|
maxPaintExtent: Mathf.Max(
|
|
Mathf.Max(child.size.height, layoutExtent) - constraints.scrollOffset, 0.0f),
|
|
layoutExtent: Mathf.Max(layoutExtent - constraints.scrollOffset, 0.0f));
|
|
} else {
|
|
geometry = SliverGeometry.zero;
|
|
}
|
|
}
|
|
public override void paint(PaintingContext paintContext, Offset offset) {
|
|
if (constraints.overlap < 0.0 || constraints.scrollOffset + child.size.height > 0) {
|
|
paintContext.paintChild(child, offset);
|
|
}
|
|
}
|
|
public override void applyPaintTransform(RenderObject child, Matrix4 transform) { }
|
|
}
|
|
|
|
public enum RefreshIndicatorMode {
|
|
/// Initial state, when not being overscrolled into, or after the overscroll
|
|
/// is canceled or after done and the sliver retracted away.
|
|
inactive,
|
|
|
|
/// While being overscrolled but not far enough yet to trigger the refresh.
|
|
drag,
|
|
|
|
/// Dragged far enough that the onRefresh callback will run and the dragged
|
|
/// displacement is not yet at the final refresh resting state.
|
|
armed,
|
|
|
|
/// While the onRefresh task is running.
|
|
refresh,
|
|
|
|
/// While the indicator is animating away after refreshing.
|
|
done,
|
|
}
|
|
public delegate Widget RefreshControlIndicatorBuilder(
|
|
BuildContext context,
|
|
RefreshIndicatorMode refreshState,
|
|
float pulledExtent,
|
|
float refreshTriggerPullDistance,
|
|
float refreshIndicatorExtent
|
|
);
|
|
|
|
public delegate Future RefreshCallback();
|
|
|
|
public class CupertinoSliverRefreshControl : StatefulWidget {
|
|
public CupertinoSliverRefreshControl(
|
|
Key key = null,
|
|
float refreshTriggerPullDistance = _defaultRefreshTriggerPullDistance,
|
|
float refreshIndicatorExtent = _defaultRefreshIndicatorExtent,
|
|
RefreshControlIndicatorBuilder builder = null,
|
|
RefreshCallback onRefresh = null
|
|
) : base(key: key) {
|
|
D.assert(refreshTriggerPullDistance != null);
|
|
D.assert(refreshTriggerPullDistance > 0.0);
|
|
D.assert(refreshIndicatorExtent != null);
|
|
D.assert(refreshIndicatorExtent >= 0.0);
|
|
D.assert(
|
|
refreshTriggerPullDistance >= refreshIndicatorExtent, () =>
|
|
"The refresh indicator cannot take more space in its final state " +
|
|
"than the amount initially created by overscrolling."
|
|
);
|
|
this.refreshIndicatorExtent = refreshIndicatorExtent;
|
|
this.refreshTriggerPullDistance = refreshTriggerPullDistance;
|
|
this.builder = builder ?? buildSimpleRefreshIndicator;
|
|
this.onRefresh = onRefresh;
|
|
|
|
|
|
}
|
|
public readonly float refreshTriggerPullDistance;
|
|
public readonly float refreshIndicatorExtent;
|
|
public readonly RefreshControlIndicatorBuilder builder;
|
|
public readonly RefreshCallback onRefresh;
|
|
const float _defaultRefreshTriggerPullDistance = 100.0f;
|
|
const float _defaultRefreshIndicatorExtent = 60.0f;
|
|
|
|
static RefreshIndicatorMode state(BuildContext context) {
|
|
_CupertinoSliverRefreshControlState state = context.findAncestorStateOfType<_CupertinoSliverRefreshControlState>();
|
|
return state.refreshState;
|
|
}
|
|
public static Widget buildSimpleRefreshIndicator(
|
|
BuildContext context,
|
|
RefreshIndicatorMode refreshState,
|
|
float pulledExtent,
|
|
float refreshTriggerPullDistance,
|
|
float refreshIndicatorExtent
|
|
) {
|
|
Curve opacityCurve = new Interval(0.4f, 0.8f, curve: Curves.easeInOut);
|
|
|
|
return new Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: new Padding(
|
|
padding: EdgeInsets.only(bottom: 16.0f),
|
|
child: refreshState == RefreshIndicatorMode.drag
|
|
? new Opacity(
|
|
opacity: opacityCurve.transform(Mathf.Min(pulledExtent / refreshTriggerPullDistance, 1.0f)
|
|
),
|
|
child: new Icon(
|
|
CupertinoIcons.down_arrow,
|
|
color: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
|
|
size: 36.0f
|
|
)
|
|
)
|
|
: new Opacity(
|
|
opacity: opacityCurve.transform(
|
|
Mathf.Min(pulledExtent / refreshIndicatorExtent, 1.0f)
|
|
),
|
|
child: new CupertinoActivityIndicator(radius: 14.0f)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
public override State createState() {
|
|
return new _CupertinoSliverRefreshControlState();
|
|
|
|
}
|
|
}
|
|
public class _CupertinoSliverRefreshControlState : State<CupertinoSliverRefreshControl> {
|
|
public const float _inactiveResetOverscrollFraction = 0.1f;
|
|
public RefreshIndicatorMode refreshState;
|
|
Future refreshTask;
|
|
float latestIndicatorBoxExtent = 0.0f;
|
|
bool hasSliverLayoutExtent = false;
|
|
public override void initState() {
|
|
base.initState();
|
|
refreshState = RefreshIndicatorMode.inactive;
|
|
}
|
|
RefreshIndicatorMode transitionNextState() {
|
|
RefreshIndicatorMode nextState = RefreshIndicatorMode.refresh;
|
|
|
|
void goToDone() {
|
|
nextState = RefreshIndicatorMode.done;
|
|
|
|
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
|
|
setState(() => hasSliverLayoutExtent = false);
|
|
} else {
|
|
SchedulerBinding.instance.addPostFrameCallback((timestamp)=> {
|
|
setState(() => hasSliverLayoutExtent = false);
|
|
});
|
|
}
|
|
}
|
|
switch (refreshState) {
|
|
case RefreshIndicatorMode.inactive:
|
|
if (latestIndicatorBoxExtent <= 0) {
|
|
return RefreshIndicatorMode.inactive;
|
|
} else {
|
|
nextState = RefreshIndicatorMode.drag;
|
|
}
|
|
|
|
goto case RefreshIndicatorMode.drag;
|
|
|
|
case RefreshIndicatorMode.drag:
|
|
if (latestIndicatorBoxExtent == 0) {
|
|
return RefreshIndicatorMode.inactive;
|
|
} else if (latestIndicatorBoxExtent < widget.refreshTriggerPullDistance) {
|
|
return RefreshIndicatorMode.drag;
|
|
} else {
|
|
if (widget.onRefresh != null) { //HapticFeedback.mediumImpact();
|
|
|
|
SchedulerBinding.instance.addPostFrameCallback((timestamp)=> {
|
|
refreshTask = widget.onRefresh().whenComplete(() =>{
|
|
if (mounted) {
|
|
setState(() => refreshTask = null);
|
|
|
|
refreshState = transitionNextState();
|
|
}
|
|
});
|
|
setState(() => hasSliverLayoutExtent = true);
|
|
});
|
|
}
|
|
return RefreshIndicatorMode.armed;
|
|
}
|
|
|
|
break;
|
|
case RefreshIndicatorMode.armed:
|
|
if (refreshState == RefreshIndicatorMode.armed && refreshTask == null) {
|
|
goToDone();
|
|
goto case RefreshIndicatorMode.done;
|
|
}
|
|
|
|
if (latestIndicatorBoxExtent > widget.refreshIndicatorExtent) {
|
|
return RefreshIndicatorMode.armed;
|
|
} else {
|
|
nextState = RefreshIndicatorMode.refresh;
|
|
}
|
|
|
|
goto case RefreshIndicatorMode.refresh;
|
|
|
|
case RefreshIndicatorMode.refresh:
|
|
if (refreshTask != null) {
|
|
return RefreshIndicatorMode.refresh;
|
|
} else {
|
|
goToDone();
|
|
}
|
|
|
|
goto case RefreshIndicatorMode.done;
|
|
|
|
case RefreshIndicatorMode.done:
|
|
|
|
if (latestIndicatorBoxExtent >
|
|
widget.refreshTriggerPullDistance * _inactiveResetOverscrollFraction) {
|
|
return RefreshIndicatorMode.done;
|
|
} else {
|
|
nextState = RefreshIndicatorMode.inactive;
|
|
}
|
|
break;
|
|
}
|
|
return nextState;
|
|
}
|
|
public override Widget build(BuildContext context) {
|
|
return new _CupertinoSliverRefresh(
|
|
refreshIndicatorLayoutExtent: widget.refreshIndicatorExtent,
|
|
hasLayoutExtent: hasSliverLayoutExtent,
|
|
|
|
child: new LayoutBuilder(
|
|
builder: (BuildContext context1, BoxConstraints constraints) =>{
|
|
latestIndicatorBoxExtent = constraints.maxHeight;
|
|
refreshState = transitionNextState();
|
|
if (widget.builder != null && latestIndicatorBoxExtent > 0) {
|
|
return widget.builder(
|
|
context1,
|
|
refreshState,
|
|
latestIndicatorBoxExtent,
|
|
widget.refreshTriggerPullDistance,
|
|
widget.refreshIndicatorExtent
|
|
);
|
|
}
|
|
return new Container();
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|