您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
420 行
15 KiB
420 行
15 KiB
using System;
|
|
using System.Collections.Generic;
|
|
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;
|
|
using Color = Unity.UIWidgets.ui.Color;
|
|
using TextStyle = Unity.UIWidgets.painting.TextStyle;
|
|
|
|
namespace Unity.UIWidgets.material {
|
|
public class RawMaterialButton : StatefulWidget {
|
|
public RawMaterialButton(
|
|
Key key = null,
|
|
VoidCallback onPressed = null,
|
|
GestureLongPressCallback onLongPress = null,
|
|
ValueChanged<bool> onHighlightChanged = null,
|
|
TextStyle textStyle = null,
|
|
Color fillColor = null,
|
|
Color focusColor = null,
|
|
Color hoverColor = null,
|
|
Color highlightColor = null,
|
|
Color splashColor = null,
|
|
float elevation = 2.0f,
|
|
float focusElevation = 4.0f,
|
|
float hoverElevation = 4.0f,
|
|
float highlightElevation = 8.0f,
|
|
float disabledElevation = 0.0f,
|
|
EdgeInsetsGeometry padding = null,
|
|
VisualDensity visualDensity = null,
|
|
BoxConstraints constraints = null,
|
|
ShapeBorder shape = null,
|
|
TimeSpan? animationDuration = null,
|
|
Clip clipBehavior = Clip.none,
|
|
FocusNode focusNode = null,
|
|
bool autofocus = false,
|
|
MaterialTapTargetSize? materialTapTargetSize = null,
|
|
Widget child = null,
|
|
bool enableFeedback = true) : base(key: key) {
|
|
D.assert(elevation >= 0.0f);
|
|
D.assert(focusElevation >= 0.0f);
|
|
D.assert(focusElevation >= 0.0f);
|
|
D.assert(highlightElevation >= 0.0f);
|
|
D.assert(disabledElevation >= 0.0f);
|
|
|
|
MaterialTapTargetSize _materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded;
|
|
shape = shape ?? new RoundedRectangleBorder();
|
|
visualDensity = visualDensity ?? new VisualDensity();
|
|
padding = padding ?? EdgeInsets.zero;
|
|
constraints = constraints ?? new BoxConstraints(minWidth: 88.0f, minHeight: 36.0f);
|
|
TimeSpan _animationDuration = animationDuration ?? material_.kThemeChangeDuration;
|
|
|
|
this.onPressed = onPressed;
|
|
this.onLongPress = onLongPress;
|
|
this.onHighlightChanged = onHighlightChanged;
|
|
this.textStyle = textStyle;
|
|
this.fillColor = fillColor;
|
|
this.focusColor = focusColor;
|
|
this.hoverColor = hoverColor;
|
|
this.highlightColor = highlightColor;
|
|
this.splashColor = splashColor;
|
|
this.elevation = elevation;
|
|
this.focusElevation = focusElevation;
|
|
this.hoverElevation = hoverElevation;
|
|
this.highlightElevation = highlightElevation;
|
|
this.disabledElevation = disabledElevation;
|
|
this.padding = padding;
|
|
this.visualDensity = visualDensity;
|
|
this.constraints = constraints;
|
|
this.shape = shape;
|
|
this.animationDuration = _animationDuration;
|
|
this.clipBehavior = clipBehavior;
|
|
this.focusNode = focusNode;
|
|
this.autofocus = autofocus;
|
|
this.enableFeedback = enableFeedback;
|
|
this.materialTapTargetSize = _materialTapTargetSize;
|
|
this.child = child;
|
|
}
|
|
|
|
public readonly VoidCallback onPressed;
|
|
|
|
public readonly GestureLongPressCallback onLongPress;
|
|
|
|
public readonly ValueChanged<bool> onHighlightChanged;
|
|
|
|
public readonly TextStyle textStyle;
|
|
|
|
public readonly Color fillColor;
|
|
|
|
public readonly Color focusColor;
|
|
|
|
public readonly Color hoverColor;
|
|
|
|
public readonly Color highlightColor;
|
|
|
|
public readonly Color splashColor;
|
|
|
|
public readonly float elevation;
|
|
|
|
public readonly float hoverElevation;
|
|
|
|
public readonly float focusElevation;
|
|
|
|
public readonly float highlightElevation;
|
|
|
|
public readonly float disabledElevation;
|
|
|
|
public readonly EdgeInsetsGeometry padding;
|
|
|
|
public readonly VisualDensity visualDensity;
|
|
|
|
public readonly BoxConstraints constraints;
|
|
|
|
public readonly ShapeBorder shape;
|
|
|
|
public readonly TimeSpan animationDuration;
|
|
|
|
public readonly Widget child;
|
|
|
|
public bool enabled {
|
|
get { return onPressed != null || onLongPress != null; }
|
|
}
|
|
|
|
public readonly MaterialTapTargetSize materialTapTargetSize;
|
|
|
|
public readonly FocusNode focusNode;
|
|
|
|
public readonly bool autofocus;
|
|
|
|
public readonly Clip clipBehavior;
|
|
|
|
public readonly bool enableFeedback;
|
|
|
|
public override State createState() {
|
|
return new _RawMaterialButtonState();
|
|
}
|
|
}
|
|
|
|
|
|
class _RawMaterialButtonState : State<RawMaterialButton> {
|
|
readonly HashSet<MaterialState> _states = new HashSet<MaterialState>();
|
|
|
|
bool _hovered {
|
|
get { return _states.Contains(MaterialState.hovered); }
|
|
}
|
|
|
|
bool _focused {
|
|
get { return _states.Contains(MaterialState.focused); }
|
|
}
|
|
|
|
bool _pressed {
|
|
get { return _states.Contains(MaterialState.pressed); }
|
|
}
|
|
|
|
bool _disabled {
|
|
get { return _states.Contains(MaterialState.disabled); }
|
|
}
|
|
|
|
void _updateState(MaterialState state, bool value) {
|
|
if (value) {
|
|
_states.Add(state);
|
|
}
|
|
else {
|
|
_states.Remove(state);
|
|
}
|
|
}
|
|
|
|
void _handleHighlightChanged(bool value) {
|
|
if (_pressed != value) {
|
|
setState(() => {
|
|
_updateState(MaterialState.pressed, value);
|
|
if (widget.onHighlightChanged != null) {
|
|
widget.onHighlightChanged(value);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void _handleHoveredChanged(bool value) {
|
|
if (_hovered != value) {
|
|
setState(() => { _updateState(MaterialState.hovered, value); });
|
|
}
|
|
}
|
|
|
|
void _handleFocusedChanged(bool value) {
|
|
if (_focused != value) {
|
|
setState(() => { _updateState(MaterialState.focused, value); });
|
|
}
|
|
}
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
_updateState(MaterialState.disabled, !widget.enabled);
|
|
}
|
|
|
|
public override void didUpdateWidget(StatefulWidget _oldWidget) {
|
|
RawMaterialButton oldWidget = _oldWidget as RawMaterialButton;
|
|
base.didUpdateWidget(oldWidget);
|
|
_updateState(MaterialState.disabled, !widget.enabled);
|
|
if (_disabled && _pressed) {
|
|
_handleHighlightChanged(false);
|
|
}
|
|
}
|
|
|
|
float _effectiveElevation {
|
|
get {
|
|
if (_disabled) {
|
|
return widget.disabledElevation;
|
|
}
|
|
|
|
if (_pressed) {
|
|
return widget.highlightElevation;
|
|
}
|
|
|
|
if (_hovered) {
|
|
return widget.hoverElevation;
|
|
}
|
|
|
|
if (_focused) {
|
|
return widget.focusElevation;
|
|
}
|
|
|
|
return widget.elevation;
|
|
}
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
Color effectiveTextColor = MaterialStateProperty<Color>.resolveAs<Color>(widget.textStyle?.color, _states);
|
|
ShapeBorder effectiveShape = MaterialStateProperty<Color>.resolveAs<ShapeBorder>(widget.shape, _states);
|
|
Offset densityAdjustment = widget.visualDensity.baseSizeAdjustment;
|
|
BoxConstraints effectiveConstraints = widget.visualDensity.effectiveConstraints(widget.constraints);
|
|
EdgeInsetsGeometry padding = widget.padding.add(
|
|
EdgeInsets.only(
|
|
left: densityAdjustment.dx,
|
|
top: densityAdjustment.dy,
|
|
right: densityAdjustment.dx,
|
|
bottom: densityAdjustment.dy
|
|
)
|
|
).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity) as EdgeInsets;
|
|
|
|
Widget result = new ConstrainedBox(
|
|
constraints: effectiveConstraints,
|
|
child: new Material(
|
|
elevation: _effectiveElevation,
|
|
textStyle: widget.textStyle?.copyWith(color: effectiveTextColor),
|
|
shape: effectiveShape,
|
|
color: widget.fillColor,
|
|
type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
|
|
animationDuration: widget.animationDuration,
|
|
clipBehavior: widget.clipBehavior,
|
|
child: new InkWell(
|
|
focusNode: widget.focusNode,
|
|
canRequestFocus: widget.enabled,
|
|
onFocusChange: _handleFocusedChanged,
|
|
autofocus: widget.autofocus,
|
|
onHighlightChanged: _handleHighlightChanged,
|
|
splashColor: widget.splashColor,
|
|
highlightColor: widget.highlightColor,
|
|
focusColor: widget.focusColor,
|
|
hoverColor: widget.hoverColor,
|
|
onHover: _handleHoveredChanged,
|
|
onTap: widget.onPressed == null
|
|
? (GestureTapCallback) null
|
|
: () => {
|
|
if (widget.onPressed != null) {
|
|
widget.onPressed();
|
|
}
|
|
},
|
|
onLongPress: widget.onLongPress,
|
|
enableFeedback: widget.enableFeedback,
|
|
customBorder: effectiveShape,
|
|
child: IconTheme.merge(
|
|
data: new IconThemeData(color: effectiveTextColor),
|
|
child: new Container(
|
|
padding: padding,
|
|
child: new Center(
|
|
widthFactor: 1.0f,
|
|
heightFactor: 1.0f,
|
|
child: widget.child)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
Size minSize = null;
|
|
switch (widget.materialTapTargetSize) {
|
|
case MaterialTapTargetSize.padded:
|
|
minSize = new Size(
|
|
material_.kMinInteractiveDimension + densityAdjustment.dx,
|
|
material_.kMinInteractiveDimension + densityAdjustment.dy
|
|
);
|
|
D.assert(minSize.width >= 0.0f);
|
|
D.assert(minSize.height >= 0.0f);
|
|
break;
|
|
case MaterialTapTargetSize.shrinkWrap:
|
|
minSize = Size.zero;
|
|
break;
|
|
}
|
|
|
|
return new _InputPadding(
|
|
minSize: minSize,
|
|
child: result
|
|
);
|
|
}
|
|
}
|
|
|
|
class _InputPadding : SingleChildRenderObjectWidget {
|
|
public _InputPadding(
|
|
Key key = null,
|
|
Widget child = null,
|
|
Size minSize = null) : base(key: key, child: child) {
|
|
this.minSize = minSize;
|
|
}
|
|
|
|
public readonly Size minSize;
|
|
|
|
public override RenderObject createRenderObject(BuildContext context) {
|
|
return new _RenderInputPadding(minSize);
|
|
}
|
|
|
|
public override void updateRenderObject(BuildContext context, RenderObject renderObject) {
|
|
var _renderObject = (_RenderInputPadding) renderObject;
|
|
_renderObject.minSize = minSize;
|
|
}
|
|
}
|
|
|
|
class _RenderInputPadding : RenderShiftedBox {
|
|
public _RenderInputPadding(
|
|
Size minSize,
|
|
RenderBox child = null
|
|
) : base(child: child) {
|
|
_minSize = minSize;
|
|
}
|
|
|
|
public Size minSize {
|
|
get { return _minSize; }
|
|
set {
|
|
if (_minSize == value) {
|
|
return;
|
|
}
|
|
|
|
_minSize = value;
|
|
markNeedsLayout();
|
|
}
|
|
}
|
|
|
|
Size _minSize;
|
|
|
|
protected internal override float computeMinIntrinsicWidth(float height) {
|
|
if (child != null) {
|
|
return Mathf.Max(child.getMinIntrinsicWidth(height), minSize.width);
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
protected internal override float computeMinIntrinsicHeight(float width) {
|
|
if (child != null) {
|
|
return Mathf.Max(child.getMinIntrinsicHeight(width), minSize.height);
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
protected internal override float computeMaxIntrinsicWidth(float height) {
|
|
if (child != null) {
|
|
return Mathf.Max(child.getMaxIntrinsicWidth(height), minSize.width);
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
protected internal override float computeMaxIntrinsicHeight(float width) {
|
|
if (child != null) {
|
|
return Mathf.Max(child.getMaxIntrinsicHeight(width), minSize.height);
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
protected override void performLayout() {
|
|
BoxConstraints constraints = this.constraints;
|
|
if (child != null) {
|
|
child.layout(constraints, parentUsesSize: true);
|
|
float height = Mathf.Max(child.size.width, minSize.width);
|
|
float width = Mathf.Max(child.size.height, minSize.height);
|
|
size = constraints.constrain(new Size(height, width));
|
|
BoxParentData childParentData = child.parentData as BoxParentData;
|
|
childParentData.offset = Alignment.center.alongOffset(size - child.size as Offset);
|
|
}
|
|
else {
|
|
size = Size.zero;
|
|
}
|
|
}
|
|
|
|
public override bool hitTest(BoxHitTestResult result, Offset position = null) {
|
|
if (base.hitTest(result, position: position)) {
|
|
return true;
|
|
}
|
|
|
|
Offset center = child.size.center(Offset.zero);
|
|
return result.addWithRawTransform(
|
|
transform: MatrixUtils.forceToPoint(center),
|
|
position: center,
|
|
hitTest: (BoxHitTestResult boxHitTest, Offset offsetPosition) => {
|
|
D.assert(offsetPosition == center);
|
|
//WARNING: inconsistent with flutter (zxw): I believe that there is a bug here in flutter
|
|
//in flutter, the following line is "return child.hitTest(boxHitTest, position: center); ". This is nonsense since it will always return true regardless of the value of the input parameter: position.
|
|
//we have tested a bit in flutter and found that, since an inputPadding has a Semantics as it parent which shares the same size, the Semantics's hitTest can hide this bug in flutter
|
|
//Therefore this bug only occurs in UIWidgets
|
|
//We are not very clear whether this is the best fix though. Please feel free to optimize it
|
|
return child.hitTest(boxHitTest, position: position);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|