зеркало из https://github.com/DeGsoft/maui-linux.git
Handle DataTemplateSelector on iOS/Android CollectionView (#5429)
* DataTemplateSelector working on Android for ItemTemplate and EmptyTemplate * Demonstrate DataTemplateSelector working with EmptyViewTemplate * Handle DataTemplateSelector on iOS CollectionView * Add UI test Fixes #4826 * Temporarily patching EditorRenderer to get tests running * Add test for binding errors; Fix binding errors on Android; * Fix binding errors for iOS * Add flag setting to allow UI test to run * Fix rebase errors
This commit is contained in:
Родитель
517642b551
Коммит
85e75046c2
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<controls:TestContentPage
|
||||
xmlns:controls="clr-namespace:Xamarin.Forms.Controls"
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Xamarin.Forms.Controls.Issues.CollectionViewBindingErrors">
|
||||
<StackLayout>
|
||||
<Label Text="The label below should read 'Binding Errors: 0'; if the number of binding errors is greater than zero, this test has failed."></Label>
|
||||
|
||||
<Label x:Name="BindingErrorCount" Text="Binding Errors: 0"></Label>
|
||||
|
||||
<CollectionView x:Name="CollectionView" ItemsSource="{Binding ItemsList}">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackLayout>
|
||||
<Image Source="{Binding Image}" WidthRequest="100" HeightRequest="100"/>
|
||||
<Label Text="{Binding Caption}"></Label>
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
</StackLayout>
|
||||
</controls:TestContentPage>
|
|
@ -0,0 +1,141 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xamarin.Forms.CustomAttributes;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
#if UITEST
|
||||
using Xamarin.UITest;
|
||||
using NUnit.Framework;
|
||||
using Xamarin.Forms.Core.UITests;
|
||||
#endif
|
||||
|
||||
namespace Xamarin.Forms.Controls.Issues
|
||||
{
|
||||
#if UITEST
|
||||
[Category(UITestCategories.CollectionView)]
|
||||
#endif
|
||||
#if APP
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
#endif
|
||||
[Preserve(AllMembers = true)]
|
||||
[Issue(IssueTracker.None, 68000, "Binding errors when CollectionView ItemsSource is set with a binding",
|
||||
PlatformAffected.Android)]
|
||||
public partial class CollectionViewBindingErrors : TestContentPage
|
||||
{
|
||||
public CollectionViewBindingErrors()
|
||||
{
|
||||
#if APP
|
||||
Device.SetFlags(new List<string> { CollectionView.CollectionViewExperimental });
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
var listener = new CountBindingErrors(BindingErrorCount);
|
||||
Log.Listeners.Add(listener);
|
||||
Disappearing += (obj, args) => { Log.Listeners.Remove(listener); };
|
||||
|
||||
BindingContext = new BindingErrorsViewModel();
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#if UITEST
|
||||
[Test]
|
||||
public void CollectionViewBindingErrorsShouldBeZero()
|
||||
{
|
||||
RunningApp.WaitForElement("Binding Errors: 0");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
public class CollectionViewGalleryTestItem
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public string Caption { get; set; }
|
||||
public string Image { get; set; }
|
||||
public int Index { get; set; }
|
||||
|
||||
public CollectionViewGalleryTestItem(DateTime date, string caption, string image, int index)
|
||||
{
|
||||
Date = date;
|
||||
Caption = caption;
|
||||
Image = image;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Item: {Index}";
|
||||
}
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
internal class CountBindingErrors : LogListener
|
||||
{
|
||||
private readonly Label _errorCount;
|
||||
int _count;
|
||||
|
||||
public CountBindingErrors(Label errorCount)
|
||||
{
|
||||
_errorCount = errorCount;
|
||||
}
|
||||
|
||||
public override void Warning(string category, string message)
|
||||
{
|
||||
if (category == "Binding")
|
||||
{
|
||||
_count += 1;
|
||||
}
|
||||
|
||||
_errorCount.Text = $"Binding Errors: {_count}";
|
||||
}
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
internal class BindingErrorsViewModel
|
||||
{
|
||||
readonly string[] _imageOptions = {
|
||||
"cover1.jpg",
|
||||
"oasis.jpg",
|
||||
"photo.jpg",
|
||||
"Vegetables.jpg",
|
||||
"Fruits.jpg",
|
||||
"FlowerBuds.jpg",
|
||||
"Legumes.jpg"
|
||||
};
|
||||
|
||||
List<CollectionViewGalleryTestItem> GenerateList()
|
||||
{
|
||||
var items = new List<CollectionViewGalleryTestItem>();
|
||||
var images = _imageOptions;
|
||||
|
||||
for (int n = 0; n < 100; n++)
|
||||
{
|
||||
items.Add(new CollectionViewGalleryTestItem(DateTime.Now.AddDays(n),
|
||||
$"Item: {n}", images[n % images.Length], n));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
List<CollectionViewGalleryTestItem> _items;
|
||||
|
||||
public List<CollectionViewGalleryTestItem> ItemsList
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_items == null)
|
||||
{
|
||||
_items = GenerateList();
|
||||
}
|
||||
|
||||
return _items;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,10 @@
|
|||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Issue4919.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Issue5461.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)CollectionViewBindingErrors.xaml.cs">
|
||||
<DependentUpon>CollectionViewBindingErrors.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Issue2102.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Issue1588.xaml.cs">
|
||||
<DependentUpon>Issue1588.xaml</DependentUpon>
|
||||
|
@ -1135,4 +1139,10 @@
|
|||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)CollectionViewBindingErrors.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
|
||||
{
|
||||
[Preserve(AllMembers = true)]
|
||||
public class CollectionViewGalleryTestItem
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public string Caption { get; set; }
|
||||
public string Image { get; set; }
|
||||
public int Index { get; set; }
|
||||
|
||||
public CollectionViewGalleryTestItem(DateTime date, string caption, string image, int index)
|
||||
{
|
||||
Date = date;
|
||||
Caption = caption;
|
||||
Image = image;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Item: {Index}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,9 +27,11 @@
|
|||
|
||||
GalleryBuilder.NavButton("ItemSizing Strategy", () =>
|
||||
new VariableSizeTemplateGridGallery (ItemsLayoutOrientation.Horizontal), Navigation),
|
||||
|
||||
|
||||
GalleryBuilder.NavButton("DataTemplateSelector", () =>
|
||||
new DataTemplateSelectorGallery(), Navigation),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries;assembly=Xamarin.Forms.Controls"
|
||||
x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.DataTemplateSelectorGallery">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<DataTemplate x:Key="DefaultTemplate">
|
||||
<Grid HeightRequest="50">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Image Source="coffee.png" AutomationId="weekday"/>
|
||||
|
||||
<Label Grid.Row="1" Text="{Binding Date, StringFormat='{}{0:dddd}'}"></Label>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="WeekendTemplate">
|
||||
<Grid HeightRequest="50">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Image Source="oasis.jpg" AutomationId="weekend"/>
|
||||
|
||||
<Label Grid.Row="1" Text="It's the weekend! Woot!"></Label>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="EmptyTemplate">
|
||||
<StackLayout>
|
||||
<Label Text="{Binding ., StringFormat='({0}) does not match any day of the week.'}"></Label>
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="SymbolsTemplate">
|
||||
<StackLayout BackgroundColor="Red">
|
||||
<Label Text="{Binding ., StringFormat='({0}) _definitely_ does not match any day of the week.'}"></Label>
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
|
||||
<local:WeekendSelector x:Key="WeekendSelector"
|
||||
DefaultTemplate="{StaticResource DefaultTemplate}"
|
||||
FridayTemplate="{StaticResource WeekendTemplate}" />
|
||||
|
||||
<local:SearchTermSelector x:Key="SearchTermSelector"
|
||||
DefaultTemplate="{StaticResource EmptyTemplate}"
|
||||
SymbolsTemplate="{StaticResource SymbolsTemplate}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentPage.Content>
|
||||
|
||||
<StackLayout>
|
||||
|
||||
<SearchBar x:Name="SearchBar" Placeholder="Day of Week Filter" />
|
||||
|
||||
<CollectionView x:Name="CollectionView" ItemTemplate="{StaticResource WeekendSelector}"
|
||||
EmptyViewTemplate="{StaticResource SearchTermSelector}"/>
|
||||
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class DataTemplateSelectorGallery : ContentPage
|
||||
{
|
||||
DemoFilteredItemSource _demoFilteredItemSource;
|
||||
|
||||
public DataTemplateSelectorGallery()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_demoFilteredItemSource = new DemoFilteredItemSource(filter: ItemMatches);
|
||||
|
||||
CollectionView.ItemsSource = _demoFilteredItemSource.Items;
|
||||
|
||||
SearchBar.SearchCommand = new Command(() =>
|
||||
{
|
||||
_demoFilteredItemSource.FilterItems(SearchBar.Text);
|
||||
CollectionView.EmptyView = SearchBar.Text;
|
||||
});
|
||||
}
|
||||
|
||||
private bool ItemMatches(string filter, CollectionViewGalleryTestItem item)
|
||||
{
|
||||
if (String.IsNullOrEmpty(filter))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return item.Date.DayOfWeek.ToString().ToLower().Contains(filter.ToLower());
|
||||
}
|
||||
}
|
||||
|
||||
public class WeekendSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate FridayTemplate { get; set; }
|
||||
public DataTemplate DefaultTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||
{
|
||||
var dow = ((CollectionViewGalleryTestItem)item).Date.DayOfWeek;
|
||||
|
||||
return dow == DayOfWeek.Saturday || dow == DayOfWeek.Sunday
|
||||
? FridayTemplate
|
||||
: DefaultTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
public class SearchTermSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate DefaultTemplate { get; set; }
|
||||
public DataTemplate SymbolsTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||
{
|
||||
var search = ((string)item);
|
||||
|
||||
return search.Any(c => !char.IsLetter(c))
|
||||
? SymbolsTemplate
|
||||
: DefaultTemplate;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,10 +8,11 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
|
|||
internal class DemoFilteredItemSource
|
||||
{
|
||||
readonly List<CollectionViewGalleryTestItem> _source;
|
||||
private readonly Func<string, CollectionViewGalleryTestItem, bool> _filter;
|
||||
|
||||
public ObservableCollection<CollectionViewGalleryTestItem> Items { get; }
|
||||
|
||||
public DemoFilteredItemSource(int count = 50)
|
||||
public DemoFilteredItemSource(int count = 50, Func<string, CollectionViewGalleryTestItem, bool> filter = null)
|
||||
{
|
||||
_source = new List<CollectionViewGalleryTestItem>();
|
||||
|
||||
|
@ -32,11 +33,18 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
|
|||
$"{images[n % images.Length]}, {n}", images[n % images.Length], n));
|
||||
}
|
||||
Items = new ObservableCollection<CollectionViewGalleryTestItem>(_source);
|
||||
|
||||
_filter = filter ?? ItemMatches;
|
||||
}
|
||||
|
||||
private bool ItemMatches(string filter, CollectionViewGalleryTestItem item)
|
||||
{
|
||||
return item.Caption.ToLower().Contains(filter.ToLower());
|
||||
}
|
||||
|
||||
public void FilterItems(string filter)
|
||||
{
|
||||
var filteredItems = _source.Where(item => item.Caption.ToLower().Contains(filter.ToLower())).ToList();
|
||||
var filteredItems = _source.Where(item => _filter(filter, item)).ToList();
|
||||
|
||||
foreach (CollectionViewGalleryTestItem collectionViewGalleryTestItem in _source)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.EmptyViewGalleries
|
||||
{
|
||||
[Preserve(AllMembers = true)]
|
||||
public class EmptyViewGalleryFilterInfo : INotifyPropertyChanged
|
||||
{
|
||||
string _filter;
|
||||
|
||||
public string Filter
|
||||
{
|
||||
get => _filter;
|
||||
set
|
||||
{
|
||||
_filter = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.Xaml;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.EmptyViewGalleries
|
||||
{
|
||||
|
@ -27,27 +24,4 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.EmptyViewG
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
public class EmptyViewGalleryFilterInfo : INotifyPropertyChanged
|
||||
{
|
||||
string _filter;
|
||||
|
||||
public string Filter
|
||||
{
|
||||
get => _filter;
|
||||
set
|
||||
{
|
||||
_filter = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
|
||||
{
|
||||
[Preserve(AllMembers = true)]
|
||||
public class CollectionViewGalleryTestItem
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public string Caption { get; set; }
|
||||
public string Image { get; set; }
|
||||
public int Index { get; set; }
|
||||
|
||||
public CollectionViewGalleryTestItem(DateTime date, string caption, string image, int index)
|
||||
{
|
||||
Date = date;
|
||||
Caption = caption;
|
||||
Image = image;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Item: {Index}";
|
||||
}
|
||||
}
|
||||
|
||||
internal enum ItemsSourceType
|
||||
internal enum ItemsSourceType
|
||||
{
|
||||
List,
|
||||
ObservableCollection,
|
||||
|
|
|
@ -57,6 +57,9 @@
|
|||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\CollectionViewGalleries\SelectionGalleries\PreselectedItemsGallery.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\CollectionViewGalleries\DataTemplateSelectorGallery.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\MapWithItemsSourceGallery.xaml">
|
||||
|
|
|
@ -280,7 +280,7 @@ namespace Xamarin.Forms.Core.UITests
|
|||
}
|
||||
}
|
||||
|
||||
[TestCase("EmptyView", "EmptyView (load simulation)", "photo")]
|
||||
[TestCase("EmptyView", "EmptyView (load simulation)", "photo")]
|
||||
public void VisitAndCheckItem(string collectionTestName, string subgallery, string item)
|
||||
{
|
||||
VisitInitialGallery(collectionTestName);
|
||||
|
@ -290,5 +290,18 @@ namespace Xamarin.Forms.Core.UITests
|
|||
|
||||
App.WaitForElement(t => t.Marked(item));
|
||||
}
|
||||
|
||||
[TestCase("DataTemplate Galleries", "DataTemplateSelector")]
|
||||
void VisitAndCheckForItems(string collectionTestName, string subGallery)
|
||||
{
|
||||
VisitInitialGallery(collectionTestName);
|
||||
|
||||
App.WaitForElement(t => t.Marked(subGallery));
|
||||
App.Tap(t => t.Marked(subGallery));
|
||||
|
||||
App.WaitForElement("weekend");
|
||||
App.WaitForElement("weekday");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
// So we give it an alternate delegate for creating the views
|
||||
|
||||
ItemsViewAdapter = new ItemsViewAdapter(ItemsView,
|
||||
(renderer, context) => new SizedItemContentView(renderer, context, () => Width, () => Height));
|
||||
(view, context) => new SizedItemContentView(context, () => Width, () => Height));
|
||||
|
||||
SwapAdapter(ItemsViewAdapter, false);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ using Android.Content;
|
|||
using Android.Support.V7.Widget;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Java.Lang;
|
||||
using Object = Java.Lang.Object;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
|
@ -10,12 +12,24 @@ namespace Xamarin.Forms.Platform.Android
|
|||
{
|
||||
public object EmptyView { get; set; }
|
||||
public DataTemplate EmptyViewTemplate { get; set; }
|
||||
protected readonly ItemsView ItemsView;
|
||||
|
||||
public override int ItemCount => 1;
|
||||
|
||||
public EmptyViewAdapter()
|
||||
public EmptyViewAdapter(ItemsView itemsView)
|
||||
{
|
||||
CollectionView.VerifyCollectionViewFlagEnabled(nameof(EmptyViewAdapter));
|
||||
ItemsView = itemsView;
|
||||
}
|
||||
|
||||
public override void OnViewRecycled(Object holder)
|
||||
{
|
||||
if (holder is TemplatedItemViewHolder templatedItemViewHolder)
|
||||
{
|
||||
templatedItemViewHolder.Recycle(ItemsView);
|
||||
}
|
||||
|
||||
base.OnViewRecycled(holder);
|
||||
}
|
||||
|
||||
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
|
||||
|
@ -25,20 +39,25 @@ namespace Xamarin.Forms.Platform.Android
|
|||
return;
|
||||
}
|
||||
|
||||
if (holder is TemplatedItemViewHolder templatedItemViewHolder)
|
||||
{
|
||||
// Use EmptyView as the binding context for the template
|
||||
templatedItemViewHolder.Bind(EmptyView, ItemsView);
|
||||
}
|
||||
|
||||
if (!(holder is EmptyViewHolder emptyViewHolder))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Use EmptyView as the binding context for the template
|
||||
BindableObject.SetInheritedBindingContext(emptyViewHolder.View, EmptyView);
|
||||
}
|
||||
|
||||
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
|
||||
{
|
||||
var context = parent.Context;
|
||||
|
||||
if (EmptyViewTemplate == null)
|
||||
var template = EmptyViewTemplate;
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
if (!(EmptyView is View formsView))
|
||||
{
|
||||
|
@ -47,29 +66,13 @@ namespace Xamarin.Forms.Platform.Android
|
|||
}
|
||||
|
||||
// EmptyView is a Forms View; display that
|
||||
var itemContentControl = new SizedItemContentView(CreateRenderer(formsView, context), context,
|
||||
() => parent.Width, () => parent.Height);
|
||||
var itemContentControl = new SizedItemContentView(context, () => parent.Width, () => parent.Height);
|
||||
itemContentControl.RealizeContent(formsView);
|
||||
return new EmptyViewHolder(itemContentControl, formsView);
|
||||
}
|
||||
|
||||
// We have a template, so create a view from it
|
||||
var templateElement = EmptyViewTemplate.CreateContent() as View;
|
||||
var templatedItemContentControl = new SizedItemContentView(CreateRenderer(templateElement, context),
|
||||
context, () => parent.Width, () => parent.Height);
|
||||
return new EmptyViewHolder(templatedItemContentControl, templateElement);
|
||||
}
|
||||
|
||||
IVisualElementRenderer CreateRenderer(View view, Context context)
|
||||
{
|
||||
if (view == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(view));
|
||||
}
|
||||
|
||||
var renderer = Platform.CreateRenderer(view, context);
|
||||
Platform.SetRenderer(view, renderer);
|
||||
|
||||
return renderer;
|
||||
var itemContentView = new SizedItemContentView(parent.Context, () => parent.Width, () => parent.Height);
|
||||
return new TemplatedItemViewHolder(itemContentView, template);
|
||||
}
|
||||
|
||||
static TextView CreateTextView(string text, Context context)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using Android.Content;
|
||||
using Android.Views;
|
||||
|
||||
|
@ -5,21 +6,31 @@ namespace Xamarin.Forms.Platform.Android
|
|||
{
|
||||
internal class ItemContentView : ViewGroup
|
||||
{
|
||||
protected readonly IVisualElementRenderer Content;
|
||||
protected IVisualElementRenderer Content;
|
||||
|
||||
public ItemContentView(IVisualElementRenderer content, Context context) : base(context)
|
||||
public ItemContentView(Context context) : base(context)
|
||||
{
|
||||
Content = content;
|
||||
AddContent();
|
||||
}
|
||||
|
||||
void AddContent()
|
||||
internal void RealizeContent(View view)
|
||||
{
|
||||
Content = CreateRenderer(view, Context);
|
||||
AddView(Content.View);
|
||||
}
|
||||
|
||||
internal void Recycle()
|
||||
{
|
||||
RemoveView(Content.View);
|
||||
Content = null;
|
||||
}
|
||||
|
||||
protected override void OnLayout(bool changed, int l, int t, int r, int b)
|
||||
{
|
||||
if (Content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var size = Context.FromPixels(r - l, b - t);
|
||||
|
||||
Content.Element.Layout(new Rectangle(Point.Zero, size));
|
||||
|
@ -29,6 +40,12 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
if (Content == null)
|
||||
{
|
||||
SetMeasuredDimension(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int pixelWidth = MeasureSpec.GetSize(widthMeasureSpec);
|
||||
int pixelHeight = MeasureSpec.GetSize(heightMeasureSpec);
|
||||
|
||||
|
@ -53,5 +70,23 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
SetMeasuredDimension(pixelWidth, pixelHeight);
|
||||
}
|
||||
|
||||
static IVisualElementRenderer CreateRenderer(View view, Context context)
|
||||
{
|
||||
if (view == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(view));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var renderer = Platform.CreateRenderer(view, context);
|
||||
Platform.SetRenderer(view, renderer);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,26 +8,26 @@ using ViewGroup = Android.Views.ViewGroup;
|
|||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
// TODO hartez 2018/07/25 14:43:04 Experiment with an ItemSource property change as _adapter.notifyDataSetChanged
|
||||
// TODO hartez 2018/07/25 14:43:04 Experiment with an ItemSource property change as _adapter.notifyDataSetChanged
|
||||
|
||||
public class ItemsViewAdapter : RecyclerView.Adapter
|
||||
{
|
||||
protected readonly ItemsView ItemsView;
|
||||
readonly Func<IVisualElementRenderer, Context, AView> _createView;
|
||||
readonly Func<View, Context, ItemContentView> _createItemContentView;
|
||||
internal readonly IItemsViewSource ItemsSource;
|
||||
bool _disposed;
|
||||
|
||||
internal ItemsViewAdapter(ItemsView itemsView, Func<IVisualElementRenderer, Context, AView> createView = null)
|
||||
internal ItemsViewAdapter(ItemsView itemsView, Func<View, Context, ItemContentView> createItemContentView = null)
|
||||
{
|
||||
CollectionView.VerifyCollectionViewFlagEnabled(nameof(ItemsViewAdapter));
|
||||
|
||||
ItemsView = itemsView;
|
||||
_createView = createView;
|
||||
_createItemContentView = createItemContentView;
|
||||
ItemsSource = ItemsSourceFactory.Create(itemsView.ItemsSource, this);
|
||||
|
||||
if (_createView == null)
|
||||
if (_createItemContentView == null)
|
||||
{
|
||||
_createView = (renderer, context) => new ItemContentView(renderer, context);
|
||||
_createItemContentView = (view, context) => new ItemContentView(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
{
|
||||
if (holder is TemplatedItemViewHolder templatedItemViewHolder)
|
||||
{
|
||||
ItemsView.RemoveLogicalChild(templatedItemViewHolder.View);
|
||||
templatedItemViewHolder.Recycle(ItemsView);
|
||||
}
|
||||
|
||||
base.OnViewRecycled(holder);
|
||||
|
@ -49,7 +49,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
textViewHolder.TextView.Text = ItemsSource[position].ToString();
|
||||
break;
|
||||
case TemplatedItemViewHolder templatedItemViewHolder:
|
||||
BindableObject.SetInheritedBindingContext(templatedItemViewHolder.View, ItemsSource[position]);
|
||||
templatedItemViewHolder.Bind(ItemsSource[position], ItemsView);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -67,12 +67,20 @@ namespace Xamarin.Forms.Platform.Android
|
|||
return new TextViewHolder(view);
|
||||
}
|
||||
|
||||
// Realize the content, create a renderer out of it, and use that
|
||||
var templateElement = (View)template.CreateContent();
|
||||
ItemsView.AddLogicalChild(templateElement);
|
||||
var itemContentControl = _createView(CreateRenderer(templateElement, context), context);
|
||||
var itemContentView = new ItemContentView(parent.Context);
|
||||
return new TemplatedItemViewHolder(itemContentView, template);
|
||||
}
|
||||
|
||||
return new TemplatedItemViewHolder(itemContentControl, templateElement);
|
||||
public override int ItemCount => ItemsSource.Count;
|
||||
|
||||
public override int GetItemViewType(int position)
|
||||
{
|
||||
// TODO hartez We might be able to turn this to our own purposes
|
||||
// We might be able to have the CollectionView signal the adapter if the ItemTemplate property changes
|
||||
// And as long as it's null, we return a value to that effect here
|
||||
// Then we don't have to check _itemsView.ItemTemplate == null in OnCreateViewHolder, we can just use
|
||||
// the viewType parameter.
|
||||
return 42;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
@ -90,36 +98,6 @@ namespace Xamarin.Forms.Platform.Android
|
|||
}
|
||||
}
|
||||
|
||||
static IVisualElementRenderer CreateRenderer(View view, Context context)
|
||||
{
|
||||
if (view == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(view));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var renderer = Platform.CreateRenderer(view, context);
|
||||
Platform.SetRenderer(view, renderer);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public override int ItemCount => ItemsSource.Count;
|
||||
|
||||
public override int GetItemViewType(int position)
|
||||
{
|
||||
// TODO hartez We might be able to turn this to our own purposes
|
||||
// We might be able to have the CollectionView signal the adapter if the ItemTemplate property changes
|
||||
// And as long as it's null, we return a value to that effect here
|
||||
// Then we don't have to check _itemsView.ItemTemplate == null in OnCreateViewHolder, we can just use
|
||||
// the viewType parameter.
|
||||
return 42;
|
||||
}
|
||||
|
||||
public virtual int GetPositionForItem(object item)
|
||||
{
|
||||
for (int n = 0; n < ItemsSource.Count; n++)
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
EmptyViewAdapter _emptyViewAdapter;
|
||||
DataChangeObserver _dataChangeViewObserver;
|
||||
bool _watchingForEmpty;
|
||||
|
||||
public ItemsViewRenderer(Context context) : base(context)
|
||||
{
|
||||
|
@ -214,7 +215,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
// Stop watching the old adapter to see if it's empty (if we _are_ watching)
|
||||
Unwatch(ItemsViewAdapter ?? GetAdapter());
|
||||
|
||||
|
||||
UpdateAdapter();
|
||||
|
||||
UpdateEmptyView();
|
||||
|
@ -228,22 +229,30 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
void Unwatch(Adapter adapter)
|
||||
{
|
||||
if (adapter != null && _dataChangeViewObserver != null)
|
||||
if (_watchingForEmpty && adapter != null && _dataChangeViewObserver != null)
|
||||
{
|
||||
adapter.UnregisterAdapterDataObserver(_dataChangeViewObserver);
|
||||
}
|
||||
|
||||
_watchingForEmpty = false;
|
||||
}
|
||||
|
||||
// TODO hartez 2018/10/24 19:25:14 I don't like these method names; too generic
|
||||
// TODO hartez 2018/11/05 22:37:42 Also, thinking all the EmptyView stuff should be moved to a helper
|
||||
void Watch(Adapter adapter)
|
||||
{
|
||||
if (_watchingForEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dataChangeViewObserver == null)
|
||||
{
|
||||
_dataChangeViewObserver = new DataChangeObserver(UpdateEmptyViewVisibility);
|
||||
}
|
||||
|
||||
adapter.RegisterAdapterDataObserver(_dataChangeViewObserver);
|
||||
_watchingForEmpty = true;
|
||||
}
|
||||
|
||||
protected virtual void SetUpNewElement(ItemsView newElement)
|
||||
|
@ -369,7 +378,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
protected virtual void UpdateEmptyView()
|
||||
{
|
||||
if (ItemsViewAdapter == null)
|
||||
if (ItemsViewAdapter == null || ItemsView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -381,7 +390,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
{
|
||||
if (_emptyViewAdapter == null)
|
||||
{
|
||||
_emptyViewAdapter = new EmptyViewAdapter();
|
||||
_emptyViewAdapter = new EmptyViewAdapter(ItemsView);
|
||||
}
|
||||
|
||||
_emptyViewAdapter.EmptyView = emptyView;
|
||||
|
|
|
@ -11,8 +11,8 @@ namespace Xamarin.Forms.Platform.Android
|
|||
protected readonly SelectableItemsView SelectableItemsView;
|
||||
List<SelectableViewHolder> _currentViewHolders = new List<SelectableViewHolder>();
|
||||
|
||||
internal SelectableItemsViewAdapter(SelectableItemsView selectableItemsView,
|
||||
Func<IVisualElementRenderer, Context, global::Android.Views.View> createView = null) : base(selectableItemsView, createView)
|
||||
internal SelectableItemsViewAdapter(SelectableItemsView selectableItemsView,
|
||||
Func<View, Context, ItemContentView> createView = null) : base(selectableItemsView, createView)
|
||||
{
|
||||
SelectableItemsView = selectableItemsView;
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ namespace Xamarin.Forms.Platform.Android
|
|||
readonly Func<int> _width;
|
||||
readonly Func<int> _height;
|
||||
|
||||
public SizedItemContentView(IVisualElementRenderer content, Context context, Func<int> width, Func<int> height)
|
||||
: base(content, context)
|
||||
public SizedItemContentView(Context context, Func<int> width, Func<int> height)
|
||||
: base(context)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
@ -17,6 +17,12 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
if (Content == null)
|
||||
{
|
||||
SetMeasuredDimension(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetWidth = _width();
|
||||
var targetHeight = _height();
|
||||
|
||||
|
|
|
@ -1,21 +1,54 @@
|
|||
using System;
|
||||
using Android.Content;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
internal class TemplatedItemViewHolder : SelectableViewHolder
|
||||
{
|
||||
public View View { get; }
|
||||
private readonly ItemContentView _itemContentView;
|
||||
private readonly DataTemplate _template;
|
||||
|
||||
public TemplatedItemViewHolder(global::Android.Views.View itemView, View rootElement) : base(itemView)
|
||||
public View View { get; private set; }
|
||||
|
||||
public TemplatedItemViewHolder(ItemContentView itemContentView, DataTemplate template) : base(itemContentView)
|
||||
{
|
||||
View = rootElement;
|
||||
_itemContentView = itemContentView;
|
||||
_template = template;
|
||||
}
|
||||
|
||||
protected override void OnSelectedChanged()
|
||||
{
|
||||
base.OnSelectedChanged();
|
||||
|
||||
if (View == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VisualStateManager.GoToState(View, IsSelected
|
||||
? VisualStateManager.CommonStates.Selected
|
||||
: VisualStateManager.CommonStates.Normal);
|
||||
}
|
||||
|
||||
public void Recycle(ItemsView itemsView)
|
||||
{
|
||||
itemsView.RemoveLogicalChild(View);
|
||||
_itemContentView.Recycle();
|
||||
}
|
||||
|
||||
public void Bind(object itemBindingContext, ItemsView itemsView)
|
||||
{
|
||||
var template = _template.SelectDataTemplate(itemBindingContext, itemsView);
|
||||
|
||||
View = (View)template.CreateContent();
|
||||
_itemContentView.RealizeContent(View);
|
||||
|
||||
// Set the binding context before we add it as a child of the ItemsView; otherwise, it will
|
||||
// inherit the ItemsView's binding context
|
||||
View.BindingContext = itemBindingContext;
|
||||
|
||||
itemsView.AddLogicalChild(View);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Platform.iOS
|
||||
{
|
||||
|
@ -227,12 +228,22 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
|
||||
void ApplyTemplateAndDataContext(TemplatedCell cell, NSIndexPath indexPath)
|
||||
{
|
||||
// We need to create a renderer, which means we need a template
|
||||
var view = _itemsView.ItemTemplate.CreateContent() as View;
|
||||
_itemsView.AddLogicalChild(view);
|
||||
var template = _itemsView.ItemTemplate;
|
||||
var item = _itemsSource[indexPath.Row];
|
||||
|
||||
// Run this through the extension method in case it's really a DataTemplateSelector
|
||||
template = template.SelectDataTemplate(item, _itemsView);
|
||||
|
||||
// Create the content and renderer for the view and
|
||||
var view = template.CreateContent() as View;
|
||||
var renderer = CreateRenderer(view);
|
||||
BindableObject.SetInheritedBindingContext(view, _itemsSource[indexPath.Row]);
|
||||
cell.SetRenderer(renderer);
|
||||
|
||||
// Bind the view to the data item
|
||||
view.BindingContext = _itemsSource[indexPath.Row];
|
||||
|
||||
// And make sure it's a "child" of the ItemsView
|
||||
_itemsView.AddLogicalChild(view);
|
||||
}
|
||||
|
||||
internal void RemoveLogicalChild(UICollectionViewCell cell)
|
||||
|
@ -357,6 +368,9 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
{
|
||||
if (emptyViewTemplate != null)
|
||||
{
|
||||
// Run this through the extension method in case it's really a DataTemplateSelector
|
||||
emptyViewTemplate = emptyViewTemplate.SelectDataTemplate(emptyView, _itemsView);
|
||||
|
||||
// We have a template; turn it into a Forms view
|
||||
var templateElement = emptyViewTemplate.CreateContent() as View;
|
||||
var renderer = CreateRenderer(templateElement);
|
||||
|
|
|
@ -72,7 +72,9 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
void CreatePlaceholderLabel()
|
||||
{
|
||||
if (Control == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Control.AddSubview(_placeholderLabel);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче