浏览代码

Implement BottomNavigationBar.

/main
Yuncong Zhang 6 年前
当前提交
d923f0c4
共有 5 个文件被更改,包括 733 次插入465 次删除
  1. 947
      Runtime/material/bottom_navigation_bar.cs
  2. 2
      Runtime/material/theme.cs
  3. 18
      Samples/UIWidgetsGallery/gallery/demos.cs
  4. 228
      Samples/UIWidgetsGallery/demo/material/bottom_navigation_demo.cs
  5. 3
      Samples/UIWidgetsGallery/demo/material/bottom_navigation_demo.cs.meta

947
Runtime/material/bottom_navigation_bar.cs


using Unity.UIWidgets.gestures;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.scheduler;
using Unity.UIWidgets.service;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;

}
public enum BottomNavigationBarType {
fix,
shifting
fix,
shifting
BottomNavigationBar(
Key key = null,
List<BottomNavigationBarItem> items = null,
ValueChanged<int> onTap = null,
int currentIndex = 0,
BottomNavigationBarType? type = null,
Color fixedColor = null,
float iconSize = 24.0f
) : base(key: key) {
D.assert(items != null);
D.assert(items.Count >= 2);
D.assert( items.All((BottomNavigationBarItem item) => item.title != null) == true,
"Every item must have a non-null title"
);
D.assert(0 <= currentIndex && currentIndex < items.Count);
this.items = items;
this.onTap = onTap;
this.currentIndex = currentIndex;
this.type = type ?? (items.Count <= 3 ? BottomNavigationBarType.fix : BottomNavigationBarType.shifting);
this.fixedColor = fixedColor;
this.iconSize = iconSize;
}
public BottomNavigationBar(
Key key = null,
List<BottomNavigationBarItem> items = null,
ValueChanged<int> onTap = null,
int currentIndex = 0,
BottomNavigationBarType? type = null,
Color fixedColor = null,
float iconSize = 24.0f
) : base(key: key) {
D.assert(items != null);
D.assert(items.Count >= 2);
D.assert(items.All((BottomNavigationBarItem item) => item.title != null) == true,
"Every item must have a non-null title"
);
D.assert(0 <= currentIndex && currentIndex < items.Count);
this.items = items;
this.onTap = onTap;
this.currentIndex = currentIndex;
this.type = type ?? (items.Count <= 3 ? BottomNavigationBarType.fix : BottomNavigationBarType.shifting);
this.fixedColor = fixedColor;
this.iconSize = iconSize;
}
public readonly List<BottomNavigationBarItem> items;
public readonly List<BottomNavigationBarItem> items;
public readonly ValueChanged<int> onTap;
public readonly ValueChanged<int> onTap;
public readonly int currentIndex;
public readonly int currentIndex;
public readonly BottomNavigationBarType? type;
public readonly BottomNavigationBarType? type;
public readonly Color fixedColor;
public readonly Color fixedColor;
public readonly float iconSize;
public readonly float iconSize;
public override State createState() => new _BottomNavigationBarState();
public override State createState() {
return new _BottomNavigationBarState();
}
public _BottomNavigationTile(
BottomNavigationBarType type,
BottomNavigationBarItem item,
Animation<float> animation,
float? iconSize = null,
VoidCallback onTap = null,
ColorTween colorTween = null,
float? flex = null,
bool selected = false,
string indexLabel = null
) {
public _BottomNavigationTile(
BottomNavigationBarType? type,
BottomNavigationBarItem item,
Animation<float> animation,
float? iconSize = null,
VoidCallback onTap = null,
ColorTween colorTween = null,
float? flex = null,
bool selected = false,
string indexLabel = null
) {
this.type = type;
this.item = item;
this.animation = animation;
this.iconSize = iconSize;
this.onTap = onTap;
this.colorTween = colorTween;
this.flex = flex;
this.selected = selected;
this.indexLabel = indexLabel;
this.type = type;
this.item = item;
this.animation = animation;
this.iconSize = iconSize;
this.onTap = onTap;
this.colorTween = colorTween;
this.flex = flex;
this.selected = selected;
this.indexLabel = indexLabel;
public readonly BottomNavigationBarType type;
public readonly BottomNavigationBarItem item;
public readonly Animation<float> animation;
public readonly float? iconSize;
public readonly VoidCallback onTap;
public readonly ColorTween colorTween;
public readonly float? flex;
public readonly bool selected;
public readonly string indexLabel;
public readonly BottomNavigationBarType? type;
public readonly BottomNavigationBarItem item;
public readonly Animation<float> animation;
public readonly float? iconSize;
public readonly VoidCallback onTap;
public readonly ColorTween colorTween;
public readonly float? flex;
public readonly bool selected;
public readonly string indexLabel;
Widget _buildIcon() {
float tweenStart;
Color iconColor;
switch (this.type) {
case BottomNavigationBarType.fix:
tweenStart = 8.0f;
iconColor = this.colorTween.evaluate(this.animation);
break;
case BottomNavigationBarType.shifting:
tweenStart = 16.0f;
iconColor = Colors.white;
break;
default:
throw new Exception("Unknown BottomNavigationBarType: " + this.type);
}
Widget _buildIcon() {
float tweenStart;
Color iconColor;
switch (this.type) {
case BottomNavigationBarType.fix:
tweenStart = 8.0f;
iconColor = this.colorTween.evaluate(this.animation);
break;
case BottomNavigationBarType.shifting:
tweenStart = 16.0f;
iconColor = Colors.white;
break;
default:
throw new Exception("Unknown BottomNavigationBarType: " + this.type);
return new Align(
alignment: Alignment.topCenter,
heightFactor: 1.0f,
child: new Container(
margin: EdgeInsets.only(
top: new FloatTween(
begin: tweenStart,
end: BottomNavigationBarUtils._kTopMargin
).evaluate(this.animation)
),
child: new IconTheme(
data: new IconThemeData(
color: iconColor,
size: this.iconSize
),
child: this.selected ? this.item.activeIcon : this.item.icon
)
)
);
return new Align(
alignment: Alignment.topCenter,
heightFactor: 1.0f,
child: new Container(
margin: EdgeInsets.only(
top: new FloatTween(
begin: tweenStart,
end: BottomNavigationBarUtils._kTopMargin
).evaluate(this.animation)
),
child: new IconTheme(
data: new IconThemeData(
color: iconColor,
size: this.iconSize
),
child: this.selected ? this.item.activeIcon : this.item.icon
)
)
);
}
Widget _buildFixedLabel() {
float t = new FloatTween(
begin: BottomNavigationBarUtils._kInactiveFontSize / BottomNavigationBarUtils._kActiveFontSize,
end: 1.0f
).evaluate(this.animation);
return new Align(
alignment: Alignment.bottomCenter,
heightFactor: 1.0f,
child: new Container(
margin: EdgeInsets.only(bottom: BottomNavigationBarUtils._kBottomMargin),
child: DefaultTextStyle.merge(
style: new TextStyle(
fontSize: BottomNavigationBarUtils._kActiveFontSize,
color: this.colorTween.evaluate(this.animation)
),
child: new Transform(
transform: Matrix3.makeAll(t,0,0,0,t,0,0,0,t),
Widget _buildFixedLabel() {
float t = new FloatTween(
begin: BottomNavigationBarUtils._kInactiveFontSize / BottomNavigationBarUtils._kActiveFontSize,
end: 1.0f
).evaluate(this.animation);
return new Align(
child: this.item.title
)
)
)
);
}
Widget _buildShiftingLabel() {
return new Align(
alignment: Alignment.bottomCenter,
heightFactor: 1.0f,
child: new Container(
margin: EdgeInsets.only(
bottom: new FloatTween(
begin: 2.0f,
end: BottomNavigationBarUtils._kBottomMargin
).evaluate(this.animation)
),
child: new FadeTransition(
opacity: this.animation,
child: DefaultTextStyle.merge(
style: new TextStyle(
fontSize: BottomNavigationBarUtils._kActiveFontSize,
color: Colors.white
),
child: this.item.title
)
)
)
);
}
heightFactor: 1.0f,
child: new Container(
margin: EdgeInsets.only(bottom: BottomNavigationBarUtils._kBottomMargin),
child: DefaultTextStyle.merge(
style: new TextStyle(
fontSize: BottomNavigationBarUtils._kActiveFontSize,
color: this.colorTween.evaluate(this.animation)
),
child: new Transform(
transform: Matrix3.makeAll(t, 0, 0, 0, t, 0, 0, 0, t),
alignment: Alignment.bottomCenter,
child: this.item.title
)
)
)
);
}
public override Widget build(BuildContext context) {
int size;
Widget label;
switch (this.type) {
case BottomNavigationBarType.fix:
size = 1;
label = this._buildFixedLabel();
break;
case BottomNavigationBarType.shifting:
size = ((this.flex * 1000.0f) ?? 0.0f).round();
label = this._buildShiftingLabel();
break;
default:
throw new Exception("Unknown BottomNavigationBarType: " + this.type);
Widget _buildShiftingLabel() {
return new Align(
alignment: Alignment.bottomCenter,
heightFactor: 1.0f,
child: new Container(
margin: EdgeInsets.only(
bottom: new FloatTween(
begin: 2.0f,
end: BottomNavigationBarUtils._kBottomMargin
).evaluate(this.animation)
),
child: new FadeTransition(
opacity: this.animation,
child: DefaultTextStyle.merge(
style: new TextStyle(
fontSize: BottomNavigationBarUtils._kActiveFontSize,
color: Colors.white
),
child: this.item.title
)
)
)
);
return new Expanded(
flex: size,
child: new Stack(
children: new List<Widget>{
new InkResponse(
onTap: this.onTap == null ? (GestureTapCallback) null : () => {this.onTap();},
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: new List<Widget>{this._buildIcon(), label}
)
public override Widget build(BuildContext context) {
int size;
Widget label;
switch (this.type) {
case BottomNavigationBarType.fix:
size = 1;
label = this._buildFixedLabel();
break;
case BottomNavigationBarType.shifting:
size = ((this.flex * 1000.0f) ?? 0.0f).round();
label = this._buildShiftingLabel();
break;
default:
throw new Exception("Unknown BottomNavigationBarType: " + this.type);
}
return new Expanded(
flex: size,
child: new Stack(
children: new List<Widget> {
new InkResponse(
onTap: this.onTap == null ? (GestureTapCallback) null : () => { this.onTap(); },
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: new List<Widget> {this._buildIcon(), label}
)
)
}
}
)
);
}
);
}
List<AnimationController> _controllers = new List<AnimationController>{};
List<CurvedAnimation> _animations;
public List<AnimationController> _controllers = new List<AnimationController> { };
public List<CurvedAnimation> _animations;
Queue<_Circle> _circles = new Queue<_Circle>();
Color _backgroundColor;
Queue<_Circle> _circles = new Queue<_Circle>();
static readonly Animatable<float> _flexTween = new FloatTween(begin: 1.0f, end: 1.5f);
Color _backgroundColor;
public _BottomNavigationBarState() {
}
static readonly Animatable<float> _flexTween = new FloatTween(begin: 1.0f, end: 1.5f);
public _BottomNavigationBarState() {}
void _resetState() {
foreach (AnimationController controller in this._controllers) {
controller.dispose();
}
void _resetState() {
foreach (AnimationController controller in this._controllers)
controller.dispose();
foreach (_Circle circle in this._circles)
circle.dispose();
this._circles.Clear();
foreach (_Circle circle in this._circles) {
circle.dispose();
}
this._controllers = List<AnimationController>.generate(this.widget.items.Count, (int index) => {
AnimationController controller = new AnimationController(
duration: ThemeUtils.kThemeAnimationDuration,
vsync: this
);
controller.addListener(this._rebuild);
});
this._animations = List<CurvedAnimation>.generate(this.widget.items.Count, (int index) => {
return new CurvedAnimation(
parent: this._controllers[index],
curve: Curves.fastOutSlowIn,
reverseCurve: Curves.fastOutSlowIn.flipped
);
});
this._controllers[this.widget.currentIndex].setValue(1.0f);
this._backgroundColor = this.widget.items[this.widget.currentIndex].backgroundColor;
}
this._circles.Clear();
public override void initState() {
base.initState();
this._resetState();
}
this._controllers = new List<AnimationController>(capacity: this.widget.items.Count);
for (int index = 0; index < this.widget.items.Count; index++) {
AnimationController controller = new AnimationController(
duration: ThemeUtils.kThemeAnimationDuration,
vsync: this
);
controller.addListener(this._rebuild);
this._controllers.Add(controller);
}
void _rebuild() {
this.setState(() => {
});
}
this._animations = new List<CurvedAnimation>(capacity: this.widget.items.Count);
for (int index = 0; index < this.widget.items.Count; index++) {
this._animations.Add(new CurvedAnimation(
parent: this._controllers[index],
curve: Curves.fastOutSlowIn,
reverseCurve: Curves.fastOutSlowIn.flipped
));
}
public override void dispose() {
foreach (AnimationController controller in this._controllers)
controller.dispose();
foreach (_Circle circle in this._circles)
circle.dispose();
base.dispose();
}
this._controllers[this.widget.currentIndex].setValue(1.0f);
this._backgroundColor = this.widget.items[this.widget.currentIndex].backgroundColor;
}
float _evaluateFlex(Animation<float> animation) => _flexTween.evaluate(animation);
public override void initState() {
base.initState();
this._resetState();
}
void _pushCircle(int index) {
if (this.widget.items[index].backgroundColor != null) {
this._circles.Add(
new _Circle(
state: this,
index: index,
color: this.widget.items[index].backgroundColor,
vsync: this
)..controller.addStatusListener(
(AnimationStatus status) => {
switch (status) {
case AnimationStatus.completed:
this.setState(() => {
_Circle circle = this._circles.RemoveFirst();
this._backgroundColor = circle.color;
circle.dispose();
});
break;
case AnimationStatus.dismissed:
case AnimationStatus.forward:
case AnimationStatus.reverse:
break;
}
}
)
);
void _rebuild() {
this.setState(() => { });
}
public override void didUpdateWidget(BottomNavigationBar oldWidget) {
base.didUpdateWidget(oldWidget);
public override void dispose() {
foreach (AnimationController controller in this._controllers) {
controller.dispose();
}
foreach (_Circle circle in this._circles) {
circle.dispose();
}
if (this.widget.items.length != oldWidget.items.Count) {
this._resetState();
return;
base.dispose();
if (this.widget.currentIndex != oldWidget.currentIndex) {
switch (this.widget.type) {
case BottomNavigationBarType.fix:
break;
case BottomNavigationBarType.shifting:
this._pushCircle(this.widget.currentIndex);
break;
}
public float _evaluateFlex(Animation<float> animation) {
return _flexTween.evaluate(animation);
}
this._controllers[oldWidget.currentIndex].reverse();
this._controllers[this.widget.currentIndex].forward();
} else {
if (this._backgroundColor != this.widget.items[this.widget.currentIndex].backgroundColor) this._backgroundColor = this.widget.items[this.widget.currentIndex].backgroundColor;
void _pushCircle(int index) {
if (this.widget.items[index].backgroundColor != null) {
_Circle circle = new _Circle(
state: this,
index: index,
color: this.widget.items[index].backgroundColor,
vsync: this
);
circle.controller.addStatusListener(
(AnimationStatus status) => {
switch (status) {
case AnimationStatus.completed:
this.setState(() => {
_Circle cir = this._circles.Dequeue();
this._backgroundColor = cir.color;
cir.dispose();
});
break;
case AnimationStatus.dismissed:
case AnimationStatus.forward:
case AnimationStatus.reverse:
break;
}
}
);
this._circles.Enqueue(circle);
}
}
List<Widget> _createTiles() {
MaterialLocalizations localizations = MaterialLocalizations.of(this.context);
D.assert(localizations != null);
List<Widget> children = new List<Widget> {};
switch (this.widget.type) {
case BottomNavigationBarType.fix:
ThemeData themeData = Theme.of(this.context);
TextTheme textTheme = themeData.textTheme;
Color themeColor;
switch (themeData.brightness) {
case Brightness.light:
themeColor = themeData.primaryColor;
break;
case Brightness.dark:
themeColor = themeData.accentColor;
break;
default:
throw new Exception("Unknown brightness: " + themeData.brightness);
public override void didUpdateWidget(StatefulWidget _oldWidget) {
base.didUpdateWidget(_oldWidget);
BottomNavigationBar oldWidget = _oldWidget as BottomNavigationBar;
if (this.widget.items.Count != oldWidget.items.Count) {
this._resetState();
return;
ColorTween colorTween = new ColorTween(
begin: textTheme.caption.color,
end: this.widget.fixedColor ?? themeColor
);
for (int i = 0; i < this.widget.items.Count; i += 1) {
children.Add(
new _BottomNavigationTile(this.widget.type, this.widget.items[i], this._animations[i], this.widget.iconSize,
onTap: () => {
if (this.widget.onTap != null) this.widget.onTap(i);
},
colorTween: colorTween,
selected: i == this.widget.currentIndex,
indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: this.widget.items.Count)
)
);
if (this.widget.currentIndex != oldWidget.currentIndex) {
switch (this.widget.type) {
case BottomNavigationBarType.fix:
break;
case BottomNavigationBarType.shifting:
this._pushCircle(this.widget.currentIndex);
break;
}
this._controllers[oldWidget.currentIndex].reverse();
this._controllers[this.widget.currentIndex].forward();
break;
case BottomNavigationBarType.shifting:
for (int i = 0; i < this.widget.items.Count; i += 1) {
children.Add(
new _BottomNavigationTile(this.widget.type, this.widget.items[i], this._animations[i], this.widget.iconSize,
onTap: () => {
if (this.widget.onTap != null) this.widget.onTap(i);
},
flex:
this._evaluateFlex(this._animations[i]),
selected: i == this.widget.currentIndex,
indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: this.widget.items.length)
)
);
else {
if (this._backgroundColor != this.widget.items[this.widget.currentIndex].backgroundColor) {
this._backgroundColor = this.widget.items[this.widget.currentIndex].backgroundColor;
}
break;
return children;
}
List<Widget> _createTiles() {
MaterialLocalizations localizations = MaterialLocalizations.of(this.context);
D.assert(localizations != null);
List<Widget> children = new List<Widget> { };
switch (this.widget.type) {
case BottomNavigationBarType.fix:
ThemeData themeData = Theme.of(this.context);
TextTheme textTheme = themeData.textTheme;
Color themeColor;
switch (themeData.brightness) {
case Brightness.light:
themeColor = themeData.primaryColor;
break;
case Brightness.dark:
themeColor = themeData.accentColor;
break;
default:
throw new Exception("Unknown brightness: " + themeData.brightness);
}
Widget _createContainer(List<Widget> tiles) {
return DefaultTextStyle.merge(
overflow: TextOverflow.ellipsis,
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: tiles
)
);
}
ColorTween colorTween = new ColorTween(
begin: textTheme.caption.color,
end: this.widget.fixedColor ?? themeColor
);
for (int i = 0; i < this.widget.items.Count; i += 1) {
int index = i;
children.Add(
new _BottomNavigationTile(this.widget.type, this.widget.items[i], this._animations[i],
this.widget.iconSize,
onTap: () => {
if (this.widget.onTap != null) {
this.widget.onTap(index);
}
},
colorTween: colorTween,
selected: i == this.widget.currentIndex,
indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: this.widget.items.Count)
)
);
}
public override Widget build(BuildContext context) {
D.assert(debugCheckHasDirectionality(context));
D.assert(debugCheckHasMaterialLocalizations(context));
break;
case BottomNavigationBarType.shifting:
for (int i = 0; i < this.widget.items.Count; i += 1) {
int index = i;
children.Add(
new _BottomNavigationTile(this.widget.type, this.widget.items[i], this._animations[i],
this.widget.iconSize,
onTap: () => {
if (this.widget.onTap != null) {
this.widget.onTap(index);
}
},
flex:
this._evaluateFlex(this._animations[i]),
selected: i == this.widget.currentIndex,
indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: this.widget.items.Count)
)
);
}
float additionalBottomPadding = Mathf.Max(MediaQuery.of(context).padding.bottom - BottomNavigationBarUtils._kBottomMargin, 0.0f);
Color backgroundColor;
switch (this.widget.type) {
case BottomNavigationBarType.fix:
break;
case BottomNavigationBarType.shifting:
backgroundColor = this._backgroundColor;
break;
break;
}
return children;
return new Semantics(
container: true,
explicitChildNodes: true,
child: new Stack(
children: new List<Widget>[
Positioned.fill(
child: new Material( // Casts shadow.
elevation: 8.0f,
color: backgroundColor
Widget _createContainer(List<Widget> tiles) {
return DefaultTextStyle.merge(
overflow: TextOverflow.ellipsis,
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: tiles
),
ConstrainedBox(
constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
child: new Stack(
children: new List<Widget>[
);
}
public override Widget build(BuildContext context) {
D.assert(WidgetsD.debugCheckHasDirectionality(context));
D.assert(MaterialD.debugCheckHasMaterialLocalizations(context));
float additionalBottomPadding =
Mathf.Max(MediaQuery.of(context).padding.bottom - BottomNavigationBarUtils._kBottomMargin, 0.0f);
Color backgroundColor = null;
switch (this.widget.type) {
case BottomNavigationBarType.fix:
break;
case BottomNavigationBarType.shifting:
backgroundColor = this._backgroundColor;
break;
}
return new Stack(
children: new List<Widget> {
child: new CustomPaint(
painter: _RadialPainter(
circles: this._circles.ToList(),
textDirection: Directionality.of(context)
child: new Material( // Casts shadow.
elevation: 8.0f,
color: backgroundColor
)
Material( // Splashes.
type: MaterialType.transparency,
child: new Padding(
padding: EdgeInsets.only(bottom: additionalBottomPadding),
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: new _createContainer(this._createTiles())
new ConstrainedBox(
constraints: new BoxConstraints(
minHeight: Constants.kBottomNavigationBarHeight + additionalBottomPadding),
child: new Stack(
children: new List<Widget> {
Positioned.fill(
child: new CustomPaint(
painter: new _RadialPainter(
circles: this._circles.ToList()
)
)
),
new Material( // Splashes.
type: MaterialType.transparency,
child: new Padding(
padding: EdgeInsets.only(bottom: additionalBottomPadding),
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: this._createContainer(this._createTiles())
)
)
)
}
)
]
)
)
]
)
);
}
}
);
}
_Circle({
@required this.state,
@required this.index,
@required this.color,
@required TickerProvider vsync
) : D.assert(this.state != null),
D.assert(this.index != null),
D.assert(this.color != null) {
this.controller = AnimationController(
duration: kThemeAnimationDuration,
vsync: vsync
);
this.animation = CurvedAnimation(
parent: this.controller,
curve: Curves.fastOutSlowIn
);
this.controller.forward();
}
public _Circle(
_BottomNavigationBarState state = null,
int? index = null,
Color color = null,
TickerProvider vsync = null
) {
D.assert(state != null);
D.assert(index != null);
D.assert(color != null);
this.state = state;
this.index = index;
this.color = color;
this.controller = new AnimationController(
duration: ThemeUtils.kThemeAnimationDuration,
vsync: vsync
);
this.animation = new CurvedAnimation(
parent: this.controller,
curve: Curves.fastOutSlowIn
);
this.controller.forward();
}
public readonly _BottomNavigationBarState state;
public readonly int index;
public readonly Color color;
AnimationController controller;
CurvedAnimation animation;
public readonly _BottomNavigationBarState state;
public readonly int? index;
public readonly Color color;
public readonly AnimationController controller;
public readonly CurvedAnimation animation;
float get horizontalLeadingOffset {
float weightSum(Iterable<Animation<float>> animations) {
return animations.map<float>(this.state._evaluateFlex).fold<float>(0.0f, (float sum, float value) => sum + value);
}
public float horizontalLeadingOffset {
get {
float weightSum(IEnumerable<Animation<float>> animations) {
return animations.Select(this.state._evaluateFlex).Sum();
}
float allWeights = weightSum(this.state._animations);
float leadingWeights = weightSum(this.state._animations.sublist(0, this.index));
float allWeights = weightSum(this.state._animations);
float leadingWeights = weightSum(this.state._animations.GetRange(0, this.index ?? 0));
return (leadingWeights + this.state._evaluateFlex(this.state._animations[this.index]) / 2.0f) / allWeights;
}
return (leadingWeights + this.state._evaluateFlex(this.state._animations[this.index ?? 0]) / 2.0f) /
allWeights;
}
}
public void dispose() {
this.controller.dispose();
}
public void dispose() {
this.controller.dispose();
}
class _RadialPainter : CustomPainter {
_RadialPainter({
@required this.circles,
@required this.textDirection
) : D.assert(this.circles != null),
D.assert(this.textDirection != null);
class _RadialPainter : AbstractCustomPainter {
public _RadialPainter(
List<_Circle> circles
) {
D.assert(circles != null);
this.circles = circles;
}
public readonly List<_Circle> circles;
public readonly TextDirection textDirection;
public readonly List<_Circle> circles;
static float _maxRadius(Offset center, Size size) {
float maxX = Mathf.Max(center.dx, size.width - center.dx);
float maxY = Mathf.Max(center.dy, size.height - center.dy);
return Mathf.Sqrt(maxX * maxX + maxY * maxY);
}
static float _maxRadius(Offset center, Size size) {
float maxX = Mathf.Max(center.dx, size.width - center.dx);
float maxY = Mathf.Max(center.dy, size.height - center.dy);
return Mathf.Sqrt(maxX * maxX + maxY * maxY);
}
public override bool shouldRepaint(_RadialPainter oldPainter) {
if (this.textDirection != oldPainter.textDirection)
return true;
if (this.circles == oldPainter.circles)
return false;
if (this.circles.length != oldPainter.circles.length)
return true;
for (int i = 0; i < this.circles.length; i += 1)
if (this.circles[i] != oldPainter.circles[i])
return true;
return false;
}
public override bool shouldRepaint(CustomPainter _oldPainter) {
_RadialPainter oldPainter = _oldPainter as _RadialPainter;
if (this.circles == oldPainter.circles) {
return false;
}
public override void paint(Canvas canvas, Size size) {
for (_Circle circle in
this.circles) {
Paint paint = Paint()..color = circle.color;
Rect rect = Rect.fromLTWH(0.0f, 0.0f, size.width, size.height);
canvas.clipRect(rect);
float leftFraction;
switch (this.textDirection) {
case TextDirection.rtl:
leftFraction = 1.0f - circle.horizontalLeadingOffset;
break;
case TextDirection.ltr:
leftFraction = circle.horizontalLeadingOffset;
break;
}
Offset center = Offset(leftFraction * size.width, size.height / 2.0f);
FloatTween radiusTween = FloatTween(
begin: 0.0f,
end: _maxRadius(center, size)
);
canvas.drawCircle(
center,
radiusTween.transform(circle.animation.value),
paint
);
if (this.circles.Count != oldPainter.circles.Count) {
return true;
}
for (int i = 0; i < this.circles.Count; i += 1) {
if (this.circles[i] != oldPainter.circles[i]) {
return true;
}
}
return false;
}
public override void paint(Canvas canvas, Size size) {
foreach (_Circle circle in this.circles) {
Paint paint = new Paint();
paint.color = circle.color;
Rect rect = Rect.fromLTWH(0.0f, 0.0f, size.width, size.height);
canvas.clipRect(rect);
float leftFraction = circle.horizontalLeadingOffset;
Offset center = new Offset(leftFraction * size.width, size.height / 2.0f);
FloatTween radiusTween = new FloatTween(
begin: 0.0f,
end: _maxRadius(center, size)
);
canvas.drawCircle(
center,
radiusTween.evaluate(circle.animation),
paint
);
}
}
}
}

2
Runtime/material/theme.cs


using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.material {
static class ThemeUtils {
public static class ThemeUtils {
public static readonly TimeSpan kThemeAnimationDuration = new TimeSpan(0, 0, 0, 0, 200);
}

18
Samples/UIWidgetsGallery/gallery/demos.cs


documentationUrl: "https://docs.flutter.io/flutter/material/BottomAppBar-class.html",
buildRoute: (BuildContext context) => new BottomAppBarDemo()
),
// new GalleryDemo(
// title: "Bottom navigation",
// subtitle: "Bottom navigation with cross-fading views",
// icon: GalleryIcons.bottom_navigation,
// category: GalleryDemoCategory._kMaterialComponents,
// routeName: BottomNavigationDemo.routeName,
// documentationUrl: "https://docs.flutter.io/flutter/material/BottomNavigationBar-class.html",
// buildRoute: (BuildContext context) => BottomNavigationDemo()
// ),
new GalleryDemo(
title: "Bottom navigation",
subtitle: "Bottom navigation with cross-fading views",
icon: GalleryIcons.bottom_navigation,
category: DemoUtils._kMaterialComponents,
routeName: BottomNavigationDemo.routeName,
documentationUrl: "https://docs.flutter.io/flutter/material/BottomNavigationBar-class.html",
buildRoute: (BuildContext context) => new BottomNavigationDemo()
),
// new GalleryDemo(
// title: "Bottom sheet: Modal",
// subtitle: "A dismissable bottom sheet",

228
Samples/UIWidgetsGallery/demo/material/bottom_navigation_demo.cs


using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.scheduler;
using Unity.UIWidgets.service;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
namespace UIWidgetsGallery.gallery {
public class NavigationIconView {
public NavigationIconView(
Widget icon = null,
Widget activeIcon = null,
string title = null,
Color color = null,
TickerProvider vsync = null
) {
this._icon = icon;
this._color = color;
this._title = title;
this.item = new BottomNavigationBarItem(
icon: icon,
activeIcon: activeIcon,
title: new Text(title),
backgroundColor: color
);
this.controller = new AnimationController(
duration: ThemeUtils.kThemeAnimationDuration,
vsync: vsync
);
this._animation = this.controller.drive(new CurveTween(
curve: new Interval(0.5f, 1.0f, curve: Curves.fastOutSlowIn)
));
}
readonly Widget _icon;
readonly Color _color;
readonly string _title;
public readonly BottomNavigationBarItem item;
public readonly AnimationController controller;
Animation<float> _animation;
public FadeTransition transition(BottomNavigationBarType type, BuildContext context) {
Color iconColor;
if (type == BottomNavigationBarType.shifting) {
iconColor = this._color;
}
else {
ThemeData themeData = Theme.of(context);
iconColor = themeData.brightness == Brightness.light
? themeData.primaryColor
: themeData.accentColor;
}
return new FadeTransition(
opacity: this._animation,
child: new SlideTransition(
position: this._animation.drive(
new OffsetTween(
begin: new Offset(0.0f, 0.02f), // Slightly down.
end: Offset.zero
)
),
child: new IconTheme(
data: new IconThemeData(
color: iconColor,
size: 120.0f
),
child: this._icon
)
)
);
}
}
public class CustomIcon : StatelessWidget {
public override Widget build(BuildContext context) {
IconThemeData iconTheme = IconTheme.of(context);
return new Container(
margin: EdgeInsets.all(4.0f),
width: iconTheme.size - 8.0f,
height: iconTheme.size - 8.0f,
color: iconTheme.color
);
}
}
public class CustomInactiveIcon : StatelessWidget {
public override Widget build(BuildContext context) {
IconThemeData iconTheme = IconTheme.of(context);
return new Container(
margin: EdgeInsets.all(4.0f),
width: iconTheme.size - 8.0f,
height: iconTheme.size - 8.0f,
decoration: new BoxDecoration(
border: Border.all(color: iconTheme.color, width: 2.0f)
)
);
}
}
public class BottomNavigationDemo : StatefulWidget {
public const string routeName = "/material/bottom_navigation";
public override State createState() {
return new _BottomNavigationDemoState();
}
}
class _BottomNavigationDemoState : TickerProviderStateMixin<BottomNavigationDemo> {
int _currentIndex = 0;
BottomNavigationBarType _type = BottomNavigationBarType.shifting;
List<NavigationIconView> _navigationViews;
public override void initState() {
base.initState();
this._navigationViews = new List<NavigationIconView> {
new NavigationIconView(
icon: new Icon(Icons.access_alarm),
title: "Alarm",
color: Colors.deepPurple,
vsync: this
),
new NavigationIconView(
activeIcon: new CustomIcon(),
icon: new CustomInactiveIcon(),
title: "Box",
color: Colors.deepOrange,
vsync: this
),
new NavigationIconView(
activeIcon: new Icon(Icons.cloud),
icon: new Icon(Icons.cloud_queue),
title: "Cloud",
color: Colors.teal,
vsync: this
),
new NavigationIconView(
activeIcon: new Icon(Icons.favorite),
icon: new Icon(Icons.favorite_border),
title: "Favorites",
color: Colors.indigo,
vsync: this
),
new NavigationIconView(
icon: new Icon(Icons.event_available),
title: "Event",
color: Colors.pink,
vsync: this
)
};
this._navigationViews[this._currentIndex].controller.setValue(1.0f);
}
public override void dispose() {
foreach (NavigationIconView view in this._navigationViews) {
view.controller.dispose();
}
base.dispose();
}
Widget _buildTransitionsStack() {
List<FadeTransition> transitions = new List<FadeTransition> { };
foreach (NavigationIconView view in this._navigationViews) {
transitions.Add(view.transition(this._type, this.context));
}
transitions.Sort((FadeTransition a, FadeTransition b) => {
Animation<float> aAnimation = a.opacity;
Animation<float> bAnimation = b.opacity;
float aValue = aAnimation.value;
float bValue = bAnimation.value;
return aValue.CompareTo(bValue);
});
return new Stack(children: transitions.Select<FadeTransition, Widget>(w => w).ToList());
}
public override Widget build(BuildContext context) {
BottomNavigationBar botNavBar = new BottomNavigationBar(
items: this._navigationViews.Select((NavigationIconView navigationView) => navigationView.item)
.ToList(),
currentIndex: this._currentIndex,
type: this._type,
onTap: (int index) => {
this.setState(() => {
this._navigationViews[this._currentIndex].controller.reverse();
this._currentIndex = index;
this._navigationViews[this._currentIndex].controller.forward();
});
}
);
return new Scaffold(
appBar: new AppBar(
title: new Text("Bottom navigation"),
actions: new List<Widget> {
new MaterialDemoDocumentationButton(BottomNavigationDemo.routeName),
new PopupMenuButton<BottomNavigationBarType>(
onSelected: (BottomNavigationBarType value) => {
this.setState(() => { this._type = value; });
},
itemBuilder: (BuildContext _context) => new List<PopupMenuEntry<BottomNavigationBarType>> {
new PopupMenuItem<BottomNavigationBarType>(
value: BottomNavigationBarType.fix,
child: new Text("Fixed")
),
new PopupMenuItem<BottomNavigationBarType>(
value: BottomNavigationBarType.shifting,
child: new Text("Shifting")
)
}
)
}
),
body: new Center(
child: this._buildTransitionsStack()
),
bottomNavigationBar: botNavBar
);
}
}
}

3
Samples/UIWidgetsGallery/demo/material/bottom_navigation_demo.cs.meta


fileFormatVersion: 2
guid: d279028b09c04c2b8ab3fba122868004
timeCreated: 1553221768
正在加载...
取消
保存