using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.EmptyViewGalleries;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries;
+using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ItemSizeGalleries;
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
{
GalleryBuilder.NavButton("EmptyView Galleries", () => new EmptyViewGallery(), Navigation),
GalleryBuilder.NavButton("Selection Galleries", () => new SelectionGallery(), Navigation),
GalleryBuilder.NavButton("Propagation Galleries", () => new PropagationGallery(), Navigation),
+ GalleryBuilder.NavButton("Item Size Galleries", () => new ItemsSizeGallery(), Navigation),
}
};
}
using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Input;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
{
[Preserve(AllMembers = true)]
- public class CollectionViewGalleryTestItem
+ public class CollectionViewGalleryTestItem : INotifyPropertyChanged
{
+ string _caption;
+
public DateTime Date { get; set; }
- public string Caption { get; set; }
+
+ public string Caption
+ {
+ get => _caption;
+ set
+ {
+ _caption = value;
+ OnPropertyChanged();
+ }
+ }
+
public string Image { get; set; }
+
public int Index { get; set; }
+ public ICommand MoreCommand { get; set; }
+ public ICommand LessCommand { get; set; }
public CollectionViewGalleryTestItem(DateTime date, string caption, string image, int index)
{
Caption = caption;
Image = image;
Index = index;
+
+ var text = " Lorem ipsum dolor sit amet, qui eleifend adversarium ei, pro tamquam pertinax inimicus ut. Quis assentior ius no, ne vel modo tantas omnium, sint labitur id nec. Mel ad cetero repudiare definiebas, eos sint placerat cu.";
+
+ MoreCommand = new Command(() => Caption += text);
+ LessCommand = new Command(() =>
+ {
+ var last = Caption.LastIndexOf(text);
+ if (last > 0)
+ {
+ Caption = Caption.Substring(0, last);
+ }
+
+ });
}
+ public event PropertyChangedEventHandler PropertyChanged;
+
public override string ToString()
{
return $"Item: {Index}";
}
+
+ protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
}
-}
\ No newline at end of file
+}
new TemplateCodeCollectionViewGridGallery (), Navigation),
GalleryBuilder.NavButton("Horizontal Grid (Code)", () =>
new TemplateCodeCollectionViewGridGallery (ItemsLayoutOrientation.Horizontal), Navigation),
-
- GalleryBuilder.NavButton("ItemSizing Strategy", () =>
- new VariableSizeTemplateGridGallery (ItemsLayoutOrientation.Horizontal), Navigation),
-
GalleryBuilder.NavButton("DataTemplateSelector", () =>
new DataTemplateSelectorGallery(), Navigation),
}
var image = new Image
{
- HeightRequest = 100, WidthRequest = 100,
+ HeightRequest = 100,
+ WidthRequest = 100,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Margin = new Thickness(2, 5, 2, 2),
};
caption.SetBinding(Label.TextProperty, new Binding("Caption"));
-
+
templateLayout.Children.Add(image);
templateLayout.Children.Add(caption);
{
var templateLayout = new Grid
{
- RowDefinitions = new RowDefinitionCollection { new RowDefinition(), new RowDefinition {Height = GridLength.Auto} },
+ RowDefinitions = new RowDefinitionCollection { new RowDefinition(), new RowDefinition { Height = GridLength.Auto } },
WidthRequest = 280,
HeightRequest = 310,
};
};
caption.SetBinding(Label.TextProperty, new Binding("Caption"));
-
+
templateLayout.Children.Add(image);
templateLayout.Children.Add(caption);
};
caption.SetBinding(Label.TextProperty, new Binding("Caption"));
-
+
grid.Children.Add(image);
grid.Children.Add(caption);
Content = grid
};
- return frame;
+ return frame;
});
}
{
HorizontalOptions = LayoutOptions.Fill,
HorizontalTextAlignment = TextAlignment.Center,
- HeightRequest = 40, WidthRequest = 100,
+ HeightRequest = 40,
+ WidthRequest = 100,
BackgroundColor = Color.Crimson,
Text = "Caption"
};
- caption.SetBinding(Label.TextProperty, new Binding("Index", stringFormat:"Index {0}"));
+ caption.SetBinding(Label.TextProperty, new Binding("Index", stringFormat: "Index {0}"));
templateLayout.Children.Add(image);
templateLayout.Children.Add(caption);
{
BackgroundColor = Color.Bisque,
- RowDefinitions = new RowDefinitionCollection { new RowDefinition(), new RowDefinition {Height = GridLength.Auto} },
+ RowDefinitions = new RowDefinitionCollection { new RowDefinition(), new RowDefinition { Height = GridLength.Auto } },
WidthRequest = 100,
HeightRequest = 140
};
{
HorizontalOptions = LayoutOptions.Fill,
HorizontalTextAlignment = TextAlignment.Center,
- HeightRequest = 40, WidthRequest = 100,
+ HeightRequest = 40,
+ WidthRequest = 100,
BackgroundColor = Color.Crimson,
Text = "Caption"
};
caption.SetBinding(Label.TextProperty, new Binding("Date", stringFormat: "{0:d}"));
-
+
templateLayout.Children.Add(image);
templateLayout.Children.Add(caption);
return templateLayout;
});
}
-
+
public static DataTemplate VariableSizeTemplate()
{
var indexHeightConverter = new IndexRequestConverter(3, 50, 150);
});
}
+ public static DataTemplate DynamicTextTemplate()
+ {
+ return new DataTemplate(() =>
+ {
+ var templateLayout = new Grid
+ {
+ RowDefinitions = new RowDefinitionCollection
+ {
+ new RowDefinition() { Height = GridLength.Auto },
+ new RowDefinition() { Height = GridLength.Auto },
+ new RowDefinition() { Height = GridLength.Auto }
+ },
+ BackgroundColor = Color.LightGoldenrodYellow,
+ Margin = 10
+ };
+
+ var frame = new Frame
+ {
+ HeightRequest = 50,
+ WidthRequest = 200,
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center,
+ Margin = new Thickness(2, 5, 2, 2),
+ AutomationId = "frame",
+ BackgroundColor = Color.CadetBlue
+ };
+
+ var date = new Label
+ {
+ FontSize = 12,
+ HorizontalOptions = LayoutOptions.Fill,
+ VerticalOptions = LayoutOptions.Fill,
+ HorizontalTextAlignment = TextAlignment.Center,
+ VerticalTextAlignment = TextAlignment.Center,
+ Margin = new Thickness(2, 0, 2, 2)
+ };
+
+ date.SetBinding(Label.TextProperty, new Binding("Date"));
+
+ frame.Content = date;
+
+ var caption = new Label
+ {
+ FontSize = 12,
+ HorizontalOptions = LayoutOptions.Fill,
+ HorizontalTextAlignment = TextAlignment.Center,
+ Margin = new Thickness(2, 0, 2, 2),
+ LineBreakMode = LineBreakMode.WordWrap,
+ MaxLines = 10
+ };
+
+ caption.SetBinding(Label.TextProperty, new Binding("Caption"));
+
+ var more = new Button { Text = "More Text" };
+ var less = new Button { Text = "Less Text" };
+
+ var buttonLayout = new StackLayout
+ {
+ Children = { more, less },
+ Orientation = StackOrientation.Horizontal,
+ HorizontalOptions = LayoutOptions.Center
+ };
+
+ more.SetBinding(Button.CommandProperty, new Binding("MoreCommand"));
+ less.SetBinding(Button.CommandProperty, new Binding("LessCommand"));
+
+ templateLayout.Children.Add(frame);
+ templateLayout.Children.Add(caption);
+ templateLayout.Children.Add(buttonLayout);
+
+ Grid.SetRow(buttonLayout, 1);
+ Grid.SetRow(caption, 2);
+
+ var rootLayout = new StackLayout
+ {
+ Children = { templateLayout }
+ };
+
+ return rootLayout;
+ });
+ }
+
+ static void More_Clicked(object sender, EventArgs e)
+ {
+ throw new NotImplementedException();
+ }
+
class IndexRequestConverter : IValueConverter
{
readonly int _cutoff;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
}
-}
\ No newline at end of file
+}
--- /dev/null
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ItemSizeGalleries
+{
+ internal class DynamicItemSizeGallery : ContentPage
+ {
+ public DynamicItemSizeGallery(IItemsLayout itemsLayout)
+ {
+ var layout = new Grid
+ {
+ RowDefinitions = new RowDefinitionCollection
+ {
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Star }
+ }
+ };
+
+ var instructions = new Label
+ {
+ Text = "Tap the buttons in each item to increase/decrease the amount of text. The items should expand and contract to accommodate the text."
+ };
+
+ var itemTemplate = ExampleTemplates.DynamicTextTemplate();
+
+ var collectionView = new CollectionView
+ {
+ ItemsLayout = itemsLayout,
+ ItemTemplate = itemTemplate,
+ AutomationId = "collectionview"
+ };
+
+ var generator = new ItemsSourceGenerator(collectionView, initialItems: 20);
+
+ layout.Children.Add(generator);
+ layout.Children.Add(instructions);
+ layout.Children.Add(collectionView);
+
+ Grid.SetRow(instructions, 1);
+ Grid.SetRow(collectionView, 2);
+
+ Content = layout;
+
+ generator.GenerateItems();
+ }
+ }
+}
--- /dev/null
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ItemSizeGalleries
+{
+ internal class ItemsSizeGallery : ContentPage
+ {
+ public ItemsSizeGallery()
+ {
+ var descriptionLabel =
+ new Label { Text = "Item Size Galleries", Margin = new Thickness(2, 2, 2, 2) };
+
+ Title = "Item Size Galleries";
+
+ Content = new ScrollView
+ {
+ Content = new StackLayout
+ {
+ Children =
+ {
+ descriptionLabel,
+ GalleryBuilder.NavButton("Expanding Text (Vertical List)", () =>
+ new DynamicItemSizeGallery(ListItemsLayout.VerticalList), Navigation),
+ GalleryBuilder.NavButton("Expanding Text (Horizontal List)", () =>
+ new DynamicItemSizeGallery(ListItemsLayout.HorizontalList), Navigation),
+ GalleryBuilder.NavButton("ItemSizing Strategy", () =>
+ new VariableSizeTemplateGridGallery (ItemsLayoutOrientation.Horizontal), Navigation)
+ }
+ }
+ };
+ }
+ }
+}
-namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ItemSizeGalleries
{
internal class VariableSizeTemplateGridGallery : ContentPage
{
-using System;
using Android.Content;
using Android.Views;
{
Content = CreateRenderer(view, Context);
AddView(Content.View);
+ Content.Element.MeasureInvalidated += ElementMeasureInvalidated;
+ }
+
+ void ElementMeasureInvalidated(object sender, System.EventArgs e)
+ {
+ RequestLayout();
}
internal void Recycle()
{
+ Content.Element.MeasureInvalidated -= ElementMeasureInvalidated;
RemoveView(Content.View);
Content = null;
}
SizeRequest measure = Content.Element.Measure(width, height, MeasureFlags.IncludeMargins);
+ // When we implement ItemSizingStrategy.MeasureFirstItem for Android, these next two clauses will need to
+ // be updated to use the static width/height
+
if (pixelWidth == 0)
{
- pixelWidth = (int)Context.ToPixels(Content.Element.Width > 0
- ? Content.Element.Width
- : measure.Request.Width);
+ pixelWidth = (int)Context.ToPixels(measure.Request.Width);
}
if (pixelHeight == 0)
{
- pixelHeight = (int)Context.ToPixels(Content.Element.Height > 0
- ? Content.Element.Height
- : measure.Request.Height);
+ pixelHeight = (int)Context.ToPixels(measure.Request.Height);
}
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;
}
}
-}
\ No newline at end of file
+}
using System;
-using Android.Content;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
{
internal class TemplatedItemViewHolder : SelectableViewHolder
{
- private readonly ItemContentView _itemContentView;
- private readonly DataTemplate _template;
+ readonly ItemContentView _itemContentView;
+ readonly DataTemplate _template;
public View View { get; private set; }
public void Recycle(ItemsView itemsView)
{
+ View.BindingContext = null;
itemsView.RemoveLogicalChild(View);
_itemContentView.Recycle();
}
-using System;
-using CoreGraphics;
+using CoreGraphics;
using Foundation;
-using UIKit;
namespace Xamarin.Forms.Platform.iOS
{
var measure = VisualElementRenderer.Element.Measure(double.PositiveInfinity,
ConstrainedDimension, MeasureFlags.IncludeMargins);
- var width = VisualElementRenderer.Element.Width > 0
- ? VisualElementRenderer.Element.Width : measure.Request.Width;
-
- return new CGSize(width, ConstrainedDimension);
+ return new CGSize(measure.Request.Height, ConstrainedDimension);
}
public override void ConstrainTo(CGSize constraint)
protected void InitializeContentConstraints(UIView nativeView)
{
- ContentView.AddSubview(nativeView);
ContentView.TranslatesAutoresizingMaskIntoConstraints = false;
+ nativeView.TranslatesAutoresizingMaskIntoConstraints = false;
+
+ ContentView.AddSubview(nativeView);
+
+ // We want the cell to be the same size as the ContentView
+ ContentView.TopAnchor.ConstraintEqualTo(TopAnchor).Active = true;
+ ContentView.BottomAnchor.ConstraintEqualTo(BottomAnchor).Active = true;
+ ContentView.LeadingAnchor.ConstraintEqualTo(LeadingAnchor).Active = true;
+ ContentView.TrailingAnchor.ConstraintEqualTo(TrailingAnchor).Active = true;
+
+ // And we want the ContentView to be the same size as the root renderer for the Forms element
ContentView.TopAnchor.ConstraintEqualTo(nativeView.TopAnchor).Active = true;
ContentView.BottomAnchor.ConstraintEqualTo(nativeView.BottomAnchor).Active = true;
ContentView.LeadingAnchor.ConstraintEqualTo(nativeView.LeadingAnchor).Active = true;
// And make sure it's a "child" of the ItemsView
_itemsView.AddLogicalChild(view);
+
+ cell.ContentSizeChanged += CellContentSizeChanged;
+ }
+
+ void CellContentSizeChanged(object sender, EventArgs e)
+ {
+ Layout?.InvalidateLayout();
}
- internal void RemoveLogicalChild(UICollectionViewCell cell)
+ internal void PrepareCellForRemoval(UICollectionViewCell cell)
{
if (cell is TemplatedCell templatedCell)
{
+ templatedCell.ContentSizeChanged -= CellContentSizeChanged;
+
var oldView = templatedCell.VisualElementRenderer?.Element;
if (oldView != null)
{
+ oldView.BindingContext = null;
_itemsView.RemoveLogicalChild(oldView);
}
+
+ templatedCell.PrepareForRemoval();
}
}
return shouldInvalidate;
}
+ public override bool ShouldInvalidateLayout(UICollectionViewLayoutAttributes preferredAttributes, UICollectionViewLayoutAttributes originalAttributes)
+ {
+ if (ItemSizingStrategy == ItemSizingStrategy.MeasureAllItems)
+ {
+ if (preferredAttributes.Bounds != originalAttributes.Bounds)
+ {
+ return true;
+ }
+ }
+
+ return base.ShouldInvalidateLayout(preferredAttributes, originalAttributes);
+ }
+
protected void DetermineCellSize()
{
if (GetPrototype == null)
namespace Xamarin.Forms.Platform.iOS
{
- // TODO hartez 2018/09/17 14:11:02 Should this be named "TemplateCell" instead of "TemplatedCell"?
public abstract class TemplatedCell : ItemsViewCell
{
+ public event EventHandler<EventArgs> ContentSizeChanged;
protected nfloat ConstrainedDimension;
[Export("initWithFrame:")]
public override UICollectionViewLayoutAttributes PreferredLayoutAttributesFittingAttributes(
UICollectionViewLayoutAttributes layoutAttributes)
{
- var nativeView = VisualElementRenderer.NativeView;
+ var preferredAttributes = base.PreferredLayoutAttributesFittingAttributes(layoutAttributes);
+ // Measure this cell (including the Forms element)
var size = Measure();
+ // Update the size of the root view to accommodate the Forms element
+ var nativeView = VisualElementRenderer.NativeView;
nativeView.Frame = new CGRect(CGPoint.Empty, size);
+
+ // Layout the Forms element
VisualElementRenderer.Element.Layout(nativeView.Frame.ToRectangle());
- layoutAttributes.Frame = nativeView.Frame;
+ // Adjust the preferred attributes to include space for the Forms element
+ preferredAttributes.Frame = new CGRect(preferredAttributes.Frame.Location, size);
- return layoutAttributes;
+ return preferredAttributes;
}
- public void SetRenderer(IVisualElementRenderer renderer)
+ public override void PrepareForReuse()
{
+ base.PrepareForReuse();
ClearSubviews();
+ }
+ public void SetRenderer(IVisualElementRenderer renderer)
+ {
VisualElementRenderer = renderer;
var nativeView = VisualElementRenderer.NativeView;
InitializeContentConstraints(nativeView);
+
+ renderer.Element.MeasureInvalidated += MeasureInvalidated;
}
protected void Layout(CGSize constraints)
{
for (int n = ContentView.Subviews.Length - 1; n >= 0; n--)
{
- // TODO hartez 2018/09/07 16:14:43 Does this also need to clear the constraints?
ContentView.Subviews[n].RemoveFromSuperview();
}
}
if (element != null)
{
- VisualStateManager.GoToState(element, value
- ? VisualStateManager.CommonStates.Selected
+ VisualStateManager.GoToState(element, value
+ ? VisualStateManager.CommonStates.Selected
: VisualStateManager.CommonStates.Normal);
}
}
}
+
+ void MeasureInvalidated(object sender, EventArgs args)
+ {
+ if (VisualElementRenderer?.Element == null)
+ {
+ return;
+ }
+
+ var bounds = VisualElementRenderer.Element.Bounds;
+
+ if (bounds.Width <= 0 || bounds.Height <= 0)
+ {
+ return;
+ }
+
+ OnContentSizeChanged();
+ }
+
+ public void PrepareForRemoval()
+ {
+ if (VisualElementRenderer?.Element != null)
+ {
+ VisualElementRenderer.Element.MeasureInvalidated -= MeasureInvalidated;
+ }
+ }
+
+ protected void OnContentSizeChanged()
+ {
+ ContentSizeChanged?.Invoke(this, EventArgs.Empty);
+ }
}
}
\ No newline at end of file
public override void CellDisplayingEnded(UICollectionView collectionView, UICollectionViewCell cell, NSIndexPath indexPath)
{
- ItemsViewController.RemoveLogicalChild(cell);
+ ItemsViewController.PrepareCellForRemoval(cell);
}
}
}
\ No newline at end of file
-using System;
-using System.Diagnostics;
using CoreGraphics;
using Foundation;
-using UIKit;
namespace Xamarin.Forms.Platform.iOS
{
var measure = VisualElementRenderer.Element.Measure(ConstrainedDimension,
double.PositiveInfinity, MeasureFlags.IncludeMargins);
- var height = VisualElementRenderer.Element.Height > 0
- ? VisualElementRenderer.Element.Height : measure.Request.Height;
-
- return new CGSize(ConstrainedDimension, height);
+ return new CGSize(ConstrainedDimension, measure.Request.Height);
}
public override void ConstrainTo(CGSize constraint)