您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
341 行
12 KiB
341 行
12 KiB
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.ui;
|
|
using Unity.UIWidgets.widgets;
|
|
|
|
namespace Unity.UIWidgets.cupertino {
|
|
public class CupertinoTabController : ChangeNotifier {
|
|
|
|
public CupertinoTabController(int initialIndex = 0) {
|
|
D.assert(initialIndex != null);
|
|
D.assert(initialIndex >= 0);
|
|
_index = initialIndex;
|
|
}
|
|
|
|
public bool _isDisposed = false;
|
|
|
|
public int index {
|
|
get { return _index; }
|
|
set {
|
|
D.assert(value != null);
|
|
D.assert(value >= 0);
|
|
if (_index == value) {
|
|
return;
|
|
}
|
|
_index = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
int _index;
|
|
public override void dispose() {
|
|
base.dispose();
|
|
_isDisposed = true;
|
|
}
|
|
}
|
|
|
|
public class CupertinoTabScaffold : StatefulWidget {
|
|
public CupertinoTabScaffold(
|
|
Key key = null,
|
|
CupertinoTabBar tabBar = null,
|
|
IndexedWidgetBuilder tabBuilder = null,
|
|
CupertinoTabController controller = null,
|
|
Color backgroundColor = null,
|
|
bool resizeToAvoidBottomInset = true
|
|
) : base(key: key) {
|
|
D.assert(tabBar != null);
|
|
D.assert(tabBuilder != null);
|
|
D.assert(
|
|
controller == null || controller.index < tabBar.items.Count, () =>
|
|
$"The CupertinoTabController's current index {controller.index} is " +
|
|
$"out of bounds for the tab bar with {tabBar.items.Count} tabs"
|
|
);
|
|
this.tabBar = tabBar;
|
|
this.controller = controller;
|
|
this.tabBuilder = tabBuilder;
|
|
this.backgroundColor = backgroundColor;
|
|
this.resizeToAvoidBottomInset = resizeToAvoidBottomInset;
|
|
}
|
|
|
|
public readonly CupertinoTabController controller;
|
|
public readonly CupertinoTabBar tabBar;
|
|
|
|
public readonly IndexedWidgetBuilder tabBuilder;
|
|
|
|
public readonly Color backgroundColor;
|
|
|
|
public readonly bool resizeToAvoidBottomInset;
|
|
|
|
public override State createState() {
|
|
return new _CupertinoTabScaffoldState();
|
|
}
|
|
}
|
|
|
|
class _CupertinoTabScaffoldState : State<CupertinoTabScaffold> {
|
|
int _currentPage;
|
|
CupertinoTabController _controller;
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
_updateTabController();
|
|
}
|
|
void _updateTabController( bool shouldDisposeOldController = false ) {
|
|
CupertinoTabController newController =
|
|
// User provided a new controller, update `_controller` with it.
|
|
widget.controller
|
|
?? new CupertinoTabController(initialIndex: widget.tabBar.currentIndex);
|
|
|
|
if (newController == _controller) {
|
|
return;
|
|
}
|
|
|
|
if (shouldDisposeOldController) {
|
|
_controller?.dispose();
|
|
} else if (_controller?._isDisposed == false) {
|
|
_controller.removeListener(_onCurrentIndexChange);
|
|
}
|
|
|
|
newController.addListener(_onCurrentIndexChange);
|
|
_controller = newController;
|
|
}
|
|
void _onCurrentIndexChange() {
|
|
D.assert(
|
|
_controller.index >= 0 && _controller.index < widget.tabBar.items.Count,()=>
|
|
$"The {GetType()}'s current index {_controller.index} is " +
|
|
$"out of bounds for the tab bar with {widget.tabBar.items.Count} tabs"
|
|
);
|
|
setState(()=> {});
|
|
}
|
|
|
|
public override void didUpdateWidget(StatefulWidget _oldWidget) {
|
|
CupertinoTabScaffold oldWidget = _oldWidget as CupertinoTabScaffold;
|
|
base.didUpdateWidget(oldWidget);
|
|
if (widget.controller != oldWidget.controller) {
|
|
_updateTabController(shouldDisposeOldController: oldWidget.controller == null);
|
|
} else if (_controller.index >= widget.tabBar.items.Count) {
|
|
_controller.index = widget.tabBar.items.Count - 1;
|
|
}
|
|
}
|
|
public override Widget build(BuildContext context) {
|
|
List<Widget> stacked = new List<Widget> { };
|
|
|
|
MediaQueryData existingMediaQuery = MediaQuery.of(context);
|
|
MediaQueryData newMediaQuery = MediaQuery.of(context);
|
|
|
|
Widget content = new _TabSwitchingView(
|
|
currentTabIndex: _controller.index,
|
|
tabNumber: widget.tabBar.items.Count,
|
|
tabBuilder: widget.tabBuilder
|
|
);
|
|
EdgeInsets contentPadding = EdgeInsets.zero;
|
|
|
|
if (widget.resizeToAvoidBottomInset) {
|
|
newMediaQuery = newMediaQuery.removeViewInsets(removeBottom: true);
|
|
contentPadding = EdgeInsets.only(bottom: existingMediaQuery.viewInsets.bottom);
|
|
}
|
|
|
|
if (widget.tabBar != null &&
|
|
(!widget.resizeToAvoidBottomInset ||
|
|
widget.tabBar.preferredSize.height > existingMediaQuery.viewInsets.bottom)) {
|
|
float bottomPadding = widget.tabBar.preferredSize.height + existingMediaQuery.padding.bottom;
|
|
|
|
if (widget.tabBar.opaque(context)) {
|
|
contentPadding = EdgeInsets.only(bottom: bottomPadding);
|
|
newMediaQuery = newMediaQuery.removePadding(removeBottom: true);
|
|
}
|
|
else {
|
|
newMediaQuery = newMediaQuery.copyWith(
|
|
padding: newMediaQuery.padding.copyWith(
|
|
bottom: bottomPadding
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
content = new MediaQuery(
|
|
data: newMediaQuery,
|
|
child: new Padding(
|
|
padding: contentPadding,
|
|
child: content
|
|
)
|
|
);
|
|
stacked.Add(content);
|
|
stacked.Add(
|
|
new MediaQuery(
|
|
data: existingMediaQuery.copyWith(textScaleFactor: 1),
|
|
child: new Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: widget.tabBar.copyWith(
|
|
currentIndex: _controller.index,
|
|
onTap: (int newIndex) =>{
|
|
_controller.index = newIndex;
|
|
if (widget.tabBar.onTap != null)
|
|
widget.tabBar.onTap(newIndex);
|
|
}
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
return new DecoratedBox(
|
|
decoration: new BoxDecoration(
|
|
color: CupertinoDynamicColor.resolve(widget.backgroundColor, context)
|
|
?? CupertinoTheme.of(context).scaffoldBackgroundColor
|
|
),
|
|
child: new Stack(
|
|
children: stacked
|
|
)
|
|
);
|
|
}
|
|
|
|
public override void dispose() {
|
|
|
|
if (widget.controller == null) {
|
|
_controller?.dispose();
|
|
} else if (_controller?._isDisposed == false) {
|
|
_controller.removeListener(_onCurrentIndexChange);
|
|
}
|
|
|
|
base.dispose();
|
|
}
|
|
|
|
}
|
|
|
|
class _TabSwitchingView : StatefulWidget {
|
|
public _TabSwitchingView(
|
|
int currentTabIndex,
|
|
int tabNumber,
|
|
IndexedWidgetBuilder tabBuilder
|
|
) {
|
|
D.assert(tabNumber > 0);
|
|
D.assert(tabBuilder != null);
|
|
this.currentTabIndex = currentTabIndex;
|
|
this.tabNumber = tabNumber;
|
|
this.tabBuilder = tabBuilder;
|
|
}
|
|
|
|
public readonly int currentTabIndex;
|
|
public readonly int tabNumber;
|
|
public readonly IndexedWidgetBuilder tabBuilder;
|
|
|
|
public override State createState() {
|
|
return new _TabSwitchingViewState();
|
|
}
|
|
}
|
|
|
|
class _TabSwitchingViewState : State<_TabSwitchingView> {
|
|
|
|
public readonly List<bool> shouldBuildTab = new List<bool>();
|
|
public readonly List<FocusScopeNode> tabFocusNodes = new List<FocusScopeNode>();
|
|
public readonly List<FocusScopeNode> discardedNodes = new List<FocusScopeNode>();
|
|
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
List<bool> tabBool = new List<bool>();
|
|
for (int i = 0; i < widget.tabNumber; i++) {
|
|
tabBool.Add(false);
|
|
}
|
|
foreach (var tab in tabBool) {
|
|
shouldBuildTab.Add(tab);
|
|
}
|
|
}
|
|
|
|
public override void didChangeDependencies() {
|
|
base.didChangeDependencies();
|
|
_focusActiveTab();
|
|
}
|
|
|
|
public override void didUpdateWidget(StatefulWidget _oldWidget) {
|
|
_TabSwitchingView oldWidget = _oldWidget as _TabSwitchingView;
|
|
base.didUpdateWidget(oldWidget);
|
|
int lengthDiff = widget.tabNumber - shouldBuildTab.Count;
|
|
if (lengthDiff > 0) {
|
|
List<bool> tabBool = new List<bool>();
|
|
for (int i = 0; i < lengthDiff; i++) {
|
|
tabBool.Add(false);
|
|
}
|
|
foreach (var tab in tabBool) {
|
|
shouldBuildTab.Add(tab);
|
|
}
|
|
}
|
|
else if (lengthDiff < 0) {
|
|
for (int i = widget.tabNumber; i < shouldBuildTab.Count; i++) {
|
|
shouldBuildTab.RemoveAt(i);
|
|
}
|
|
|
|
}
|
|
_focusActiveTab();
|
|
}
|
|
|
|
void _focusActiveTab() {
|
|
if (tabFocusNodes.Count!= widget.tabNumber) {
|
|
if (tabFocusNodes.Count> widget.tabNumber) {
|
|
for (int i = widget.tabNumber; i < tabFocusNodes.Count; i++) {
|
|
discardedNodes.Add(tabFocusNodes[i]);
|
|
}
|
|
for (int i = widget.tabNumber; i < tabFocusNodes.Count; i++ ){
|
|
tabFocusNodes.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
else {
|
|
List<FocusScopeNode> scopeNodes = new List<FocusScopeNode>();
|
|
var length = widget.tabNumber - tabFocusNodes.Count;
|
|
for (int i = 0; i < length; i++) {
|
|
scopeNodes.Add(new FocusScopeNode(debugLabel: $"CupertinoTabScaffold Tab {i + tabFocusNodes.Count}")
|
|
);
|
|
}
|
|
tabFocusNodes.AddRange(scopeNodes);
|
|
|
|
}
|
|
|
|
}
|
|
FocusScope.of(context).setFirstFocus(tabFocusNodes[widget.currentTabIndex]);
|
|
}
|
|
|
|
public override void dispose() {
|
|
foreach(FocusScopeNode focusScopeNode in tabFocusNodes) {
|
|
focusScopeNode.dispose();
|
|
}
|
|
foreach( FocusScopeNode focusScopeNode in discardedNodes) {
|
|
focusScopeNode.dispose();
|
|
}
|
|
base.dispose();
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
|
|
List<Widget> stages = new List<Widget>();
|
|
int count = widget.tabNumber;
|
|
for (int i = 0; i < count; i++) {
|
|
bool active = i == widget.currentTabIndex;
|
|
shouldBuildTab[i] = active || shouldBuildTab[i];
|
|
int temp = i;
|
|
stages.Add(new Offstage(
|
|
offstage: !active,
|
|
child: new TickerMode(
|
|
enabled: active,
|
|
child: new FocusScope(
|
|
node: tabFocusNodes[i],
|
|
child: new Builder(
|
|
builder: (BuildContext context1) => {
|
|
return shouldBuildTab[temp] ? widget.tabBuilder(context1, temp) : new Container();
|
|
}
|
|
)
|
|
)
|
|
)
|
|
));
|
|
|
|
}
|
|
|
|
return new Stack(
|
|
fit: StackFit.expand,
|
|
children:stages
|
|
|
|
);
|
|
}
|
|
}
|
|
}
|