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:
E.Z. Hart 2019-03-27 17:03:35 -06:00 коммит произвёл Shane Neuville
Родитель 517642b551
Коммит 85e75046c2
23 изменённых файлов: 572 добавлений и 145 удалений

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

@ -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);