浏览代码

[Add Widget] stepper

/main
iizzaya 5 年前
当前提交
928efc84
共有 3 个文件被更改,包括 633 次插入0 次删除
  1. 2
      Runtime/widgets/framework.cs
  2. 620
      Runtime/material/stepper.cs
  3. 11
      Runtime/material/stepper.cs.meta

2
Runtime/widgets/framework.cs


public delegate Widget IndexedWidgetBuilder(BuildContext context, int index);
public delegate Widget TransitionBuilder(BuildContext context, Widget child);
public delegate Widget ControlsWidgetBuilder(BuildContext context, VoidCallback onStepContinue = null, VoidCallback onStepCancel = null);
public abstract class ComponentElement : Element {
protected ComponentElement(Widget widget) : base(widget) {

620
Runtime/material/stepper.cs


using System.Collections.Generic;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.gestures;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.service;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
namespace Unity.UIWidgets.material {
public enum StepState {
indexed,
editing,
complete,
disabled,
error
}
public enum StepperType {
vertical,
horizontal
}
public class Step {
public Step(
Widget title,
Widget content,
Widget subtitle = null,
StepState state = StepState.indexed,
bool isActive = false
) {
D.assert(title != null);
D.assert(content != null);
this.title = title;
this.content = content;
this.subtitle = subtitle;
this.state = state;
this.isActive = isActive;
}
public readonly Widget title;
public readonly Widget subtitle;
public readonly Widget content;
public readonly StepState state;
public readonly bool isActive;
}
public class Stepper : StatefulWidget {
public Stepper(
List<Step> steps,
Key key = null,
StepperType type = StepperType.vertical,
int currentStep = 0,
ValueChanged<int> onStepTapped = null,
VoidCallback onStepContinue = null,
VoidCallback onStepCancel = null,
ControlsWidgetBuilder controlsBuilder = null
) : base(key) {
this.steps = steps;
this.type = type;
this.currentStep = currentStep;
this.onStepTapped = onStepTapped;
this.onStepContinue = onStepContinue;
this.onStepCancel = onStepCancel;
this.controlsBuilder = controlsBuilder;
}
public readonly List<Step> steps;
public readonly StepperType type;
public readonly int currentStep;
public readonly ValueChanged<int> onStepTapped;
public readonly VoidCallback onStepContinue;
public readonly VoidCallback onStepCancel;
public readonly ControlsWidgetBuilder controlsBuilder;
public override State createState() {
return new _StepperState();
}
}
public class _StepperState : TickerProviderStateMixin<Stepper> {
readonly TextStyle _kStepStyleLight = new TextStyle(
fontSize: 12.0f,
color: Colors.white
);
readonly TextStyle _kStepStyleDark = new TextStyle(
fontSize: 12.0f,
color: Colors.black87
);
readonly Color _kErrorLight = Colors.red;
readonly Color _kErrorDark = Colors.red.shade400;
readonly Color _kCircleActiveLight = Colors.white;
readonly Color _kCircleActiveDark = Colors.black87;
readonly Color _kDisabledLight = Colors.black38;
readonly Color _kDisabledDark = Colors.white30;
readonly float _kStepSize = 24.0f;
readonly float _kTriangleHeight = 24.0f * 0.866025f;
List<GlobalKey> _keys;
public Dictionary<int, StepState> _oldStates = new Dictionary<int, StepState>();
public override void initState() {
base.initState();
this._keys = new List<GlobalKey>();
for (int i = 0; i < this.widget.steps.Count; i++) {
this._keys.Add(GlobalKey.key());
this._oldStates[i] = this.widget.steps[i].state;
}
}
public override void didUpdateWidget(StatefulWidget oldWidget) {
base.didUpdateWidget(oldWidget);
Stepper _oldWidget = (Stepper) oldWidget;
D.assert(this.widget.steps.Count == _oldWidget.steps.Count);
for (int i = 0; i < _oldWidget.steps.Count; i++) {
this._oldStates[i] = _oldWidget.steps[i].state;
}
}
bool _isFirst(int index) {
return index == 0;
}
bool _isLast(int index) {
return this.widget.steps.Count - 1 == index;
}
bool _isCurrent(int index) {
return this.widget.currentStep == index;
}
bool _isDark() {
return Theme.of(this.context).brightness == Brightness.dark;
}
Widget _buildLine(bool visible) {
return new Container(
width: visible ? 1.0f : 0.0f,
height: 16.0f,
color: Colors.grey.shade400
);
}
Widget _buildCircleChild(int index, bool oldState) {
StepState state = oldState ? this._oldStates[index] : this.widget.steps[index].state;
bool isDarkActive = this._isDark() && this.widget.steps[index].isActive;
switch (state) {
case StepState.indexed:
case StepState.disabled:
return new Text(
(index + 1).ToString(),
style: isDarkActive ? this._kStepStyleDark : this._kStepStyleLight
);
case StepState.editing:
return new Icon(
Icons.edit,
color: isDarkActive ? this._kCircleActiveDark : this._kCircleActiveLight,
size: 18.0f
);
case StepState.complete:
return new Icon(
Icons.check,
color: isDarkActive ? this._kCircleActiveDark : this._kCircleActiveLight,
size: 18.0f
);
case StepState.error:
return new Text(
"!",
style: this._kStepStyleLight
);
}
return null;
}
Color _circleColor(int index) {
ThemeData themeData = Theme.of(this.context);
if (this._isDark()) {
return this.widget.steps[index].isActive ? themeData.primaryColor : Colors.black38;
}
else {
return this.widget.steps[index].isActive ? themeData.accentColor : themeData.backgroundColor;
}
}
Widget _buildCircle(int index, bool oldState) {
return new Container(
margin: EdgeInsets.symmetric(vertical: 8.0f),
width: this._kStepSize,
height: this._kStepSize,
child: new AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: ThemeUtils.kThemeAnimationDuration,
decoration: new BoxDecoration(
color: this._circleColor(index),
shape: BoxShape.circle
),
child: new Center(
child: this._buildCircleChild(index,
oldState && this.widget.steps[index].state == StepState.error
)
)
)
);
}
Widget _buildTriangle(int index, bool oldState) {
return new Container(
margin: EdgeInsets.symmetric(vertical: 8.0f),
width: this._kStepSize,
height: this._kStepSize,
child: new Center(
child: new SizedBox(
width: this._kStepSize,
height: this._kTriangleHeight,
child: new CustomPaint(
painter: new _TrianglePainter(
color: this._isDark() ? this._kErrorDark : this._kErrorLight),
child: new Align(
alignment: new Alignment(0.0f, 0.8f),
child: this._buildCircleChild(index,
oldState && this.widget.steps[index].state != StepState.error)
)
)
)
)
);
}
Widget _buildIcon(int index) {
if (this.widget.steps[index].state != this._oldStates[index]) {
return new AnimatedCrossFade(
firstChild: this._buildCircle(index, true),
secondChild: this._buildTriangle(index, true),
firstCurve: new Interval(0.0f, 0.6f, curve: Curves.fastOutSlowIn),
secondCurve: new Interval(0.4f, 1.0f, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: this.widget.steps[index].state == StepState.error
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: ThemeUtils.kThemeAnimationDuration
);
}
else {
if (this.widget.steps[index].state != StepState.error) {
return this._buildCircle(index, false);
}
else {
return this._buildTriangle(index, false);
}
}
}
Widget _buildVerticalControls() {
if (this.widget.controlsBuilder != null) {
return this.widget.controlsBuilder(
this.context,
onStepContinue: this.widget.onStepContinue,
onStepCancel: this.widget.onStepCancel
);
}
Color cancelColor = null;
switch (Theme.of(this.context).brightness) {
case Brightness.light:
cancelColor = Colors.black54;
break;
case Brightness.dark:
cancelColor = Colors.white70;
break;
}
D.assert(cancelColor != null);
ThemeData themeData = Theme.of(this.context);
MaterialLocalizations localizations = MaterialLocalizations.of(this.context);
return new Container(
margin: EdgeInsets.only(top: 16.0f),
child: new ConstrainedBox(
constraints: BoxConstraints.tightFor(height: 48.0f),
child: new Row(
children: new List<Widget> {
new FlatButton(
onPressed: this.widget.onStepContinue,
color: this._isDark() ? themeData.backgroundColor : themeData.primaryColor,
textColor: Colors.white,
textTheme: ButtonTextTheme.normal,
child: new Text(localizations.continueButtonLabel)
),
new Container(
margin: EdgeInsets.only(8.0f), // EdgeInsetsDirection
child: new FlatButton(
onPressed: this.widget.onStepCancel,
textColor: cancelColor,
textTheme: ButtonTextTheme.normal,
child: new Text(localizations.cancelButtonLabel)
)
)
}
)
)
);
}
TextStyle _titleStyle(int index) {
ThemeData themeData = Theme.of(this.context);
TextTheme textTheme = themeData.textTheme;
switch (this.widget.steps[index].state) {
case StepState.indexed:
case StepState.editing:
case StepState.complete:
return textTheme.body2;
case StepState.disabled:
return textTheme.body2.copyWith(
color: this._isDark() ? this._kDisabledDark : this._kDisabledLight
);
case StepState.error:
return textTheme.body2.copyWith(
color: this._isDark() ? this._kErrorDark : this._kErrorLight
);
}
return null;
}
TextStyle _subTitleStyle(int index) {
ThemeData themeData = Theme.of(this.context);
TextTheme textTheme = themeData.textTheme;
switch (this.widget.steps[index].state) {
case StepState.indexed:
case StepState.editing:
case StepState.complete:
return textTheme.caption;
case StepState.disabled:
return textTheme.caption.copyWith(
color: this._isDark() ? this._kDisabledDark : this._kDisabledLight
);
case StepState.error:
return textTheme.caption.copyWith(
color: this._isDark() ? this._kErrorDark : this._kErrorLight
);
}
return null;
}
Widget _buildheaderText(int index) {
List<Widget> children = new List<Widget> {
new AnimatedDefaultTextStyle(
style: this._titleStyle(index),
duration: ThemeUtils.kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
child: this.widget.steps[index].title
)
};
if (this.widget.steps[index].subtitle != null) {
children.Add(
new Container(
margin: EdgeInsets.only(top: 2.0f),
child: new AnimatedDefaultTextStyle(
style: this._subTitleStyle(index),
duration: ThemeUtils.kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
child: this.widget.steps[index].subtitle
)
)
);
}
return new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: children
);
}
Widget _buildVerticalHeader(int index) {
return new Container(
margin: EdgeInsets.symmetric(horizontal: 24.0f),
child: new Row(
children: new List<Widget> {
new Column(
children: new List<Widget> {
this._buildLine(!this._isFirst(index)),
this._buildIcon(index),
this._buildLine(!this._isLast(index))
}
),
new Container(
margin: EdgeInsets.only(12.0f), // EdgeInsetsDirectional
child: this._buildheaderText(index)
)
}
)
);
}
Widget _buildVerticalBody(int index) {
return new Stack(
children: new List<Widget> {
new Positioned( // PositionedDirectional
left: 24.0f,
top: 0.0f,
bottom: 0.0f,
child: new SizedBox(
width: 24.0f,
child: new Center(
child: new SizedBox(
width: this._isLast(index) ? 0.0f : 1.0f,
child: new Container(
color: Colors.grey.shade400)
)
)
)
),
new AnimatedCrossFade(
firstChild: new Container(height: 0.0f),
secondChild: new Container(
margin: EdgeInsets.only(
left: 60.0f,
right: 24.0f,
bottom: 24.0f
),
child: new Column(
children: new List<Widget> {
this.widget.steps[index].content,
this._buildVerticalControls()
}
)
),
firstCurve: new Interval(0.0f, 0.6f, Curves.fastOutSlowIn),
secondCurve: new Interval(0.4f, 1.0f, Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: this._isCurrent(index) ? CrossFadeState.showSecond : CrossFadeState.showFirst,
duration: ThemeUtils.kThemeAnimationDuration
)
}
);
}
Widget _buildVertical() {
List<Widget> children = new List<Widget>();
for (int i = 0; i < this.widget.steps.Count; i++) {
int _i = i;
children.Add(
new Column(
key: this._keys[_i],
children: new List<Widget> {
new InkWell(
onTap: this.widget.steps[_i].state != StepState.disabled
? () => {
Scrollable.ensureVisible(
this._keys[_i].currentContext,
curve: Curves.fastOutSlowIn,
duration: ThemeUtils.kThemeAnimationDuration
);
if (this.widget.onStepTapped != null) {
this.widget.onStepTapped(_i);
}
}
: (GestureTapCallback) null,
child: this._buildVerticalHeader(_i)
),
this._buildVerticalBody(_i)
}
)
);
}
return new ListView(
shrinkWrap: true,
children: children
);
}
Widget _buildHorizontal() {
List<Widget> children = new List<Widget>();
for (int i = 0; i < this.widget.steps.Count; i++) {
int _i = i;
children.Add(
new InkResponse(
onTap: this.widget.steps[_i].state != StepState.disabled
? new GestureTapCallback(
() => {
if (this.widget.onStepTapped != null) {
this.widget.onStepTapped(_i);
}
})
: null,
child: new Row(
children: new List<Widget> {
new Container(
height: 72.0f,
child: new Center(child: this._buildIcon(_i))
),
new Container(
margin: EdgeInsets.only(left: 12.0f), // EdgeinsetsDirectional
child: this._buildheaderText(_i)
)
}
)
)
);
if (!this._isLast(_i)) {
children.Add(
new Expanded(
child: new Container(
margin: EdgeInsets.symmetric(horizontal: 8.0f),
height: 1.0f,
color: Colors.grey.shade400
)
)
);
}
}
return new Column(
children: new List<Widget> {
new Material(
elevation: 2.0f,
child: new Container(
margin: EdgeInsets.symmetric(horizontal: 24.0f),
child: new Row(children: children)
)
),
new Expanded(
child: new ListView(
padding: EdgeInsets.all(24.0f),
children: new List<Widget> {
new AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: ThemeUtils.kThemeAnimationDuration,
vsync: this,
child: this.widget.steps[this.widget.currentStep].content
),
this._buildVerticalControls()
}
)
)
}
);
}
public override Widget build(BuildContext context) {
D.assert(MaterialD.debugCheckHasMaterial(context));
D.assert(MaterialD.debugCheckHasMaterialLocalizations(context));
D.assert(() => {
if (context.ancestorWidgetOfExactType(typeof(Stepper)) != null) {
throw new UIWidgetsError(
"Steppers must not be nested. The material specification advises that one should avoid embedding steppers within steppers. "
);
}
return true;
});
switch (this.widget.type) {
case StepperType.vertical:
return this._buildVertical();
case StepperType.horizontal:
return this._buildHorizontal();
}
return new Text("Build Failed");
}
}
class _TrianglePainter : AbstractCustomPainter {
public _TrianglePainter(
Color color) {
this.color = color;
}
public readonly Color color;
public override bool? hitTest(Offset position) {
return true;
}
public override bool shouldRepaint(CustomPainter oldPainter) {
return ((_TrianglePainter) oldPainter).color != this.color;
}
public override void paint(Canvas canvas, Size size) {
float baseline = size.width;
float halfBase = size.width / 2.0f;
float height = size.height;
List<Offset> points = new List<Offset> {
new Offset(0.0f, height),
new Offset(baseline, height),
new Offset(halfBase, 0.0f)
};
Path newPath = new Path();
newPath.addPolygon(points, true);
Paint newPaint = new Paint();
newPaint.color = this.color;
canvas.drawPath(newPath, newPaint);
}
}
}

11
Runtime/material/stepper.cs.meta


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