|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Linq; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using Unity.UIWidgets.animation; |
|
|
|
using Unity.UIWidgets.foundation; |
|
|
|
using Unity.UIWidgets.gestures; |
|
|
|
|
|
|
using Transform = Unity.UIWidgets.widgets.Transform; |
|
|
|
|
|
|
|
namespace Unity.UIWidgets.material { |
|
|
|
class BottomNavigationBarUtils { |
|
|
|
public const float _kActiveFontSize = 14.0f; |
|
|
|
public const float _kInactiveFontSize = 12.0f; |
|
|
|
public const float _kTopMargin = 6.0f; |
|
|
|
public const float _kBottomMargin = 8.0f; |
|
|
|
} |
|
|
|
|
|
|
|
public enum BottomNavigationBarType { |
|
|
|
fix, |
|
|
|
shifting |
|
|
|
|
|
|
List<BottomNavigationBarItem> items = null, |
|
|
|
ValueChanged<int> onTap = null, |
|
|
|
int currentIndex = 0, |
|
|
|
float elevation = 8.0f, |
|
|
|
float iconSize = 24.0f |
|
|
|
Color backgroundColor = null, |
|
|
|
float iconSize = 24.0f, |
|
|
|
Color selectedItemColor = null, |
|
|
|
Color unselectedItemColor = null, |
|
|
|
float selectedFontSize = 14.0f, |
|
|
|
float unselectedFontSize = 12.0f, |
|
|
|
bool showSelectedLabels = true, |
|
|
|
bool? showUnselectedLabels = null |
|
|
|
) : base(key: key) { |
|
|
|
D.assert(items != null); |
|
|
|
D.assert(items.Count >= 2); |
|
|
|
|
|
|
D.assert(0 <= currentIndex && currentIndex < items.Count); |
|
|
|
D.assert(elevation >= 0.0f); |
|
|
|
D.assert(iconSize >= 0.0f); |
|
|
|
D.assert(selectedItemColor == null || fixedColor == null, |
|
|
|
() => "Either selectedItemColor or fixedColor can be specified, but not both!"); |
|
|
|
D.assert(selectedFontSize >= 0.0f); |
|
|
|
D.assert(unselectedFontSize >= 0.0f); |
|
|
|
type = _type(type, items); |
|
|
|
this.elevation = elevation; |
|
|
|
this.fixedColor = fixedColor; |
|
|
|
this.backgroundColor = backgroundColor; |
|
|
|
this.selectedItemColor = selectedItemColor ?? fixedColor; |
|
|
|
this.unselectedItemColor = unselectedItemColor; |
|
|
|
this.selectedFontSize = selectedFontSize; |
|
|
|
this.unselectedFontSize = unselectedFontSize; |
|
|
|
this.showSelectedLabels = showSelectedLabels; |
|
|
|
this.showUnselectedLabels = showUnselectedLabels ?? _defaultShowUnselected(_type(type, items)); |
|
|
|
} |
|
|
|
|
|
|
|
public readonly List<BottomNavigationBarItem> items; |
|
|
|
|
|
|
public readonly int currentIndex; |
|
|
|
|
|
|
|
public readonly float elevation; |
|
|
|
|
|
|
|
public readonly Color fixedColor; |
|
|
|
public Color fixedColor { |
|
|
|
get { return this.selectedItemColor; } |
|
|
|
} |
|
|
|
|
|
|
|
public readonly Color backgroundColor; |
|
|
|
|
|
|
|
public readonly Color selectedItemColor; |
|
|
|
|
|
|
|
public readonly Color unselectedItemColor; |
|
|
|
|
|
|
|
public readonly float selectedFontSize; |
|
|
|
|
|
|
|
public readonly float unselectedFontSize; |
|
|
|
|
|
|
|
public readonly bool showUnselectedLabels; |
|
|
|
|
|
|
|
public readonly bool showSelectedLabels; |
|
|
|
|
|
|
|
static BottomNavigationBarType _type( |
|
|
|
BottomNavigationBarType? type, |
|
|
|
List<BottomNavigationBarItem> items |
|
|
|
) { |
|
|
|
if (type != null) { |
|
|
|
return type.Value; |
|
|
|
} |
|
|
|
|
|
|
|
return items.Count <= 3 ? BottomNavigationBarType.fix : BottomNavigationBarType.shifting; |
|
|
|
} |
|
|
|
|
|
|
|
static bool _defaultShowUnselected(BottomNavigationBarType type) { |
|
|
|
switch (type) { |
|
|
|
case BottomNavigationBarType.shifting: |
|
|
|
return false; |
|
|
|
case BottomNavigationBarType.fix: |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(false); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
public override State createState() { |
|
|
|
return new _BottomNavigationBarState(); |
|
|
|
} |
|
|
|
|
|
|
ColorTween colorTween = null, |
|
|
|
float? flex = null, |
|
|
|
bool selected = false, |
|
|
|
float? selectedFontSize = null, |
|
|
|
float? unselectedFontSize = null, |
|
|
|
bool? showSelectedLabels = null, |
|
|
|
bool? showUnselectedLabels = null, |
|
|
|
D.assert(type != null); |
|
|
|
D.assert(item != null); |
|
|
|
D.assert(animation != null); |
|
|
|
D.assert(selectedFontSize != null && selectedFontSize >= 0); |
|
|
|
D.assert(unselectedFontSize != null && unselectedFontSize >= 0); |
|
|
|
this.type = type; |
|
|
|
this.item = item; |
|
|
|
this.animation = animation; |
|
|
|
|
|
|
this.flex = flex; |
|
|
|
this.selected = selected; |
|
|
|
this.selectedFontSize = selectedFontSize.Value; |
|
|
|
this.unselectedFontSize = unselectedFontSize.Value; |
|
|
|
this.showSelectedLabels = showSelectedLabels ?? false; |
|
|
|
this.showUnselectedLabels = showUnselectedLabels ?? false; |
|
|
|
this.indexLabel = indexLabel; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
public readonly ColorTween colorTween; |
|
|
|
public readonly float? flex; |
|
|
|
public readonly bool selected; |
|
|
|
public readonly float selectedFontSize; |
|
|
|
public readonly float unselectedFontSize; |
|
|
|
public readonly bool showSelectedLabels; |
|
|
|
public readonly bool showUnselectedLabels; |
|
|
|
Widget label; |
|
|
|
float bottomPadding = this.selectedFontSize / 2.0f; |
|
|
|
float topPadding = this.selectedFontSize / 2.0f; |
|
|
|
if (this.showSelectedLabels && !this.showUnselectedLabels) { |
|
|
|
bottomPadding = new FloatTween(begin: 0.0f, end: this.selectedFontSize / 2.0f).evaluate(this.animation); |
|
|
|
topPadding = new FloatTween( |
|
|
|
begin: this.selectedFontSize, |
|
|
|
end: this.selectedFontSize / 2.0f |
|
|
|
).evaluate(this.animation); |
|
|
|
} |
|
|
|
|
|
|
|
if (!this.showSelectedLabels && !this.showUnselectedLabels) { |
|
|
|
bottomPadding = 0.0f; |
|
|
|
topPadding = this.selectedFontSize; |
|
|
|
} |
|
|
|
label = new _FixedLabel(colorTween: this.colorTween, animation: this.animation, item: this.item); |
|
|
|
label = new _ShiftingLabel(animation: this.animation, item: this.item); |
|
|
|
break; |
|
|
|
default: |
|
|
|
throw new Exception("Unknown BottomNavigationBarType: " + this.type); |
|
|
|
|
|
|
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> { |
|
|
|
new _TileIcon( |
|
|
|
type: this.type, |
|
|
|
colorTween: this.colorTween, |
|
|
|
animation: this.animation, |
|
|
|
iconSize: this.iconSize, |
|
|
|
selected: this.selected, |
|
|
|
item: this.item |
|
|
|
), |
|
|
|
label |
|
|
|
} |
|
|
|
child: new Padding( |
|
|
|
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding), |
|
|
|
child: new Column( |
|
|
|
crossAxisAlignment: CrossAxisAlignment.center, |
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
children: new List<Widget> { |
|
|
|
new _TileIcon( |
|
|
|
colorTween: this.colorTween, |
|
|
|
animation: this.animation, |
|
|
|
iconSize: this.iconSize, |
|
|
|
selected: this.selected, |
|
|
|
item: this.item |
|
|
|
), |
|
|
|
new _Label( |
|
|
|
colorTween: this.colorTween, |
|
|
|
animation: this.animation, |
|
|
|
item: this.item, |
|
|
|
selectedFontSize: this.selectedFontSize, |
|
|
|
unselectedFontSize: this.unselectedFontSize, |
|
|
|
showSelectedLabels: this.showSelectedLabels, |
|
|
|
showUnselectedLabels: this.showUnselectedLabels |
|
|
|
) |
|
|
|
} |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
class _TileIcon : StatelessWidget { |
|
|
|
public _TileIcon( |
|
|
|
Key key = null, |
|
|
|
BottomNavigationBarType? type = null, |
|
|
|
ColorTween colorTween = null, |
|
|
|
Animation<float> animation = null, |
|
|
|
float? iconSize = null, |
|
|
|
|
|
|
this.type = type; |
|
|
|
D.assert(selected != null); |
|
|
|
D.assert(item != null); |
|
|
|
this.colorTween = colorTween; |
|
|
|
this.animation = animation; |
|
|
|
this.iconSize = iconSize; |
|
|
|
|
|
|
|
|
|
|
BottomNavigationBarType? type; |
|
|
|
ColorTween colorTween; |
|
|
|
Animation<float> animation; |
|
|
|
float? iconSize; |
|
|
|
|
|
|
public override Widget build(BuildContext context) { |
|
|
|
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); |
|
|
|
} |
|
|
|
Color iconColor = this.colorTween.evaluate(this.animation); |
|
|
|
margin: EdgeInsets.only( |
|
|
|
top: new FloatTween( |
|
|
|
begin: tweenStart, |
|
|
|
end: BottomNavigationBarUtils._kTopMargin |
|
|
|
).evaluate(this.animation) |
|
|
|
), |
|
|
|
child: new IconTheme( |
|
|
|
data: new IconThemeData( |
|
|
|
color: iconColor, |
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class _FixedLabel : StatelessWidget { |
|
|
|
public _FixedLabel( |
|
|
|
class _Label : StatelessWidget { |
|
|
|
public _Label( |
|
|
|
BottomNavigationBarItem item = null |
|
|
|
BottomNavigationBarItem item = null, |
|
|
|
float? selectedFontSize = null, |
|
|
|
float? unselectedFontSize = null, |
|
|
|
bool? showSelectedLabels = null, |
|
|
|
bool? showUnselectedLabels = null |
|
|
|
D.assert(colorTween != null); |
|
|
|
D.assert(animation != null); |
|
|
|
D.assert(item != null); |
|
|
|
D.assert(selectedFontSize != null); |
|
|
|
D.assert(unselectedFontSize != null); |
|
|
|
D.assert(showSelectedLabels != null); |
|
|
|
D.assert(showUnselectedLabels != null); |
|
|
|
this.selectedFontSize = selectedFontSize.Value; |
|
|
|
this.unselectedFontSize = unselectedFontSize.Value; |
|
|
|
this.showSelectedLabels = showSelectedLabels.Value; |
|
|
|
this.showUnselectedLabels = showUnselectedLabels.Value; |
|
|
|
ColorTween colorTween; |
|
|
|
Animation<float> animation; |
|
|
|
BottomNavigationBarItem item; |
|
|
|
public readonly ColorTween colorTween; |
|
|
|
public readonly Animation<float> animation; |
|
|
|
public readonly BottomNavigationBarItem item; |
|
|
|
public readonly float selectedFontSize; |
|
|
|
public readonly float unselectedFontSize; |
|
|
|
public readonly bool showSelectedLabels; |
|
|
|
public readonly bool showUnselectedLabels; |
|
|
|
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.makeScale(t), |
|
|
|
alignment: Alignment.bottomCenter, |
|
|
|
child: this.item.title |
|
|
|
) |
|
|
|
) |
|
|
|
float t = new FloatTween(begin: this.unselectedFontSize / this.selectedFontSize, end: 1.0f) |
|
|
|
.evaluate(this.animation); |
|
|
|
Widget text = DefaultTextStyle.merge( |
|
|
|
style: new TextStyle( |
|
|
|
fontSize: this.selectedFontSize, |
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class _ShiftingLabel : StatelessWidget { |
|
|
|
public _ShiftingLabel( |
|
|
|
Key key = null, |
|
|
|
Animation<float> animation = null, |
|
|
|
BottomNavigationBarItem item = null |
|
|
|
) : base(key: key) { |
|
|
|
this.animation = animation; |
|
|
|
this.item = item; |
|
|
|
} |
|
|
|
|
|
|
|
Animation<float> animation; |
|
|
|
BottomNavigationBarItem item; |
|
|
|
|
|
|
|
public override Widget build(BuildContext context) { |
|
|
|
if (!this.showUnselectedLabels && !this.showSelectedLabels) { |
|
|
|
text = new Opacity( |
|
|
|
opacity: 0.0f, |
|
|
|
child: text |
|
|
|
); |
|
|
|
} |
|
|
|
else if (this.showUnselectedLabels) { |
|
|
|
text = new FadeTransition( |
|
|
|
opacity: this.animation, |
|
|
|
child: text |
|
|
|
); |
|
|
|
} |
|
|
|
else if (!this.showSelectedLabels) { |
|
|
|
text = new FadeTransition( |
|
|
|
opacity: new FloatTween(begin: 1.0f, end: 0.0f).animate(this.animation), |
|
|
|
child: text |
|
|
|
); |
|
|
|
} |
|
|
|
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 |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
child: new Container(child: text) |
|
|
|
|
|
|
|
|
|
|
|
class _BottomNavigationBarState : TickerProviderStateMixin<BottomNavigationBar> { |
|
|
|
public List<AnimationController> _controllers = new List<AnimationController> { }; |
|
|
|
|
|
|
List<Widget> _createTiles() { |
|
|
|
MaterialLocalizations localizations = MaterialLocalizations.of(this.context); |
|
|
|
D.assert(localizations != null); |
|
|
|
List<Widget> children = new List<Widget> { }; |
|
|
|
ThemeData themeData = Theme.of(this.context); |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
ColorTween colorTween; |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
ColorTween colorTween = new ColorTween( |
|
|
|
begin: textTheme.caption.color, |
|
|
|
end: this.widget.fixedColor ?? themeColor |
|
|
|
colorTween = new ColorTween( |
|
|
|
begin: this.widget.unselectedItemColor ?? themeData.textTheme.caption.color, |
|
|
|
end: this.widget.selectedItemColor ?? 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) |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
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) |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
colorTween = new ColorTween( |
|
|
|
begin: this.widget.unselectedItemColor ?? Colors.white, |
|
|
|
end: this.widget.selectedItemColor ?? Colors.white |
|
|
|
); |
|
|
|
break; |
|
|
|
default: |
|
|
|
throw new UIWidgetsError($"Unknown bottom navigation bar type: {this.widget.type}"); |
|
|
|
} |
|
|
|
break; |
|
|
|
List<Widget> tiles = new List<Widget>(); |
|
|
|
for (int i = 0; i < this.widget.items.Count; i++) { |
|
|
|
int index = i; |
|
|
|
tiles.Add(new _BottomNavigationTile( |
|
|
|
this.widget.type, |
|
|
|
this.widget.items[i], |
|
|
|
this._animations[i], |
|
|
|
this.widget.iconSize, |
|
|
|
selectedFontSize: this.widget.selectedFontSize, |
|
|
|
unselectedFontSize: this.widget.unselectedFontSize, |
|
|
|
onTap: () => { |
|
|
|
if (this.widget.onTap != null) { |
|
|
|
this.widget.onTap(index); |
|
|
|
} |
|
|
|
}, |
|
|
|
colorTween: colorTween, |
|
|
|
flex: this._evaluateFlex(this._animations[i]), |
|
|
|
selected: i == this.widget.currentIndex, |
|
|
|
showSelectedLabels: this.widget.showSelectedLabels, |
|
|
|
showUnselectedLabels: this.widget.showUnselectedLabels, |
|
|
|
indexLabel: localizations.tabLabel(tabIndex: i+1, tabCount: this.widget.items.Count) |
|
|
|
)); |
|
|
|
return children; |
|
|
|
return tiles; |
|
|
|
} |
|
|
|
|
|
|
|
Widget _createContainer(List<Widget> tiles) { |
|
|
|
|
|
|
D.assert(MaterialD.debugCheckHasMaterialLocalizations(context)); |
|
|
|
|
|
|
|
float additionalBottomPadding = |
|
|
|
Mathf.Max(MediaQuery.of(context).padding.bottom - BottomNavigationBarUtils._kBottomMargin, 0.0f); |
|
|
|
Mathf.Max(MediaQuery.of(context).padding.bottom - this.widget.selectedFontSize / 2.0f, 0.0f); |
|
|
|
Color backgroundColor = null; |
|
|
|
switch (this.widget.type) { |
|
|
|
case BottomNavigationBarType.fix: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return new Material( |
|
|
|
elevation: 8.0f, |
|
|
|
elevation: this.widget.elevation, |
|
|
|
color: backgroundColor, |
|
|
|
child: new ConstrainedBox( |
|
|
|
constraints: new BoxConstraints( |
|
|
|