Skip to content
This repository was archived by the owner on Apr 29, 2021. It is now read-only.

Commit ed6ebb9

Browse files
author
Yuncong Zhang
committed
[Cupertino] Implement tab scaffold.
1 parent f3c4253 commit ed6ebb9

File tree

3 files changed

+227
-2
lines changed

3 files changed

+227
-2
lines changed

Runtime/cupertino/bottom_app_bar.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public Size preferredSize {
6969
get { return Size.fromHeight(BottomAppBarUtils._kTabBarHeight); }
7070
}
7171

72-
bool opaque(BuildContext context) {
72+
public bool opaque(BuildContext context) {
7373
Color backgroundColor =
7474
this.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor;
7575
return backgroundColor.alpha == 0xFF;
@@ -175,7 +175,7 @@ Widget _wrapActiveItem(BuildContext context, Widget item, bool active) {
175175
);
176176
}
177177

178-
CupertinoTabBar copyWith(
178+
public CupertinoTabBar copyWith(
179179
Key key = null,
180180
List<BottomNavigationBarItem> items = null,
181181
Color backgroundColor = null,

Runtime/cupertino/tab_scaffold.cs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Unity.UIWidgets.foundation;
4+
using Unity.UIWidgets.painting;
5+
using Unity.UIWidgets.rendering;
6+
using Unity.UIWidgets.ui;
7+
using Unity.UIWidgets.widgets;
8+
9+
namespace Unity.UIWidgets.cupertino {
10+
public class CupertinoTabScaffold : StatefulWidget {
11+
public CupertinoTabScaffold(
12+
Key key = null,
13+
CupertinoTabBar tabBar = null,
14+
IndexedWidgetBuilder tabBuilder = null,
15+
Color backgroundColor = null,
16+
bool resizeToAvoidBottomInset = true
17+
) : base(key: key) {
18+
D.assert(tabBar != null);
19+
D.assert(tabBuilder != null);
20+
this.tabBar = tabBar;
21+
this.tabBuilder = tabBuilder;
22+
this.backgroundColor = backgroundColor;
23+
this.resizeToAvoidBottomInset = resizeToAvoidBottomInset;
24+
}
25+
26+
27+
public readonly CupertinoTabBar tabBar;
28+
29+
public readonly IndexedWidgetBuilder tabBuilder;
30+
31+
public readonly Color backgroundColor;
32+
33+
public readonly bool resizeToAvoidBottomInset;
34+
35+
public override State createState() {
36+
return new _CupertinoTabScaffoldState();
37+
}
38+
}
39+
40+
class _CupertinoTabScaffoldState : State<CupertinoTabScaffold> {
41+
int _currentPage;
42+
43+
public override void initState() {
44+
base.initState();
45+
this._currentPage = this.widget.tabBar.currentIndex;
46+
}
47+
48+
public override void didUpdateWidget(StatefulWidget _oldWidget) {
49+
CupertinoTabScaffold oldWidget = _oldWidget as CupertinoTabScaffold;
50+
base.didUpdateWidget(oldWidget);
51+
if (this._currentPage >= this.widget.tabBar.items.Count) {
52+
this._currentPage = this.widget.tabBar.items.Count - 1;
53+
D.assert(this._currentPage >= 0,
54+
() => "CupertinoTabBar is expected to keep at least 2 tabs after updating"
55+
);
56+
}
57+
58+
if (this.widget.tabBar.currentIndex != oldWidget.tabBar.currentIndex) {
59+
this._currentPage = this.widget.tabBar.currentIndex;
60+
}
61+
}
62+
63+
public override Widget build(BuildContext context) {
64+
List<Widget> stacked = new List<Widget> { };
65+
66+
MediaQueryData existingMediaQuery = MediaQuery.of(context);
67+
MediaQueryData newMediaQuery = MediaQuery.of(context);
68+
69+
Widget content = new _TabSwitchingView(
70+
currentTabIndex: this._currentPage,
71+
tabNumber: this.widget.tabBar.items.Count,
72+
tabBuilder: this.widget.tabBuilder
73+
);
74+
EdgeInsets contentPadding = EdgeInsets.zero;
75+
76+
if (this.widget.resizeToAvoidBottomInset) {
77+
newMediaQuery = newMediaQuery.removeViewInsets(removeBottom: true);
78+
contentPadding = EdgeInsets.only(bottom: existingMediaQuery.viewInsets.bottom);
79+
}
80+
81+
if (this.widget.tabBar != null &&
82+
(!this.widget.resizeToAvoidBottomInset ||
83+
this.widget.tabBar.preferredSize.height > existingMediaQuery.viewInsets.bottom)) {
84+
float bottomPadding = this.widget.tabBar.preferredSize.height + existingMediaQuery.padding.bottom;
85+
86+
if (this.widget.tabBar.opaque(context)) {
87+
contentPadding = EdgeInsets.only(bottom: bottomPadding);
88+
}
89+
else {
90+
newMediaQuery = newMediaQuery.copyWith(
91+
padding: newMediaQuery.padding.copyWith(
92+
bottom: bottomPadding
93+
)
94+
);
95+
}
96+
}
97+
98+
content = new MediaQuery(
99+
data: newMediaQuery,
100+
child: new Padding(
101+
padding: contentPadding,
102+
child: content
103+
)
104+
);
105+
106+
stacked.Add(content);
107+
108+
if (this.widget.tabBar != null) {
109+
stacked.Add(new Align(
110+
alignment: Alignment.bottomCenter,
111+
child: this.widget.tabBar.copyWith(
112+
currentIndex: this._currentPage,
113+
onTap: (int newIndex) => {
114+
this.setState(() => { this._currentPage = newIndex; });
115+
if (this.widget.tabBar.onTap != null) {
116+
this.widget.tabBar.onTap(newIndex);
117+
}
118+
}
119+
)
120+
));
121+
}
122+
123+
return new DecoratedBox(
124+
decoration: new BoxDecoration(
125+
color: this.widget.backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor
126+
),
127+
child: new Stack(
128+
children: stacked
129+
)
130+
);
131+
}
132+
}
133+
134+
class _TabSwitchingView : StatefulWidget {
135+
public _TabSwitchingView(
136+
int currentTabIndex,
137+
int tabNumber,
138+
IndexedWidgetBuilder tabBuilder
139+
) {
140+
D.assert(currentTabIndex != null);
141+
D.assert(tabNumber != null && tabNumber > 0);
142+
D.assert(tabBuilder != null);
143+
}
144+
145+
public readonly int currentTabIndex;
146+
public readonly int tabNumber;
147+
public readonly IndexedWidgetBuilder tabBuilder;
148+
149+
public override State createState() {
150+
return new _TabSwitchingViewState();
151+
}
152+
}
153+
154+
class _TabSwitchingViewState : State<_TabSwitchingView> {
155+
List<Widget> tabs;
156+
List<FocusScopeNode> tabFocusNodes;
157+
158+
public override void initState() {
159+
base.initState();
160+
this.tabs = new List<Widget>(this.widget.tabNumber);
161+
this.tabFocusNodes = Enumerable.Repeat(new FocusScopeNode(), this.widget.tabNumber).ToList();
162+
}
163+
164+
public override void didChangeDependencies() {
165+
base.didChangeDependencies();
166+
this._focusActiveTab();
167+
}
168+
169+
public override void didUpdateWidget(StatefulWidget _oldWidget) {
170+
_TabSwitchingView oldWidget = _oldWidget as _TabSwitchingView;
171+
base.didUpdateWidget(oldWidget);
172+
this._focusActiveTab();
173+
}
174+
175+
void _focusActiveTab() {
176+
FocusScope.of(this.context).setFirstFocus(this.tabFocusNodes[this.widget.currentTabIndex]);
177+
}
178+
179+
public override void dispose() {
180+
foreach (FocusScopeNode focusScopeNode in this.tabFocusNodes) {
181+
focusScopeNode.detach();
182+
}
183+
184+
base.dispose();
185+
}
186+
187+
public override Widget build(BuildContext context) {
188+
List<Widget> children = new List<Widget>();
189+
for (int index = 0; index < this.widget.tabNumber; index++) {
190+
bool active = index == this.widget.currentTabIndex;
191+
192+
if (active || this.tabs[index] != null) {
193+
this.tabs[index] = this.widget.tabBuilder(context, index);
194+
}
195+
196+
children.Add(new Offstage(
197+
offstage: !active,
198+
child: new TickerMode(
199+
enabled: active,
200+
child: new FocusScope(
201+
node: this.tabFocusNodes[index],
202+
child: this.tabs[index] ?? new Container()
203+
)
204+
)
205+
));
206+
}
207+
208+
return new Stack(
209+
fit: StackFit.expand,
210+
children: children
211+
);
212+
}
213+
}
214+
}

Runtime/cupertino/tab_scaffold.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)