Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit 0e708d0

Browse files
authored
Force Shell TitleView to height of container and fix flyout header scroll behaviors (#13514)
* Force Shell TitleView to height of container * - fix collapse on scroll and scroll * - fix android * - uiitests * - fix header positioning * - Fix casing
1 parent 0f42424 commit 0e708d0

File tree

9 files changed

+311
-18
lines changed

9 files changed

+311
-18
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using Xamarin.Forms.CustomAttributes;
2+
using Xamarin.Forms.Internals;
3+
4+
5+
#if UITEST
6+
using NUnit.Framework;
7+
using Xamarin.Forms.Core.UITests;
8+
#endif
9+
10+
11+
namespace Xamarin.Forms.Controls.Issues
12+
{
13+
[Preserve(AllMembers = true)]
14+
[Issue(IssueTracker.Github, 13476, "Shell Title View Test",
15+
PlatformAffected.iOS)]
16+
#if UITEST
17+
[NUnit.Framework.Category(UITestCategories.Shell)]
18+
[NUnit.Framework.Category(UITestCategories.TitleView)]
19+
[NUnit.Framework.Category(UITestCategories.UwpIgnore)]
20+
#endif
21+
public class Issue13476 : TestShell
22+
{
23+
protected override void Init()
24+
{
25+
AddTopTab(createContentPage("title 1"), "page 1");
26+
AddTopTab(createContentPage("title 2"), "page 2");
27+
28+
ContentPage createContentPage(string titleView)
29+
{
30+
Label safeArea = new Label();
31+
ContentPage page = new ContentPage()
32+
{
33+
Content = new StackLayout()
34+
{
35+
Children =
36+
{
37+
new Label()
38+
{
39+
Text = "If the TitleView is not visible the test has failed.",
40+
AutomationId = "Instructions"
41+
},
42+
safeArea
43+
}
44+
}
45+
};
46+
47+
if (!string.IsNullOrWhiteSpace(titleView))
48+
{
49+
Shell.SetTitleView(page,
50+
new Grid()
51+
{
52+
BackgroundColor = Color.PaleGoldenrod,
53+
AutomationId = "TitleViewId",
54+
Children = { new Label() { Text = titleView, VerticalTextAlignment = TextAlignment.End } }
55+
});
56+
}
57+
58+
return page;
59+
}
60+
}
61+
62+
63+
#if UITEST
64+
65+
[Test]
66+
public void TitleViewHeightDoesntOverflow()
67+
{
68+
var titleView = RunningApp.WaitForElement("TitleViewId")[0].Rect;
69+
var topTab = RunningApp.WaitForElement("page 1")[0].Rect;
70+
71+
var titleViewBottom = titleView.Y + titleView.Height;
72+
var topTabTop = topTab.Y;
73+
74+
Assert.GreaterOrEqual(topTabTop, titleViewBottom, "Title View is incorrectly positioned behind tabs");
75+
}
76+
#endif
77+
78+
}
79+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System;
2+
using Xamarin.Forms.CustomAttributes;
3+
using Xamarin.Forms.Internals;
4+
5+
6+
#if UITEST
7+
using Xamarin.UITest;
8+
using NUnit.Framework;
9+
using Xamarin.Forms.Core.UITests;
10+
#endif
11+
12+
namespace Xamarin.Forms.Controls.Issues
13+
{
14+
[Preserve(AllMembers = true)]
15+
[Issue(IssueTracker.None, 0, "Shell Flyout Header Behavior",
16+
PlatformAffected.All)]
17+
#if UITEST
18+
[NUnit.Framework.Category(UITestCategories.Shell)]
19+
[NUnit.Framework.Category(UITestCategories.UwpIgnore)]
20+
#endif
21+
public class ShellFlyoutHeaderBehavior : TestShell
22+
{
23+
public ShellFlyoutHeaderBehavior()
24+
{
25+
}
26+
27+
protected override void Init()
28+
{
29+
FlyoutHeader = new Grid()
30+
{
31+
HeightRequest = 143,
32+
BackgroundColor = Color.Black,
33+
AutomationId = "FlyoutHeaderId",
34+
Children =
35+
{
36+
new Image()
37+
{
38+
Aspect = Aspect.AspectFill,
39+
Source = "xamarinstore.jpg",
40+
Opacity = 0.6
41+
},
42+
new Label()
43+
{
44+
Margin = new Thickness(0, 40, 0, 0),
45+
Text="Hello XamStore",
46+
TextColor=Color.White,
47+
FontAttributes=FontAttributes.Bold,
48+
VerticalTextAlignment = TextAlignment.Center
49+
}
50+
}
51+
};
52+
53+
for (int i = 0; i < 40; i++)
54+
{
55+
AddFlyoutItem(CreateContentPage(), $"Item {i}");
56+
}
57+
58+
ContentPage CreateContentPage()
59+
{
60+
var page = new ContentPage();
61+
var layout = new StackLayout();
62+
63+
foreach(FlyoutHeaderBehavior value in Enum.GetValues(typeof(FlyoutHeaderBehavior)))
64+
{
65+
var local = value;
66+
layout.Children.Add(new Button()
67+
{
68+
Text = $"{value}",
69+
AutomationId = $"{value}",
70+
Command = new Command(()=>
71+
{
72+
this.FlyoutHeaderBehavior = local;
73+
})
74+
});
75+
}
76+
77+
page.Content = layout;
78+
return page;
79+
};
80+
}
81+
82+
83+
#if UITEST
84+
85+
[Test]
86+
public void FlyoutHeaderBehaviorFixed()
87+
{
88+
RunningApp.Tap(nameof(FlyoutHeaderBehavior.Fixed));
89+
this.ShowFlyout();
90+
float startingHeight = GetFlyoutHeight();
91+
RunningApp.ScrollDown("Item 4", ScrollStrategy.Gesture);
92+
float endHeight = GetFlyoutHeight();
93+
94+
Assert.AreEqual(startingHeight, endHeight);
95+
}
96+
97+
[Test]
98+
public void FlyoutHeaderBehaviorCollapseOnScroll()
99+
{
100+
RunningApp.Tap(nameof(FlyoutHeaderBehavior.CollapseOnScroll));
101+
this.ShowFlyout();
102+
float startingHeight = GetFlyoutHeight();
103+
RunningApp.ScrollDown("Item 4", ScrollStrategy.Gesture);
104+
float endHeight = GetFlyoutHeight();
105+
106+
Assert.Greater(startingHeight, endHeight);
107+
}
108+
109+
[Test]
110+
public void FlyoutHeaderBehaviorScroll()
111+
{
112+
RunningApp.Tap(nameof(FlyoutHeaderBehavior.Scroll));
113+
this.ShowFlyout();
114+
115+
var startingY = GetFlyoutY();
116+
RunningApp.ScrollDown("Item 5", ScrollStrategy.Gesture);
117+
var nextY = GetFlyoutY();
118+
119+
while(nextY != null)
120+
{
121+
Assert.Greater(startingY.Value, nextY.Value);
122+
startingY = nextY;
123+
RunningApp.ScrollDown("Item 5", ScrollStrategy.Gesture);
124+
nextY = GetFlyoutY();
125+
}
126+
}
127+
128+
float GetFlyoutHeight() =>
129+
RunningApp.WaitForElement("FlyoutHeaderId")[0].Rect.Height;
130+
131+
float? GetFlyoutY()
132+
{
133+
var flyoutHeader =
134+
RunningApp.Query("FlyoutHeaderId");
135+
136+
if (flyoutHeader.Length == 0)
137+
return null;
138+
139+
return flyoutHeader[0].Rect.Y;
140+
}
141+
142+
#endif
143+
}
144+
}

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,8 @@
17171717
<Compile Include="$(MSBuildThisFileDirectory)ShellFlyoutContentWithZeroMargin.cs" />
17181718
<Compile Include="$(MSBuildThisFileDirectory)LabelFormattedTextHtmlPadding.cs" />
17191719
<Compile Include="$(MSBuildThisFileDirectory)Issue13436.xaml.cs" />
1720+
<Compile Include="$(MSBuildThisFileDirectory)Issue13476.cs" />
1721+
<Compile Include="$(MSBuildThisFileDirectory)ShellFlyoutHeaderBehavior.cs" />
17201722
<Compile Include="$(MSBuildThisFileDirectory)Issue8701.cs" />
17211723
<Compile Include="$(MSBuildThisFileDirectory)Issue13390.cs" />
17221724
<Compile Include="$(MSBuildThisFileDirectory)Issue13616.xaml.cs" />

Xamarin.Forms.Platform.Android/Renderers/ContainerView.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ protected override void OnLayout(bool changed, int l, int t, int r, int b)
6767

6868
protected virtual void LayoutView(double x, double y, double width, double height)
6969
{
70-
_shellViewRenderer.LayoutView(width, height);
70+
_shellViewRenderer.LayoutView(x, y, width, height);
7171
}
7272

7373
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)

Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,8 +549,17 @@ void UpdateElevation()
549549

550550
protected override void LayoutView(double x, double y, double width, double height)
551551
{
552+
var context = Context;
553+
var paddingLeft = context.FromPixels(PaddingLeft);
554+
var paddingTop = context.FromPixels(PaddingTop);
555+
var paddingRight = context.FromPixels(PaddingRight);
556+
var paddingBottom = context.FromPixels(PaddingBottom);
557+
558+
width -= paddingLeft + paddingRight;
559+
height -= paddingTop + paddingBottom;
560+
552561
UpdateElevation();
553-
base.LayoutView(x, y, width, height);
562+
base.LayoutView(paddingLeft, paddingTop, width, height);
554563
}
555564

556565
protected override void Dispose(bool disposing)

Xamarin.Forms.Platform.Android/Renderers/ShellViewRenderer.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ public void TearDown()
4343
}
4444

4545
public void LayoutView(double width, double height, double? maxWidth = null, double? maxHeight = null)
46+
{
47+
LayoutView(0, 0, width, height, maxWidth, maxHeight);
48+
}
49+
50+
public void LayoutView(double x, double y, double width, double height, double? maxWidth = null, double? maxHeight = null)
4651
{
4752
if (width == -1)
4853
width = double.PositiveInfinity;
@@ -86,7 +91,7 @@ public void LayoutView(double width, double height, double? maxWidth = null, dou
8691
layoutParams.Height = (int)context.ToPixels(height);
8792

8893
NativeView.LayoutParameters = layoutParams;
89-
View.Layout(new Rectangle(0, 0, width, height));
94+
View.Layout(new Rectangle(x, y, width, height));
9095
Renderer.UpdateLayout();
9196
}
9297

Xamarin.Forms.Platform.iOS/Renderers/ShellFlyoutLayoutManager.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public virtual UIView FooterView
170170

171171
void OnHeaderFooterSizeChanged(object sender, EventArgs e)
172172
{
173-
_headerSize = HeaderMax;
173+
HeaderSize = HeaderMax;
174174
SetHeaderContentInset();
175175
LayoutParallax();
176176
}
@@ -266,21 +266,22 @@ public void LayoutParallax()
266266
}
267267
}
268268

269-
if (HeaderView != null && !double.IsNaN(_headerSize))
269+
if (HeaderView != null && !double.IsNaN(HeaderSize))
270270
{
271271
var margin = HeaderView.Margin;
272272
var leftMargin = margin.Left - margin.Right;
273273

274-
HeaderView.Frame = new CGRect(leftMargin, _headerOffset, parent.Frame.Width, _headerSize + HeaderTopMargin);
275-
274+
HeaderView.Frame = new CGRect(leftMargin, _headerOffset, parent.Frame.Width, HeaderSize + HeaderTopMargin);
275+
276276
if (_context.Shell.FlyoutHeaderBehavior == FlyoutHeaderBehavior.Scroll && HeaderTopMargin > 0 && _headerOffset < 0)
277277
{
278-
var headerHeight = Math.Max(_headerMin, _headerSize + _headerOffset);
278+
var headerHeight = Math.Max(_headerMin, HeaderSize + _headerOffset + HeaderTopMargin);
279279
CAShapeLayer shapeLayer = new CAShapeLayer();
280280
CGRect rect = new CGRect(0, _headerOffset * -1, parent.Frame.Width, headerHeight);
281281
var path = CGPath.FromRect(rect);
282282
shapeLayer.Path = path;
283283
HeaderView.Layer.Mask = shapeLayer;
284+
284285
}
285286
else if (HeaderView.Layer.Mask != null)
286287
HeaderView.Layer.Mask = null;
@@ -314,24 +315,39 @@ public void OnScrolled(nfloat contentOffsetY)
314315
{
315316
case FlyoutHeaderBehavior.Default:
316317
case FlyoutHeaderBehavior.Fixed:
317-
_headerSize = HeaderMax;
318+
HeaderSize = HeaderMax;
318319
_headerOffset = 0;
319320
break;
320321

321322
case FlyoutHeaderBehavior.Scroll:
322-
_headerSize = HeaderMax;
323+
HeaderSize = HeaderMax;
323324
_headerOffset = Math.Min(0, -(HeaderMax + contentOffsetY));
324325
break;
325326

326327
case FlyoutHeaderBehavior.CollapseOnScroll:
327-
_headerSize = Math.Max(_headerMin, -contentOffsetY);
328+
HeaderSize = Math.Max(_headerMin, -contentOffsetY);
328329
_headerOffset = 0;
329330
break;
330331
}
331332

332333
LayoutParallax();
333334
}
334335

336+
337+
double HeaderSize
338+
{
339+
get => _headerSize;
340+
set
341+
{
342+
if (HeaderView != null)
343+
{
344+
HeaderView.Height = value;
345+
}
346+
347+
_headerSize = value;
348+
}
349+
}
350+
335351
double HeaderMax => HeaderView?.MeasuredHeight ?? 0;
336352
double HeaderTopMargin => (HeaderView != null) ? HeaderView.Margin.Top - HeaderView.Margin.Bottom : 0;
337353

Xamarin.Forms.Platform.iOS/Renderers/ShellPageRendererTracker.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ public class TitleViewContainer : UIContainerView
398398
{
399399
public TitleViewContainer(View view) : base(view)
400400
{
401+
MatchHeight = true;
401402
}
402403

403404
public override CGRect Frame
@@ -415,6 +416,20 @@ public override CGRect Frame
415416
}
416417
}
417418

419+
public override void WillMoveToSuperview(UIView newSuper)
420+
{
421+
if (newSuper != null)
422+
{
423+
if (!Forms.IsiOS11OrNewer)
424+
Frame = new CGRect(Frame.X, newSuper.Bounds.Y, Frame.Width, newSuper.Bounds.Height);
425+
426+
Height = newSuper.Bounds.Height;
427+
Width = newSuper.Bounds.Width;
428+
}
429+
430+
base.WillMoveToSuperview(newSuper);
431+
}
432+
418433
public override CGSize IntrinsicContentSize => UILayoutFittingExpandedSize;
419434

420435
public override CGSize SizeThatFits(CGSize size)

0 commit comments

Comments
 (0)