This commit is contained in:
Stephane Delcroix 2018-05-14 10:20:35 +02:00
Родитель 7e563b07df dc4dec9df9
Коммит 67d2be4cd2
13 изменённых файлов: 1294 добавлений и 198 удалений

Просмотреть файл

@ -10,7 +10,7 @@
<iconUrl>http://xamarin.com/content/images/nuget/xamarin.png</iconUrl>
<projectUrl>http://xamarin.com/forms</projectUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<description>Build native UIs for iOS, Android, and Windows Phone from a single, shared C# codebase</description>
<description>Build native UIs for iOS, Android, UWP, macOS, Tizen and many more from a single, shared C# codebase</description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<dependencies>
<group targetFramework="MonoAndroid10">

Просмотреть файл

@ -0,0 +1,302 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
using System.Collections.Specialized;
#if UITEST
using NUnit.Framework;
using Xamarin.UITest;
#endif
namespace Xamarin.Forms.Controls.Issues
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 1675, "Bottom Tabbed Page Basic Test", PlatformAffected.All)]
public class BottomTabbedPageTests : TestTabbedPage
{
Label pageCountLabel = null;
public BottomTabbedPageTests() : base()
{
}
protected override void Init()
{
On<Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
pageCountLabel = new Label() { AutomationId = "PageCount" };
var popButton1 = new Button() { Text = "Pop", BackgroundColor = Color.Blue };
popButton1.Clicked += (s, a) => Navigation.PopModalAsync();
var popButton2 = new Button() { Text = "Pop 2", BackgroundColor = Color.Blue };
popButton2.Clicked += (s, a) => Navigation.PopModalAsync();
var longerTest = new Button() { Text = "Manual Color Tests", BackgroundColor = Color.Blue };
Children.Add(new ContentPage() { Title = "Page 1", Content = popButton1, Icon = "coffee.png" });
Children.Add(new ContentPage() { Title = "Page 2", Content = popButton2, Icon = "bank.png" });
Button btnChangeBarText = null;
Button btnChangeBarItemColorText = null;
Button btnChangeBarSelectedItemColorText = null;
Button btnAddPage = null;
Button btnRemovePage = null;
Label lblSuccess = new Label() { AutomationId = "Outcome" };
btnChangeBarText = new Button()
{
Text = "Change Bar Text",
Command = new Command(() =>
{
if (BarTextColor == Color.Default)
{
BarTextColor = Color.HotPink;
btnChangeBarText.Text = $"Bar Text: HotPink";
}
else
{
BarTextColor = Color.Default;
btnChangeBarText.Text = $"Bar Text: Default";
}
})
};
btnChangeBarItemColorText = new Button()
{
Text = "Change Item Color",
Command = new Command(() =>
{
if (On<Android>().GetBarItemColor() == Color.Default)
{
On<Android>().SetBarItemColor(new Color(0, 255, 0, 128));
btnChangeBarItemColorText.Text = $"Item Color: Less Green";
}
else
{
On<Android>().SetBarItemColor(Color.Default);
btnChangeBarItemColorText.Text = $"Item Color: Default";
}
})
};
btnChangeBarSelectedItemColorText = new Button()
{
Text = "Change Selected Item Color",
Command = new Command(() =>
{
if (On<Android>().GetBarSelectedItemColor() == Color.Default)
{
On<Android>().SetBarSelectedItemColor(Color.Green);
btnChangeBarSelectedItemColorText.Text = $"Selected Item Color: Green";
}
else
{
On<Android>().SetBarSelectedItemColor(Color.Default);
btnChangeBarSelectedItemColorText.Text = $"Selected Item Color: Default";
}
})
};
btnAddPage = new Button()
{
Text = $"Add Page (more than {On<Android>().GetMaxItemCount()} will crash)",
Command = new Command(() =>
{
Children.Add(new ContentPage()
{
Content = new Label() { Text = (Children.Count + 1).ToString() },
Title = (Children.Count + 1).ToString(),
Icon = "calculator.png"
});
btnRemovePage.IsEnabled = true;
}),
AutomationId = "AddPage"
};
btnRemovePage = new Button()
{
Text = "Remove Page",
Command = new Command(() =>
{
Children.Remove(Children.Last());
if (Children.Count == 3)
{
btnRemovePage.IsEnabled = false;
}
}),
IsEnabled = false,
AutomationId = "RemovePage"
};
var layout = new StackLayout()
{
Children =
{
btnChangeBarText,
new Button()
{
Text = "Change Bar Background Color",
Command = new Command(()=>
{
if(BarBackgroundColor == Color.Default)
BarBackgroundColor = Color.Fuchsia;
else
BarBackgroundColor = Color.Default;
})
},
btnAddPage,
btnRemovePage,
new Button()
{
Text = "Page Add/Remove Permutations",
Command = new Command(() =>
{
while(Children.Count > 3)
{
Children.Remove(Children.Last());
}
Children.Insert(1, new ContentPage(){ Icon = "bank.png" });
Children.Insert(1, new ContentPage(){ Icon = "bank.png" });
int i = 0;
Device.StartTimer(TimeSpan.FromSeconds(3), () =>
{
if(i == 0)
{
// Ensure inserting didn't change current page
if (CurrentPage != Children[4])
{
throw new Exception("Inserting page caused Current Page to Change");
}
Children.RemoveAt(1);
}
else if(i == 1)
{
// Ensure removing didn't change current page
if (CurrentPage != Children[3])
{
throw new Exception("Removing page caused Current Page to Change");
}
Children.Insert(1, new ContentPage(){ Icon = "bank.png" });
CurrentPage = Children[1];
}
else if(i == 2)
{
if (CurrentPage != Children[1])
{
throw new Exception("Current Page not correctly set to new page inserted");
}
Children.RemoveAt(1);
Children.RemoveAt(1);
}
else if(i == 3)
{
if(CurrentPage != Children[0])
{
throw new Exception("Current Page not reset to Page one after Current Page was Removed");
}
CurrentPage = Children.Last();
lblSuccess.Text = "Success";
}
else if(i >= 4)
{
return false;
}
i++;
return true;
});
})
},
pageCountLabel,
lblSuccess
},
};
if (Device.RuntimePlatform == Device.Android)
{
layout.Children.Insert(1, btnChangeBarItemColorText);
layout.Children.Insert(2, btnChangeBarSelectedItemColorText);
}
Children.Add(new ContentPage()
{
Title = "Test",
Content = layout,
Icon = "calculator.png"
});
}
protected override void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
pageCountLabel.Text = $"{Children.Count} Pages";
}
protected override void OnPagesChanged(NotifyCollectionChangedEventArgs e)
{
base.OnPagesChanged(e);
pageCountLabel.Text = $"{Children.Count} Pages";
}
#if UITEST
[Test]
public async Task AddAndRemovePages()
{
RunningApp.WaitForElement(q => q.Marked("Test"));
RunningApp.Tap(q => q.Marked("Test"));
RunningApp.WaitForElement(q => q.Marked("3 Pages"));
RunningApp.Tap(q => q.Button("AddPage"));
RunningApp.WaitForElement(q => q.Marked("4 Pages"));
RunningApp.Tap(q => q.Button("AddPage"));
RunningApp.WaitForElement(q => q.Marked("5 Pages"));
RunningApp.Tap(q => q.Button("RemovePage"));
RunningApp.WaitForElement(q => q.Marked("4 Pages"));
RunningApp.Tap(q => q.Button("RemovePage"));
RunningApp.WaitForElement(q => q.Marked("3 Pages"));
RunningApp.Tap(q => q.Button("Page Add/Remove Permutations"));
// This test cakes about 12 seconds so just adding a delay so WaitForElement doesn't time out
await Task.Delay(10000);
RunningApp.WaitForElement(q => q.Marked("Success"));
}
[Test]
public void BottomTabbedPageWithModalIssueTestsAllElementsPresent()
{
RunningApp.WaitForElement(q => q.Marked("Page 1"));
RunningApp.WaitForElement(q => q.Marked("Page 2"));
RunningApp.WaitForElement(q => q.Button("Pop"));
RunningApp.Screenshot("All elements present");
}
[Test]
public void BottomTabbedPageWithModalIssueTestsPopFromFirstTab()
{
RunningApp.Tap(q => q.Button("Pop"));
RunningApp.WaitForElement(q => q.Marked("Bug Repro's"));
RunningApp.Screenshot("Popped from first tab");
}
[Test]
public void BottomTabbedPageWithModalIssueTestsPopFromSecondTab()
{
RunningApp.Tap(q => q.Marked("Page 2"));
RunningApp.WaitForElement(q => q.Button("Pop 2"));
RunningApp.Screenshot("On second tab");
RunningApp.Tap(q => q.Button("Pop 2"));
RunningApp.WaitForElement(q => q.Marked("Bug Repro's"));
RunningApp.Screenshot("Popped from second tab");
}
#endif
}
}

Просмотреть файл

@ -18,7 +18,8 @@ namespace Xamarin.Forms.Controls.Issues
Content = new StackLayout
{
Children = {
new Button { Text = "Call 123 4567", AutomationId = "tel", Command = new Command(() => Device.OpenUri(new System.Uri("tel:123 4567"))) }
new Button { Text = "Call 123 4567", AutomationId = "tel", Command = new Command(() => Device.OpenUri(new System.Uri("tel:123 4567"))) },
new Button { Text = "Mail support@xamarin.com", AutomationId = "mailto", Command = new Command(() => Device.OpenUri(new System.Uri("mailto:support@xamarin.com"))) }
}
};
}

Просмотреть файл

@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Controls.Issues
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 1760, "Content set after an await is not visible", PlatformAffected.Android)]
public class Issue1760 : TestMasterDetailPage
{
const string Before = "Before";
const string After = "After";
const int Wait = 3;
protected override void Init()
{
Master = new _1760Master();
Detail = new _1760TestPage();
IsPresented = true;
}
[Preserve(AllMembers = true)]
public class _1760Master : ContentPage
{
public _1760Master()
{
var instructions = new Label { Text = $"Select one of the menu items. The detail page text should change to {Before}. After {Wait} seconds the text should change to {After}." };
var menuView = new ListView(ListViewCachingStrategy.RetainElement)
{
ItemsSource = new List<string> { "Test Page 1", "Test Page 2" }
};
menuView.ItemSelected += OnMenuClicked;
Content = new StackLayout{Children = { instructions, menuView }};
Title = "GH 1760 Test App";
}
void OnMenuClicked(object sender, SelectedItemChangedEventArgs e)
{
var mainPage = (MasterDetailPage)Parent;
mainPage.Detail = new _1760TestPage();
mainPage.IsPresented = false;
}
}
[Preserve(AllMembers = true)]
public class _1760TestPage : ContentPage
{
public async Task DisplayPage()
{
IsBusy = true;
HeaderPageContent = new Label {Text = Before, TextColor = Color.Black};
await Task.Delay(Wait * 1000);
HeaderPageContent = new Label { Text = After, TextColor = Color.Black};
IsBusy = false;
}
ContentView _headerPageContent;
public View HeaderPageContent
{
set => _headerPageContent.Content = value;
}
public _1760TestPage()
{
CreateHeaderPage();
DisplayPage();
}
void CreateHeaderPage()
{
_headerPageContent = new ContentView
{
Content = new Label { Text = "_1760 Test Page Content" },
BackgroundColor = Color.White,
Margin = 40
};
Title = "_1760 Test Page";
Content = new ScrollView
{
Content = _headerPageContent
};
}
}
}
}

Просмотреть файл

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Timers;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Controls.Issues
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 2595, "ScrollView.Content is not re-layouted on Android", PlatformAffected.Android)]
public class Issue2595 : TestMasterDetailPage
{
protected override void Init()
{
Master = new _2595Master();
Detail = new _2595ScrollPage();
IsPresented = true;
}
[Preserve(AllMembers = true)]
public class _2595Master : ContentPage
{
public _2595Master()
{
var instructions = new Label { Text = $"Select one of the menu items. The detail page text should "
+ $"display a label which disappears after 1 second and is"
+ $" replaced by an updating list of labels which grows vertically." };
var menuView = new ListView(ListViewCachingStrategy.RetainElement)
{
ItemsSource = new List<string> { "Test Page 1", "Test Page 2" }
};
menuView.ItemSelected += OnMenuClicked;
Content = new StackLayout{Children = { instructions, menuView }};
Title = "GH 2595 Test App";
}
void OnMenuClicked(object sender, SelectedItemChangedEventArgs e)
{
var mainPage = (MasterDetailPage)Parent;
mainPage.Detail = new _2595ScrollPage ();
mainPage.IsPresented = false;
}
}
[Preserve(AllMembers = true)]
public class _2595ScrollPage : ContentPage
{
readonly Timer _timer = new Timer(1000);
protected Label Label;
public _2595ScrollPage() {
Content = new ScrollView {
BackgroundColor = Color.Red,
Content = new StackLayout {
BackgroundColor = Color.BlueViolet,
Children = {
(Label = new Label {
Text = "this text should disappear after 1 sec",
BackgroundColor = Color.LightBlue,
HorizontalOptions = LayoutOptions.StartAndExpand,
})
}
}
};
}
protected StackLayout ScrollContent {
get => (Content as ScrollView).Content as StackLayout;
set => (Content as ScrollView).Content = value;
}
protected override void OnAppearing() {
base.OnAppearing();
_timer.Elapsed += (s, e) => Device.BeginInvokeOnMainThread(OnTimerElapsed);
_timer.Start();
}
void OnTimerElapsed() {
Label.Text = $"{ DateTime.Now.ToString() }: expecting {ScrollContent?.Children.Count} dates to show up.";
ScrollContent.Children.Add(new Label { Text = DateTime.Now.ToString() });
}
}
}
}

Просмотреть файл

@ -250,6 +250,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Issue1415.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue2247.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GroupListViewHeaderIndexOutOfRange.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1760.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1975.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1601.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1717.cs" />
@ -304,6 +305,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Issue1908.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1672.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue2394.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue2595.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue2983.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue2963.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue2981.cs" />
@ -372,6 +374,7 @@
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla53179_1.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RestartAppTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)BottomTabbedPageTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestPages\QuickCollectNavigationPage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestPages\ScreenshotConditionalApp.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41842.cs" />

Просмотреть файл

@ -6,9 +6,9 @@ using Xamarin.Forms.Controls.GalleryPages;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
using Xamarin.Forms.Controls.GalleryPages.VisualStateManagerGalleries;
namespace Xamarin.Forms.Controls
{
[Preserve(AllMembers = true)]
@ -19,37 +19,38 @@ namespace Xamarin.Forms.Controls
[Preserve(AllMembers = true)]
internal class CoreCarouselPage : CarouselPage
{
public CoreCarouselPage ()
public CoreCarouselPage()
{
AutomationId = "CarouselPageRoot";
Children.Add (new CoreRootPage (this, NavigationBehavior.PushModalAsync) { Title = "Page 1" });
Children.Add (new CoreRootPage (this, NavigationBehavior.PushModalAsync) { Title = "Page 2" });
Children.Add(new CoreRootPage(this, NavigationBehavior.PushModalAsync) { Title = "Page 1" });
Children.Add(new CoreRootPage(this, NavigationBehavior.PushModalAsync) { Title = "Page 2" });
}
}
[Preserve(AllMembers = true)]
internal class CoreContentPage : ContentPage
{
public CoreContentPage ()
public CoreContentPage()
{
On<iOS>().SetUseSafeArea(true);
AutomationId = "ContentPageRoot";
Content = new StackLayout { Children = { new CoreRootView (), new CorePageView (this, NavigationBehavior.PushModalAsync) } };
Content = new StackLayout { Children = { new CoreRootView(), new CorePageView(this, NavigationBehavior.PushModalAsync) } };
}
}
[Preserve(AllMembers = true)]
internal class CoreMasterDetailPage : MasterDetailPage
{
public CoreMasterDetailPage ()
public CoreMasterDetailPage()
{
AutomationId = "MasterDetailPageRoot";
var toCrashButton = new Button {Text = "Crash Me"};
var toCrashButton = new Button { Text = "Crash Me" };
var masterPage = new ContentPage {Title = "Menu", Icon = "bank.png", Content = toCrashButton};
var detailPage = new CoreRootPage (this, NavigationBehavior.PushModalAsync) { Title = "DetailPage" };
var masterPage = new ContentPage { Title = "Menu", Icon = "bank.png", Content = toCrashButton };
var detailPage = new CoreRootPage(this, NavigationBehavior.PushModalAsync) { Title = "DetailPage" };
bool toggle = false;
toCrashButton.Clicked += (sender, args) => {
toCrashButton.Clicked += (sender, args) =>
{
if (toggle)
Detail = new ContentPage { BackgroundColor = Color.Green, };
else
@ -65,14 +66,15 @@ namespace Xamarin.Forms.Controls
[Preserve(AllMembers = true)]
internal class CoreNavigationPage : NavigationPage
{
public CoreNavigationPage ()
public CoreNavigationPage()
{
AutomationId = "NavigationPageRoot";
BarBackgroundColor = Color.Maroon;
BarTextColor = Color.Yellow;
Device.StartTimer(TimeSpan.FromSeconds(2), () => {
Device.StartTimer(TimeSpan.FromSeconds(2), () =>
{
BarBackgroundColor = Color.Default;
BarTextColor = Color.Default;
@ -80,29 +82,45 @@ namespace Xamarin.Forms.Controls
});
On<iOS>().SetPrefersLargeTitles(true);
Navigation.PushAsync (new CoreRootPage (this));
Navigation.PushAsync(new CoreRootPage(this));
}
}
[Preserve(AllMembers = true)]
public class CoreTabbedPageAsBottomNavigation : CoreTabbedPageBase
{
protected override void Init()
{
On<Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
base.Init();
}
}
[Preserve (AllMembers = true)]
[Issue (IssueTracker.Github, 2456, "StackOverflow after reordering tabs in a TabbedPageView", PlatformAffected.All)]
public class CoreTabbedPage : TestTabbedPage
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 2456, "StackOverflow after reordering tabs in a TabbedPageView", PlatformAffected.All)]
public class CoreTabbedPage : CoreTabbedPageBase
{
protected override void Init ()
}
[Preserve(AllMembers = true)]
public class CoreTabbedPageBase : TestTabbedPage
{
protected override void Init()
{
}
#if APP
public CoreTabbedPage ()
public CoreTabbedPageBase()
{
AutomationId = "TabbedPageRoot";
Device.StartTimer(TimeSpan.FromSeconds(6), () => {
Device.StartTimer(TimeSpan.FromSeconds(6), () =>
{
BarBackgroundColor = Color.Maroon;
BarTextColor = Color.Yellow;
Device.StartTimer(TimeSpan.FromSeconds(6), () => {
Device.StartTimer(TimeSpan.FromSeconds(6), () =>
{
BarBackgroundColor = Color.Default;
BarTextColor = Color.Default;
@ -115,37 +133,40 @@ namespace Xamarin.Forms.Controls
Children.Add(new CoreRootPage(this, NavigationBehavior.PushModalAsync) { Title = "Tab 1" });
Children.Add(new CoreRootPage(this, NavigationBehavior.PushModalAsync) { Title = "Tab 2" });
Children.Add(new NavigationPage(new Page())
{
Title = "Rubriques",
Icon = "coffee.png",
BarBackgroundColor = Color.Blue,
BarTextColor = Color.Aqua
});
{
Title = "Rubriques",
Icon = "coffee.png",
BarBackgroundColor = Color.Blue,
BarTextColor = Color.Aqua
});
Children.Add(new NavigationPage(new Page())
{
Title = "Le Club"
});
{
Title = "Le Club"
});
Children.Add(new NavigationPage(new Page { Title = "Bookmarks" })
{
Title = "Bookmarks",
});
if (On<Android>().GetMaxItemCount() > 5)
{
Children.Add(new NavigationPage(new Page { Title = "Alertes" })
{
Title = "Bookmarks",
Title = "Notifications",
});
Children.Add(new NavigationPage(new Page { Title = "Alertes" })
{
Title = "Notifications",
});
Children.Add(new NavigationPage(new Page { Title = "My account" })
Children.Add(new NavigationPage(new Page { Title = "My account" })
{
Title = "My account",
});
Children.Add(new NavigationPage(new Page { Title = "About" })
Children.Add(new NavigationPage(new Page { Title = "About" })
{
Title = "About",
});
}
}
#endif
@ -171,13 +192,13 @@ namespace Xamarin.Forms.Controls
#endif
}
[Preserve (AllMembers = true)]
[Preserve(AllMembers = true)]
internal class CoreViewContainer
{
public string Name { get; private set; }
public Type PageType { get; private set; }
public CoreViewContainer (string name, Type pageType)
public CoreViewContainer(string name, Type pageType)
{
Name = name;
PageType = pageType;
@ -186,31 +207,34 @@ namespace Xamarin.Forms.Controls
[Preserve(AllMembers = true)]
public class CoreRootView : ListView
{
public CoreRootView ()
public CoreRootView()
{
var roots = new [] {
new CoreViewContainer ("SwapRoot - CarouselPage", typeof(CoreCarouselPage)),
var roots = new[] {
new CoreViewContainer ("SwapRoot - CarouselPage", typeof(CoreCarouselPage)),
new CoreViewContainer ("SwapRoot - ContentPage", typeof(CoreContentPage)),
new CoreViewContainer ("SwapRoot - MasterDetailPage", typeof(CoreMasterDetailPage)),
new CoreViewContainer ("SwapRoot - NavigationPage", typeof(CoreNavigationPage)),
new CoreViewContainer ("SwapRoot - TabbedPage", typeof(CoreTabbedPage)),
new CoreViewContainer ("SwapRoot - BottomNavigation TabbedPage", typeof(CoreTabbedPageAsBottomNavigation)),
};
var template = new DataTemplate (typeof(TextCell));
template.SetBinding (TextCell.TextProperty, "Name");
var template = new DataTemplate(typeof(TextCell));
template.SetBinding(TextCell.TextProperty, "Name");
ItemTemplate = template;
ItemsSource = roots;
#if PRE_APPLICATION_CLASS
ItemSelected += (sender, args) => MessagingCenter.Send (this, Messages.ChangeRoot, ((CoreViewContainer)args.SelectedItem).PageType);
#else
ItemSelected += (sender, args) => {
#else
ItemSelected += (sender, args) =>
{
var app = Application.Current as App;
if (app != null) {
var page = (Page)Activator.CreateInstance (((CoreViewContainer)args.SelectedItem).PageType);
app.SetMainPage (page);
}
if (app != null)
{
var page = (Page)Activator.CreateInstance(((CoreViewContainer)args.SelectedItem).PageType);
app.SetMainPage(page);
}
};
#endif
SetValue(AutomationProperties.NameProperty, "SwapRoot");
@ -231,7 +255,7 @@ namespace Xamarin.Forms.Controls
p.Title = title;
return p;
};
Title = title;
}
@ -239,7 +263,7 @@ namespace Xamarin.Forms.Controls
public string Title { get; set; }
public override string ToString()
{
{
// a11y: let Narrator read a friendly string instead of the default ToString()
return Title;
}
@ -354,30 +378,32 @@ namespace Xamarin.Forms.Controls
new GalleryPageFactory(() => new WebViewGallery(), "WebView Gallery - Legacy"),
};
public CorePageView (Page rootPage, NavigationBehavior navigationBehavior = NavigationBehavior.PushAsync)
public CorePageView(Page rootPage, NavigationBehavior navigationBehavior = NavigationBehavior.PushAsync)
{
_titleToPage = _pages.ToDictionary (o => o.Title);
_titleToPage = _pages.ToDictionary(o => o.Title);
// avoid NRE for root pages without NavigationBar
if (navigationBehavior == NavigationBehavior.PushAsync && rootPage.GetType () == typeof (CoreNavigationPage)) {
_pages.Add (new GalleryPageFactory(() => new NavigationBarGallery((NavigationPage)rootPage), "NavigationBar Gallery - Legacy"));
if (navigationBehavior == NavigationBehavior.PushAsync && rootPage.GetType() == typeof(CoreNavigationPage))
{
_pages.Add(new GalleryPageFactory(() => new NavigationBarGallery((NavigationPage)rootPage), "NavigationBar Gallery - Legacy"));
}
var template = new DataTemplate (typeof(TextCell));
template.SetBinding (TextCell.TextProperty, "Title");
var template = new DataTemplate(typeof(TextCell));
template.SetBinding(TextCell.TextProperty, "Title");
BindingContext = _pages;
ItemTemplate = template;
ItemsSource = _pages;
ItemSelected += async (sender, args) => {
ItemSelected += async (sender, args) =>
{
if (SelectedItem == null)
return;
var item = args.SelectedItem;
var page = item as GalleryPageFactory;
if (page != null)
await PushPage (page.Realize());
await PushPage(page.Realize());
SelectedItem = null;
};
@ -387,60 +413,67 @@ namespace Xamarin.Forms.Controls
NavigationBehavior navigationBehavior;
async Task PushPage (Page contentPage)
async Task PushPage(Page contentPage)
{
if (navigationBehavior == NavigationBehavior.PushModalAsync) {
await Navigation.PushModalAsync (contentPage);
} else {
await Navigation.PushAsync (contentPage);
if (navigationBehavior == NavigationBehavior.PushModalAsync)
{
await Navigation.PushModalAsync(contentPage);
}
else
{
await Navigation.PushAsync(contentPage);
}
}
readonly Dictionary<string, GalleryPageFactory> _titleToPage;
public async Task PushPage (string pageTitle)
public async Task PushPage(string pageTitle)
{
GalleryPageFactory pageFactory = null;
if (!_titleToPage.TryGetValue (pageTitle, out pageFactory))
if (!_titleToPage.TryGetValue(pageTitle, out pageFactory))
return;
var page = pageFactory.Realize();
await PushPage (page);
await PushPage(page);
}
}
[Preserve(AllMembers = true)]
internal class CoreRootPage : ContentPage
{
public CoreRootPage (Page rootPage, NavigationBehavior navigationBehavior = NavigationBehavior.PushAsync)
public CoreRootPage(Page rootPage, NavigationBehavior navigationBehavior = NavigationBehavior.PushAsync)
{
IStringProvider stringProvider = DependencyService.Get<IStringProvider> ();
IStringProvider stringProvider = DependencyService.Get<IStringProvider>();
Title = stringProvider.CoreGalleryTitle;
var corePageView = new CorePageView (rootPage, navigationBehavior);
var corePageView = new CorePageView(rootPage, navigationBehavior);
var searchBar = new SearchBar () {
var searchBar = new SearchBar()
{
AutomationId = "SearchBar"
};
var testCasesButton = new Button {
var testCasesButton = new Button
{
Text = "Go to Test Cases",
AutomationId = "GoToTestButton",
Command = new Command (async () => {
if (!string.IsNullOrEmpty (searchBar.Text))
await corePageView.PushPage (searchBar.Text);
Command = new Command(async () =>
{
if (!string.IsNullOrEmpty(searchBar.Text))
await corePageView.PushPage(searchBar.Text);
else
await Navigation.PushModalAsync (TestCases.GetTestCases ());
await Navigation.PushModalAsync(TestCases.GetTestCases());
})
};
var stackLayout = new StackLayout () {
var stackLayout = new StackLayout()
{
Children = {
testCasesButton,
searchBar,
new Button {
Text = "Click to Force GC",
Text = "Click to Force GC",
Command = new Command(() => {
GC.Collect ();
GC.WaitForPendingFinalizers ();
@ -451,7 +484,8 @@ namespace Xamarin.Forms.Controls
}
};
Content = new AbsoluteLayout {
Content = new AbsoluteLayout
{
Children = {
{ new CoreRootView (), new Rectangle(0, 0.0, 1, 0.35), AbsoluteLayoutFlags.All },
{ stackLayout, new Rectangle(0, 0.5, 1, 0.30), AbsoluteLayoutFlags.All },
@ -468,9 +502,9 @@ namespace Xamarin.Forms.Controls
[Preserve(AllMembers = true)]
public static class CoreGallery
{
public static Page GetMainPage ()
public static Page GetMainPage()
{
return new CoreNavigationPage ();
return new CoreNavigationPage();
}
}
}

Просмотреть файл

@ -1,5 +1,6 @@
namespace Xamarin.Forms.PlatformConfiguration.AndroidSpecific
{
using System;
using FormsElement = Forms.TabbedPage;
public static class TabbedPage
@ -65,5 +66,102 @@
SetOffscreenPageLimit(config.Element, value);
return config;
}
public static readonly BindableProperty ToolbarPlacementProperty =
BindableProperty.Create("ToolbarPlacement", typeof(ToolbarPlacement),
typeof(TabbedPage), ToolbarPlacement.Top);
public static ToolbarPlacement GetToolbarPlacement(BindableObject element)
{
return (ToolbarPlacement)element.GetValue(ToolbarPlacementProperty);
}
public static void SetToolbarPlacement(BindableObject element, ToolbarPlacement value)
{
if (element.IsSet(ToolbarPlacementProperty) && GetToolbarPlacement(element) != value)
{
throw new InvalidOperationException("Changing the tabs placement after it's been set is not supported.");
}
element.SetValue(ToolbarPlacementProperty, value);
}
public static ToolbarPlacement GetToolbarPlacement(this IPlatformElementConfiguration<Android, FormsElement> config)
{
return GetToolbarPlacement(config.Element);
}
public static IPlatformElementConfiguration<Android, FormsElement> SetToolbarPlacement(this IPlatformElementConfiguration<Android, FormsElement> config, ToolbarPlacement value)
{
SetToolbarPlacement(config.Element, value);
return config;
}
public static int GetMaxItemCount(BindableObject element)
{
if (GetToolbarPlacement(element) == ToolbarPlacement.Bottom)
{
return 5;
}
return int.MaxValue;
}
public static int GetMaxItemCount(this IPlatformElementConfiguration<Android, FormsElement> config)
{
return GetMaxItemCount(config.Element);
}
public static readonly BindableProperty BarItemColorProperty =
BindableProperty.Create("BarItemColor", typeof(Color),
typeof(TabbedPage), Color.Default);
public static Color GetBarItemColor(BindableObject element)
{
return (Color)element.GetValue(BarItemColorProperty);
}
public static void SetBarItemColor(BindableObject element, Color value)
{
element.SetValue(BarItemColorProperty, value);
}
public static Color GetBarItemColor(this IPlatformElementConfiguration<Android, FormsElement> config)
{
return GetBarItemColor(config.Element);
}
public static IPlatformElementConfiguration<Android, FormsElement> SetBarItemColor(this IPlatformElementConfiguration<Android, FormsElement> config, Color value)
{
SetBarItemColor(config.Element, value);
return config;
}
public static readonly BindableProperty BarSelectedItemColorProperty =
BindableProperty.Create("BarSelectedItemColor", typeof(Color),
typeof(TabbedPage), Color.Default);
public static Color GetBarSelectedItemColor(BindableObject element)
{
return (Color)element.GetValue(BarSelectedItemColorProperty);
}
public static void SetBarSelectedItemColor(BindableObject element, Color value)
{
element.SetValue(BarSelectedItemColorProperty, value);
}
public static IPlatformElementConfiguration<Android, FormsElement> SetBarSelectedItemColor(this IPlatformElementConfiguration<Android, FormsElement> config, Color value)
{
SetBarSelectedItemColor(config.Element, value);
return config;
}
public static Color GetBarSelectedItemColor(this IPlatformElementConfiguration<Android, FormsElement> config)
{
return GetBarSelectedItemColor(config.Element);
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
namespace Xamarin.Forms.PlatformConfiguration.AndroidSpecific
{
public enum ToolbarPlacement
{
Default,
Top,
Bottom
}
}

Просмотреть файл

@ -14,26 +14,14 @@ namespace Xamarin.Forms
public Color BarBackgroundColor
{
get
{
return (Color)GetValue(BarBackgroundColorProperty);
}
set
{
SetValue(BarBackgroundColorProperty, value);
}
get => (Color)GetValue(BarBackgroundColorProperty);
set => SetValue(BarBackgroundColorProperty, value);
}
public Color BarTextColor
{
get
{
return (Color)GetValue(BarTextColorProperty);
}
set
{
SetValue(BarTextColorProperty, value);
}
get => (Color)GetValue(BarTextColorProperty);
set => SetValue(BarTextColorProperty, value);
}
protected override Page CreateDefault(object item)

Просмотреть файл

@ -10,22 +10,40 @@ using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Support.V4.App;
using Android.Support.V4.View;
using AWidget = Android.Widget;
using Android.Views;
using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
using ADrawableCompat = Android.Support.V4.Graphics.Drawable.DrawableCompat;
using AView = Android.Views.View;
using AMenu = Android.Views.Menu;
using AColor = Android.Graphics.Color;
namespace Xamarin.Forms.Platform.Android.AppCompat
{
public class TabbedPageRenderer : VisualElementRenderer<TabbedPage>, TabLayout.IOnTabSelectedListener, ViewPager.IOnPageChangeListener, IManageFragments
public class TabbedPageRenderer : VisualElementRenderer<TabbedPage>, TabLayout.IOnTabSelectedListener, ViewPager.IOnPageChangeListener, IManageFragments, BottomNavigationView.IOnNavigationItemSelectedListener
{
Drawable _backgroundDrawable;
int? _defaultColor;
Drawable _wrappedBackgroundDrawable;
ColorStateList _originalTabTextColors;
ColorStateList _orignalTabIconColors;
ColorStateList _newTabTextColors;
ColorStateList _newTabIconColors;
bool _disposed;
FragmentManager _fragmentManager;
TabLayout _tabLayout;
BottomNavigationView _bottomNavigationView;
AWidget.RelativeLayout _relativeLayout;
bool _useAnimations = true;
FormsViewPager _viewPager;
Page _previousPage;
int[] _checkedStateSet = null;
int[] _selectedStateSet = null;
int[] _emptyStateSet = null;
int _defaultARGBColor = Color.Default.ToAndroid().ToArgb();
AColor _defaultAndroidColor = Color.Default.ToAndroid();
public TabbedPageRenderer(Context context) : base(context)
{
@ -39,6 +57,9 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
}
FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = ((FormsAppCompatActivity)Context).SupportFragmentManager);
bool IsBottomTabPlacement => (Element != null) ? Element.OnThisPlatform().GetToolbarPlacement() == ToolbarPlacement.Bottom : false;
public Color BarItemColor => (Element != null) ? Element.OnThisPlatform().GetBarItemColor() : Color.Default;
public Color BarSelectedItemColor => (Element != null) ? Element.OnThisPlatform().GetBarSelectedItemColor() : Color.Default;
internal bool UseAnimations
{
@ -63,7 +84,8 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
void ViewPager.IOnPageChangeListener.OnPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
UpdateTabBarTranslation(position, positionOffset);
if (!IsBottomTabPlacement)
UpdateTabBarTranslation(position, positionOffset);
}
void ViewPager.IOnPageChangeListener.OnPageScrollStateChanged(int state)
@ -72,13 +94,16 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
void ViewPager.IOnPageChangeListener.OnPageSelected(int position)
{
if(_previousPage != Element.CurrentPage)
if (_previousPage != Element.CurrentPage)
{
_previousPage?.SendDisappearing();
_previousPage = Element.CurrentPage;
}
Element.CurrentPage = Element.Children[position];
Element.CurrentPage.SendAppearing();
if (IsBottomTabPlacement)
_bottomNavigationView.SelectedItemId = position;
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
@ -93,10 +118,13 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
int selectedIndex = tab.Position;
if (Element.Children.Count > selectedIndex && selectedIndex >= 0)
Element.CurrentPage = Element.Children[selectedIndex];
SetIconColorFilter(tab, true);
}
void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
{
SetIconColorFilter(tab, false);
}
protected override void Dispose(bool disposing)
@ -131,6 +159,19 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
_tabLayout = null;
}
if (_bottomNavigationView != null)
{
_bottomNavigationView.SetOnNavigationItemSelectedListener(null);
_bottomNavigationView.Dispose();
_bottomNavigationView = null;
}
if (_relativeLayout != null)
{
_relativeLayout.Dispose();
_relativeLayout = null;
}
if (Element != null)
PageController.InternalChildren.CollectionChanged -= OnChildrenCollectionChanged;
@ -163,32 +204,68 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
if (e.NewElement != null)
{
if (_tabLayout == null)
if (IsBottomTabPlacement)
{
TabLayout tabs;
if (FormsAppCompatActivity.TabLayoutResource > 0)
if (_relativeLayout == null)
{
tabs = _tabLayout = activity.LayoutInflater.Inflate(FormsAppCompatActivity.TabLayoutResource, null).JavaCast<TabLayout>();
}
else
tabs = _tabLayout = new TabLayout(activity) { TabMode = TabLayout.ModeFixed, TabGravity = TabLayout.GravityFill };
FormsViewPager pager =
_viewPager =
new FormsViewPager(activity)
_relativeLayout = new AWidget.RelativeLayout(Context)
{
OverScrollMode = OverScrollMode.Never,
EnableGesture = UseAnimations,
LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent),
Adapter = new FormsFragmentPagerAdapter<Page>(e.NewElement, FragmentManager) { CountOverride = e.NewElement.Children.Count }
};
pager.Id = Platform.GenerateViewId();
pager.AddOnPageChangeListener(this);
AddView(pager);
AddView(tabs);
if (_bottomNavigationView != null)
{
_relativeLayout.RemoveView(_bottomNavigationView);
_bottomNavigationView.SetOnNavigationItemSelectedListener(null);
}
OnChildrenCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
var bottomNavigationViewLayoutParams = new AWidget.RelativeLayout.LayoutParams(
LayoutParams.MatchParent,
LayoutParams.WrapContent);
bottomNavigationViewLayoutParams.AddRule(AWidget.LayoutRules.AlignParentBottom);
_bottomNavigationView = new BottomNavigationView(Context)
{
LayoutParameters = bottomNavigationViewLayoutParams,
Id = Platform.GenerateViewId()
};
var viewPagerParams = new AWidget.RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
viewPagerParams.AddRule(AWidget.LayoutRules.Above, _bottomNavigationView.Id);
FormsViewPager pager = _viewPager = CreateFormsViewPager(activity, e.NewElement);
pager.Id = Platform.GenerateViewId();
pager.AddOnPageChangeListener(this);
_relativeLayout.AddView(pager, viewPagerParams);
_relativeLayout.AddView(_bottomNavigationView, bottomNavigationViewLayoutParams);
AddView(_relativeLayout);
}
}
else
{
if (_tabLayout == null)
{
TabLayout tabs;
if (FormsAppCompatActivity.TabLayoutResource > 0)
tabs = _tabLayout = activity.LayoutInflater.Inflate(FormsAppCompatActivity.TabLayoutResource, null).JavaCast<TabLayout>();
else
tabs = _tabLayout = new TabLayout(activity) { TabMode = TabLayout.ModeFixed, TabGravity = TabLayout.GravityFill };
FormsViewPager pager = _viewPager = CreateFormsViewPager(activity, e.NewElement);
pager.Id = Platform.GenerateViewId();
pager.AddOnPageChangeListener(this);
AddView(pager);
AddView(tabs);
}
}
OnChildrenCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
TabbedPage tabbedPage = e.NewElement;
if (tabbedPage.CurrentPage != null)
@ -199,6 +276,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
((IPageController)tabbedPage).InternalChildren.CollectionChanged += OnChildrenCollectionChanged;
UpdateBarBackgroundColor();
UpdateBarTextColor();
UpdateItemIconColor();
UpdateSwipePaging();
UpdateOffscreenPageLimit();
}
@ -215,55 +293,90 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
}
else if (e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName)
UpdateBarBackgroundColor();
else if (e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName)
else if (e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName ||
e.PropertyName == PlatformConfiguration.AndroidSpecific.TabbedPage.BarItemColorProperty.PropertyName ||
e.PropertyName == PlatformConfiguration.AndroidSpecific.TabbedPage.BarSelectedItemColorProperty.PropertyName)
{
_newTabTextColors = null;
_newTabIconColors = null;
UpdateBarTextColor();
UpdateItemIconColor();
}
else if (e.PropertyName == PlatformConfiguration.AndroidSpecific.TabbedPage.IsSwipePagingEnabledProperty.PropertyName)
UpdateSwipePaging();
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
TabLayout tabs = _tabLayout;
FormsViewPager pager = _viewPager;
Context context = Context;
int width = r - l;
int height = b - t;
tabs.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.AtMost));
var tabsHeight = 0;
var width = r - l;
var height = b - t;
if (tabs.Visibility != ViewStates.Gone)
if (IsBottomTabPlacement)
{
//MinimumHeight is only available on API 16+
if ((int)Build.VERSION.SdkInt >= 16)
tabsHeight = Math.Min(height, Math.Max(tabs.MeasuredHeight, tabs.MinimumHeight));
else
tabsHeight = Math.Min(height, tabs.MeasuredHeight);
}
if (width <= 0 || height <= 0)
return;
pager.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.AtMost), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.AtMost));
_relativeLayout.Measure(
MeasureSpec.MakeMeasureSpec(width, MeasureSpecMode.Exactly),
MeasureSpec.MakeMeasureSpec(height, MeasureSpecMode.Exactly));
if (width > 0 && height > 0)
{
PageController.ContainerArea = new Rectangle(0, context.FromPixels(tabsHeight), context.FromPixels(width), context.FromPixels(height - tabsHeight));
pager.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.AtMost), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.AtMost));
for (var i = 0; i < PageController.InternalChildren.Count; i++)
if (width > 0 && height > 0)
{
var child = PageController.InternalChildren[i] as VisualElement;
if (child == null)
continue;
IVisualElementRenderer renderer = Android.Platform.GetRenderer(child);
var navigationRenderer = renderer as NavigationPageRenderer;
if (navigationRenderer != null)
navigationRenderer.ContainerPadding = tabsHeight;
PageController.ContainerArea = new Rectangle(0, 0, context.FromPixels(width), context.FromPixels(height - _bottomNavigationView.Height));
pager.Layout(0, 0, width, b);
// We need to measure again to ensure that the tabs show up
_relativeLayout.Measure(
MeasureSpec.MakeMeasureSpec(width, MeasureSpecMode.Exactly),
MeasureSpec.MakeMeasureSpec(height, MeasureSpecMode.Exactly));
_relativeLayout.Layout(0, 0, _relativeLayout.MeasuredWidth, _relativeLayout.MeasuredHeight);
}
}
else
{
TabLayout tabs = _tabLayout;
tabs.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.AtMost));
var tabsHeight = 0;
if (tabs.Visibility != ViewStates.Gone)
{
//MinimumHeight is only available on API 16+
if ((int)Build.VERSION.SdkInt >= 16)
tabsHeight = Math.Min(height, Math.Max(tabs.MeasuredHeight, tabs.MinimumHeight));
else
tabsHeight = Math.Min(height, tabs.MeasuredHeight);
}
pager.Layout(0, 0, width, b);
// We need to measure again to ensure that the tabs show up
tabs.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(tabsHeight, MeasureSpecMode.Exactly));
tabs.Layout(0, 0, width, tabsHeight);
pager.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.AtMost), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.AtMost));
UpdateTabBarTranslation(pager.CurrentItem, 0);
if (width > 0 && height > 0)
{
PageController.ContainerArea = new Rectangle(0, context.FromPixels(tabsHeight), context.FromPixels(width), context.FromPixels(height - tabsHeight));
for (var i = 0; i < PageController.InternalChildren.Count; i++)
{
var child = PageController.InternalChildren[i] as VisualElement;
if (child == null)
continue;
IVisualElementRenderer renderer = Android.Platform.GetRenderer(child);
var navigationRenderer = renderer as NavigationPageRenderer;
if (navigationRenderer != null)
navigationRenderer.ContainerPadding = tabsHeight;
}
pager.Layout(0, 0, width, b);
// We need to measure again to ensure that the tabs show up
tabs.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(tabsHeight, MeasureSpecMode.Exactly));
tabs.Layout(0, 0, width, tabsHeight);
UpdateTabBarTranslation(pager.CurrentItem, 0);
}
}
base.OnLayout(changed, l, t, r, b);
@ -273,26 +386,60 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
{
e.Apply((o, i, c) => SetupPage((Page)o), (o, i) => TeardownPage((Page)o), Reset);
FormsViewPager pager = _viewPager;
TabLayout tabs = _tabLayout;
((FormsFragmentPagerAdapter<Page>)pager.Adapter).CountOverride = Element.Children.Count;
pager.Adapter.NotifyDataSetChanged();
if (Element.Children.Count == 0)
if (IsBottomTabPlacement)
{
tabs.RemoveAllTabs();
tabs.SetupWithViewPager(null);
FormsViewPager pager = _viewPager;
BottomNavigationView bottomNavigationView = _bottomNavigationView;
((FormsFragmentPagerAdapter<Page>)pager.Adapter).CountOverride = Element.Children.Count;
pager.Adapter.NotifyDataSetChanged();
if (Element.Children.Count == 0)
{
bottomNavigationView.Menu.Clear();
}
else
{
SetupBottomNavigationView(e);
UpdateBottomNavigationViewIcons();
bottomNavigationView.SetOnNavigationItemSelectedListener(this);
}
UpdateIgnoreContainerAreas();
}
else
{
tabs.SetupWithViewPager(pager);
UpdateTabIcons();
tabs.AddOnTabSelectedListener(this);
}
FormsViewPager pager = _viewPager;
TabLayout tabs = _tabLayout;
UpdateIgnoreContainerAreas();
((FormsFragmentPagerAdapter<Page>)pager.Adapter).CountOverride = Element.Children.Count;
pager.Adapter.NotifyDataSetChanged();
if (Element.Children.Count == 0)
{
tabs.RemoveAllTabs();
tabs.SetupWithViewPager(null);
}
else
{
tabs.SetupWithViewPager(pager);
UpdateTabIcons();
tabs.AddOnTabSelectedListener(this);
}
UpdateIgnoreContainerAreas();
}
}
FormsViewPager CreateFormsViewPager(Context context, TabbedPage tabbedPage)
{
return new FormsViewPager(context)
{
OverScrollMode = OverScrollMode.Never,
EnableGesture = UseAnimations,
LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent),
Adapter = new FormsFragmentPagerAdapter<Page>(tabbedPage, FragmentManager) { CountOverride = tabbedPage.Children.Count }
};
}
void TeardownPage(Page page)
@ -317,8 +464,17 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
{
var page = (Page)sender;
var index = Element.Children.IndexOf(page);
TabLayout.Tab tab = _tabLayout.GetTabAt(index);
tab.SetText(page.Title);
if (IsBottomTabPlacement)
{
IMenuItem tab = _bottomNavigationView.Menu.GetItem(index);
tab.SetTitle(page.Title);
}
else
{
TabLayout.Tab tab = _tabLayout.GetTabAt(index);
tab.SetText(page.Title);
}
}
}
@ -347,6 +503,9 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
void UpdateTabBarTranslation(int position, float offset)
{
if (IsDisposed)
return;
TabLayout tabs = _tabLayout;
if (position >= PageController.InternalChildren.Count)
@ -385,8 +544,62 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
}
}
void SetupBottomNavigationView(NotifyCollectionChangedEventArgs e)
{
if (IsDisposed)
return;
BottomNavigationView bottomNavigationView = _bottomNavigationView;
int startingIndex = 0;
if (e.Action == NotifyCollectionChangedAction.Add && e.NewStartingIndex == bottomNavigationView.Menu.Size())
startingIndex = e.NewStartingIndex;
else if (e.Action == NotifyCollectionChangedAction.Remove && (e.OldStartingIndex + 1) == bottomNavigationView.Menu.Size())
{
startingIndex = Element.Children.Count;
bottomNavigationView.Menu.RemoveItem(e.OldStartingIndex);
}
else
bottomNavigationView.Menu.Clear();
for (var i = startingIndex; i < Element.Children.Count; i++)
{
Page child = Element.Children[i];
var menuItem = bottomNavigationView.Menu.Add(AMenu.None, i, i, child.Title);
if (Element.CurrentPage == child)
bottomNavigationView.SelectedItemId = menuItem.ItemId;
}
if (Element.CurrentPage == null && Element.Children.Count > 0)
Element.CurrentPage = Element.Children[0];
}
void UpdateBottomNavigationViewIcons()
{
if (IsDisposed)
return;
BottomNavigationView bottomNavigationView = _bottomNavigationView;
for (var i = 0; i < Element.Children.Count; i++)
{
Page child = Element.Children[i];
FileImageSource icon = child.Icon;
if (string.IsNullOrEmpty(icon))
continue;
var menuItem = bottomNavigationView.Menu.GetItem(i);
menuItem.SetIcon(ResourceManager.IdFromTitle(icon, ResourceManager.DrawableClass));
}
}
void UpdateTabIcons()
{
if (IsDisposed)
return;
TabLayout tabs = _tabLayout;
if (tabs.TabCount != Element.Children.Count)
@ -407,55 +620,290 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
protected virtual void SetTabIcon(TabLayout.Tab tab, FileImageSource icon)
{
tab.SetIcon(ResourceManager.IdFromTitle(icon, ResourceManager.DrawableClass));
this.SetIconColorFilter(tab);
}
void UpdateBarBackgroundColor()
{
if (_disposed || _tabLayout == null)
if (IsDisposed)
return;
Color tintColor = Element.BarBackgroundColor;
if (Forms.IsLollipopOrNewer)
if (IsBottomTabPlacement)
{
Color tintColor = Element.BarBackgroundColor;
if (tintColor.IsDefault)
_tabLayout.BackgroundTintMode = null;
else
{
_tabLayout.BackgroundTintMode = PorterDuff.Mode.Src;
_tabLayout.BackgroundTintList = ColorStateList.ValueOf(tintColor.ToAndroid());
}
_bottomNavigationView.SetBackground(null);
else if (!tintColor.IsDefault)
_bottomNavigationView.SetBackgroundColor(tintColor.ToAndroid());
}
else
{
if (tintColor.IsDefault && _backgroundDrawable != null)
_tabLayout.SetBackground(_backgroundDrawable);
else if (!tintColor.IsDefault)
Color tintColor = Element.BarBackgroundColor;
if (Forms.IsLollipopOrNewer)
{
if (_backgroundDrawable == null)
_backgroundDrawable = _tabLayout.Background;
_tabLayout.SetBackgroundColor(tintColor.ToAndroid());
if (tintColor.IsDefault)
_tabLayout.BackgroundTintMode = null;
else
{
_tabLayout.BackgroundTintMode = PorterDuff.Mode.Src;
_tabLayout.BackgroundTintList = ColorStateList.ValueOf(tintColor.ToAndroid());
}
}
else
{
if (tintColor.IsDefault && _backgroundDrawable != null)
_tabLayout.SetBackground(_backgroundDrawable);
else if (!tintColor.IsDefault)
{
// if you don't create a new drawable then SetBackgroundColor
// just sets the color on the background drawable that's saved
// it doesn't create a new one
if (_backgroundDrawable == null && _tabLayout.Background != null)
{
_backgroundDrawable = _tabLayout.Background;
_wrappedBackgroundDrawable = ADrawableCompat.Wrap(_tabLayout.Background).Mutate();
}
if (_wrappedBackgroundDrawable != null)
_tabLayout.Background = _wrappedBackgroundDrawable;
_tabLayout.SetBackgroundColor(tintColor.ToAndroid());
}
}
}
}
protected virtual ColorStateList GetItemTextColorStates()
{
if (IsDisposed)
return null;
if (_originalTabTextColors == null)
_originalTabTextColors = (IsBottomTabPlacement) ? _bottomNavigationView.ItemTextColor : _tabLayout.TabTextColors;
Color barItemColor = BarItemColor;
Color barTextColor = Element.BarTextColor;
Color barSelectedItemColor = BarSelectedItemColor;
if (barItemColor.IsDefault && barTextColor.IsDefault && barSelectedItemColor.IsDefault)
return _originalTabTextColors;
if (_newTabTextColors != null)
return _newTabTextColors;
int checkedColor;
int defaultColor;
if (!barTextColor.IsDefault)
{
checkedColor = barTextColor.ToAndroid().ToArgb();
defaultColor = checkedColor;
}
else
{
defaultColor = barItemColor.ToAndroid().ToArgb();
if (barItemColor.IsDefault && _originalTabTextColors != null)
defaultColor = _originalTabTextColors.DefaultColor;
checkedColor = defaultColor;
if (!barSelectedItemColor.IsDefault)
checkedColor = barSelectedItemColor.ToAndroid().ToArgb();
}
_newTabTextColors = GetColorStateList(defaultColor, checkedColor);
return _newTabTextColors;
}
protected virtual ColorStateList GetItemIconTintColorState()
{
if (IsDisposed)
return null;
if (IsBottomTabPlacement)
{
if (_orignalTabIconColors == null)
_orignalTabIconColors = _bottomNavigationView.ItemIconTintList;
}
// this ensures that existing behavior doesn't change
else if (!IsBottomTabPlacement && BarSelectedItemColor.IsDefault && BarItemColor.IsDefault)
return null;
Color barItemColor = BarItemColor;
Color barSelectedItemColor = BarSelectedItemColor;
if (barItemColor.IsDefault && barSelectedItemColor.IsDefault)
return _orignalTabIconColors;
if (_newTabIconColors != null)
return _newTabIconColors;
int defaultColor = barItemColor.ToAndroid().ToArgb();
if (barItemColor.IsDefault && _orignalTabIconColors != null)
defaultColor = _orignalTabIconColors.DefaultColor;
int checkedColor = defaultColor;
if (!barSelectedItemColor.IsDefault)
checkedColor = barSelectedItemColor.ToAndroid().ToArgb();
_newTabIconColors = GetColorStateList(defaultColor, checkedColor);
return _newTabIconColors;
}
public bool OnNavigationItemSelected(IMenuItem item)
{
if (Element == null || IsDisposed)
return false;
int selectedIndex = item.Order;
if (_bottomNavigationView.SelectedItemId != item.ItemId && Element.Children.Count > selectedIndex && selectedIndex >= 0)
Element.CurrentPage = Element.Children[selectedIndex];
return true;
}
bool IsDisposed
{
get
{
if (IsBottomTabPlacement)
{
if (_disposed || _relativeLayout == null || _bottomNavigationView == null)
return true;
}
else
{
if (_disposed || _tabLayout == null)
return true;
}
return false;
}
}
void UpdateItemIconColor()
{
if (IsDisposed)
return;
if (IsBottomTabPlacement)
_bottomNavigationView.ItemIconTintList = GetItemIconTintColorState() ?? _orignalTabIconColors;
else
{
var colors = GetItemIconTintColorState() ?? _orignalTabIconColors;
for (int i = 0; i < _tabLayout.TabCount; i++)
{
TabLayout.Tab tab = _tabLayout.GetTabAt(i);
this.SetIconColorFilter(tab);
}
}
}
void UpdateBarTextColor()
{
if (_disposed || _tabLayout == null)
if (IsDisposed)
return;
int currentColor = _tabLayout.TabTextColors.DefaultColor;
var colors = GetItemTextColorStates() ?? _originalTabTextColors;
if (IsBottomTabPlacement)
_bottomNavigationView.ItemTextColor = colors;
else
_tabLayout.TabTextColors = colors;
}
if (!_defaultColor.HasValue)
_defaultColor = currentColor;
void SetIconColorFilter(TabLayout.Tab tab)
{
SetIconColorFilter(tab, _tabLayout.GetTabAt(_tabLayout.SelectedTabPosition) == tab);
}
Color newTextColor = Element.BarTextColor;
int newTextColorArgb = newTextColor.ToAndroid().ToArgb();
void SetIconColorFilter(TabLayout.Tab tab, bool selected)
{
var icon = tab.Icon;
if (icon == null)
return;
if (!newTextColor.IsDefault && currentColor != newTextColorArgb)
_tabLayout.SetTabTextColors(newTextColorArgb, newTextColorArgb);
else if (newTextColor.IsDefault && _defaultColor.HasValue && currentColor != _defaultColor)
_tabLayout.SetTabTextColors(_defaultColor.Value, _defaultColor.Value);
var colors = GetItemIconTintColorState();
if (colors == null)
ADrawableCompat.SetTintList(icon, null);
else
{
int[] _stateSet = null;
if (selected)
_stateSet = GetSelectedStateSet();
else
_stateSet = GetEmptyStateSet();
if (colors.GetColorForState(_stateSet, _defaultAndroidColor) == _defaultARGBColor)
ADrawableCompat.SetTintList(icon, null);
else
{
var wrappedIcon = ADrawableCompat.Wrap(icon);
if (wrappedIcon != icon)
{
icon = wrappedIcon;
tab.SetIcon(wrappedIcon);
}
icon.Mutate();
icon.SetState(_stateSet);
ADrawableCompat.SetTintList(icon, colors);
}
}
icon.InvalidateSelf();
}
int[] GetSelectedStateSet()
{
if (IsBottomTabPlacement)
{
if (_checkedStateSet == null)
_checkedStateSet = new int[] { global::Android.Resource.Attribute.StateChecked };
return _checkedStateSet;
}
else
{
if (_selectedStateSet == null)
_selectedStateSet = GetStateSet(AView.SelectedStateSet);
return _selectedStateSet;
}
}
int[] GetEmptyStateSet()
{
if (_emptyStateSet == null)
_emptyStateSet = GetStateSet(AView.EmptyStateSet);
return _emptyStateSet;
}
int[] GetStateSet(System.Collections.Generic.IList<int> stateSet)
{
var results = new int[stateSet.Count];
for (int i = 0; i < results.Length; i++)
results[i] = stateSet[i];
return results;
}
ColorStateList GetColorStateList(int defaultColor, int checkedColor)
{
int[][] states = new int[2][];
int[] colors = new int[2];
states[0] = GetSelectedStateSet();
colors[0] = checkedColor;
states[1] = GetEmptyStateSet();
colors[1] = defaultColor;
return new ColorStateList(states, colors);
}
}
}

Просмотреть файл

@ -1,3 +1,5 @@
using System;
using System.ComponentModel;
using Android.Content;
using Android.Views;
@ -23,6 +25,8 @@ namespace Xamarin.Forms.Platform.Android
RemoveAllViews();
UnsubscribeChildLayoutChanges();
_childView = value;
if (_childView == null)
@ -36,6 +40,29 @@ namespace Xamarin.Forms.Platform.Android
renderer.View.RemoveFromParent();
AddView(renderer.View);
if (_childView is Layout layout)
{
layout.LayoutChanged += OnChildLayoutChanged;
}
}
}
void OnChildLayoutChanged(object sender, EventArgs e)
{
if (IsInLayout)
{
return;
}
RequestLayout();
}
void UnsubscribeChildLayoutChanges()
{
if (_childView is Layout layout)
{
layout.LayoutChanged -= OnChildLayoutChanged;
}
}
@ -45,6 +72,8 @@ namespace Xamarin.Forms.Platform.Android
if (disposing)
{
UnsubscribeChildLayoutChanges();
if (ChildCount > 0)
GetChildAt(0).Dispose();
RemoveAllViews();

Просмотреть файл

@ -260,7 +260,7 @@ namespace Xamarin.Forms
{
NSUrl url;
if (uri.Scheme == "tel")
if (uri.Scheme == "tel" || uri.Scheme == "mailto")
url = new NSUrl(uri.AbsoluteUri);
else
url = NSUrl.FromString(uri.OriginalString) ?? new NSUrl(uri.Scheme, uri.Host, uri.PathAndQuery);