Zobrazení kolekcí v Xamarin.iOS
Zobrazení kolekcí umožňují zobrazení obsahu pomocí libovolných rozložení. Umožňují snadno vytvářet rozložení podobná mřížce a zároveň podporovat vlastní rozložení.
Zobrazení kolekcí, která jsou k dispozici ve UICollectionView
třídě, jsou novým konceptem v iOSu 6, který představuje prezentaci více položek na obrazovce pomocí rozložení. Vzory pro poskytování dat k UICollectionView
vytváření položek a interakci s těmito položkami se řídí stejnými vzory delegování a zdroje dat, které se běžně používají při vývoji pro iOS.
Zobrazení kolekcí ale pracují se subsystémem rozložení, který je nezávislý na UICollectionView
sobě. Proto jednoduše poskytnutí jiného rozložení může snadno změnit prezentaci zobrazení kolekce.
iOS poskytuje třídu rozložení, UICollectionViewFlowLayout
která umožňuje vytvářet rozložení založená na řádcích, jako je mřížka, bez další práce. Můžete také vytvořit vlastní rozložení, která umožňují libovolnou prezentaci, kterou si můžete představit.
Základy UICollectionView
Třída UICollectionView
se skládá ze tří různých položek:
- Buňky – zobrazení řízená daty pro každou položku
- Doplňková zobrazení – zobrazení založená na datech přidružená k oddílu.
- Zobrazení dekorace – nedefinovaná zobrazení vytvořená rozložením
Cells
Buňky jsou objekty, které představují jednu položku v sadě dat, která je prezentována zobrazením kolekce. Každá buňka je instance UICollectionViewCell
třídy, která se skládá ze tří různých zobrazení, jak je znázorněno na následujícím obrázku:
Třída UICollectionViewCell
má pro každé z těchto zobrazení následující vlastnosti:
ContentView
– Toto zobrazení obsahuje obsah, který buňka prezentuje. Vykreslí se v nejvyšším pořadí vykreslování na obrazovce.SelectedBackgroundView
– Buňky mají integrovanou podporu výběru. Toto zobrazení slouží k vizuálnímu označení vybrané buňky. Zobrazí se těsně pod vybranouContentView
buňkou.BackgroundView
– Buňky mohou také zobrazit pozadí, které je prezentovánoBackgroundView
. Toto zobrazení je vykresleno pod položkouSelectedBackgroundView
.
ContentView
Nastavením tak, aby byla menší než BackgroundView
a SelectedBackgroundView
, BackgroundView
lze použít k vizuálnímu rámečku obsahu, zatímco SelectedBackgroundView
se zobrazí při výběru buňky, jak je znázorněno níže:
Buňky na výše uvedeném snímku obrazovky jsou vytvořeny zděděním UICollectionViewCell
a nastavením SelectedBackgroundView
ContentView
a BackgroundView
vlastností, jak je znázorněno v následujícím kódu:
public class AnimalCell : UICollectionViewCell
{
UIImageView imageView;
[Export ("initWithFrame:")]
public AnimalCell (CGRect frame) : base (frame)
{
BackgroundView = new UIView{BackgroundColor = UIColor.Orange};
SelectedBackgroundView = new UIView{BackgroundColor = UIColor.Green};
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
ContentView.Transform = CGAffineTransform.MakeScale (0.8f, 0.8f);
imageView = new UIImageView (UIImage.FromBundle ("placeholder.png"));
imageView.Center = ContentView.Center;
imageView.Transform = CGAffineTransform.MakeScale (0.7f, 0.7f);
ContentView.AddSubview (imageView);
}
public UIImage Image {
set {
imageView.Image = value;
}
}
}
Doplňková zobrazení
Doplňková zobrazení jsou zobrazení, která představují informace spojené s každou částí oddílu UICollectionView
. Podobně jako buňky jsou doplňková zobrazení řízená daty. Kde buňky prezentují data položky ze zdroje dat, doplňková zobrazení prezentují data oddílu, jako jsou kategorie knihy v knihovně knih nebo žánr hudby v hudební knihovně.
K prezentaci záhlaví konkrétního oddílu můžete použít například doplňkové zobrazení, jak je znázorněno na následujícím obrázku:
Chcete-li použít doplňkové zobrazení, musí být nejprve registrován v ViewDidLoad
metodě:
CollectionView.RegisterClassForSupplementaryView (typeof(Header), UICollectionElementKindSection.Header, headerId);
Pak je potřeba zobrazení vrátit pomocí , vytvořené pomocí GetViewForSupplementaryElement
DequeueReusableSupplementaryView
, a dědí z UICollectionReusableView
. Následující fragment kódu vytvoří doplněk SupplementaryView zobrazený na snímku obrazovky výše:
public override UICollectionReusableView GetViewForSupplementaryElement (UICollectionView collectionView, NSString elementKind, NSIndexPath indexPath)
{
var headerView = (Header)collectionView.DequeueReusableSupplementaryView (elementKind, headerId, indexPath);
headerView.Text = "Supplementary View";
return headerView;
}
Doplňková zobrazení jsou obecnější než jen záhlaví a zápatí. Dají se umístit kdekoli v zobrazení kolekce a můžou se skládat z libovolných zobrazení, takže jejich vzhled je plně přizpůsobitelný.
Dekorační zobrazení
Dekorační zobrazení jsou čistě vizuální zobrazení, která lze zobrazit v objektu UICollectionView
. Na rozdíl od buněk a doplňkových zobrazení nejsou řízené daty. Vždy se vytvoří v rámci podtřídy rozložení a následně se můžou změnit jako rozložení obsahu. Například zobrazení dekorace lze použít k prezentaci zobrazení na pozadí, které se posunuje s obsahem v zobrazení UICollectionView
, jak je znázorněno níže:
Následující fragment kódu změní pozadí na červenou ve třídě ukázek CircleLayout
:
public class MyDecorationView : UICollectionReusableView
{
[Export ("initWithFrame:")]
public MyDecorationView (CGRect frame) : base (frame)
{
BackgroundColor = UIColor.Red;
}
}
Zdroj dat
Stejně jako u jiných částí iOSu, například UITableView
a MKMapView
, UICollectionView
získává data ze zdroje dat, který je vystaven v Xamarin.iOS prostřednictvím UICollectionViewDataSource
třídy. Tato třída zodpovídá za poskytování obsahu UICollectionView
, například:
- Buňky – Vráceno z
GetCell
metody. - Doplňková zobrazení – Vráceno z
GetViewForSupplementaryElement
metody. - Počet oddílů – Vráceno z
NumberOfSections
metody. Výchozí hodnota je 1, pokud není implementována. - Počet položek na oddíl – Vráceno z
GetItemsCount
metody.
UICollectionViewController
Pro usnadnění UICollectionViewController
práce je třída k dispozici. Tato možnost je automaticky nakonfigurovaná tak, aby byla delegátem, což je popsáno v další části a zdroj dat pro jeho UICollectionView
zobrazení.
UITableView
Stejně jako v UICollectionView
případě , třída bude volat pouze zdroj dat získat buňky pro položky, které jsou na obrazovce.
Buňky, které se posunou mimo obrazovku, se umístí do fronty pro opakované použití, jak znázorňuje následující obrázek:
Opakované použití buněk bylo zjednodušeno pomocí UICollectionView
a UITableView
. Buňku už nemusíte vytvářet přímo ve zdroji dat, pokud není k dispozici ve frontě pro opakované použití, protože buňky jsou zaregistrované v systému. Pokud není buňka při volání zrušení fronty buňka z opakované fronty k dispozici, iOS ji automaticky vytvoří na základě typu nebo nib, který byl zaregistrován.
Stejná technika je také k dispozici pro doplňková zobrazení.
Představte si například následující kód, který zaregistruje AnimalCell
třídu:
static NSString animalCellId = new NSString ("AnimalCell");
CollectionView.RegisterClassForCell (typeof(AnimalCell), animalCellId);
UICollectionView
Když potřebuje buňku, protože je její položka na obrazovce, UICollectionView
volá metodu GetCell
zdroje dat. Podobně jako to funguje s UITableView, tato metoda je zodpovědná za konfiguraci buňky z backingových dat, což by v tomto případě byla AnimalCell
třída.
Následující kód ukazuje implementaci GetCell
, která vrací AnimalCell
instanci:
public override UICollectionViewCell GetCell (UICollectionView collectionView, Foundation.NSIndexPath indexPath)
{
var animalCell = (AnimalCell)collectionView.DequeueReusableCell (animalCellId, indexPath);
var animal = animals [indexPath.Row];
animalCell.Image = animal.Image;
return animalCell;
}
Volání DequeReusableCell
je místo, kde bude buňka z fronty znovu zařazena do fronty, nebo pokud buňka není k dispozici ve frontě, vytvořena na základě typu registrovaného v volání CollectionView.RegisterClassForCell
.
V tomto případě, když zaregistrujete AnimalCell
třídu, iOS vytvoří novou AnimalCell
interně a vrátí ji při volání de-queue buňky, po které je nakonfigurována s obrázkem obsaženým ve třídě zvířat a vrácen pro zobrazení na UICollectionView
.
Delegát
Třída UICollectionView
používá delegáta typu UICollectionViewDelegate
k podpoře interakce s obsahem v objektu UICollectionView
. To umožňuje řídit:
- Výběr buňky – Určuje, zda je buňka vybrána.
- Zvýraznění buněk – Určuje, jestli se buňka právě dotkne.
- Nabídky buněk – Nabídka zobrazená pro buňku v reakci na dlouhé stisknutí gesta
Stejně jako u zdroje dat je ve výchozím nastavení nakonfigurovaný tak, UICollectionViewController
aby byl delegátem pro .UICollectionView
Zvýraznění buněk
Když je buňka stisknuta, buňka přejde do zvýrazněného stavu a není vybraná, dokud uživatel nezvedne prst z buňky. To umožňuje dočasnou změnu vzhledu buňky předtím, než je skutečně vybrána. Při výběru se zobrazí buňka SelectedBackgroundView
. Následující obrázek znázorňuje zvýrazněný stav těsně před výběrem:
K implementaci zvýrazňování ItemHighlighted
je možné použít metody a ItemUnhighlighted
metody UICollectionViewDelegate
. Například následující kód použije žluté pozadí ContentView
zvýrazněné buňky a bílé pozadí při zrušení zvýraznění, jak je znázorněno na obrázku výše:
public override void ItemHighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.CellForItem(indexPath);
cell.ContentView.BackgroundColor = UIColor.Yellow;
}
public override void ItemUnhighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.CellForItem(indexPath);
cell.ContentView.BackgroundColor = UIColor.White;
}
Zakázání výběru
Ve výchozím nastavení je ve výchozím nastavení UICollectionView
povolen výběr . Pokud chcete zakázat výběr, přepsat ShouldHighlightItem
a vrátit hodnotu false, jak je znázorněno níže:
public override bool ShouldHighlightItem (UICollectionView collectionView, NSIndexPath indexPath)
{
return false;
}
Když je zvýraznění zakázané, proces výběru buňky je také zakázán. Kromě toho existuje také ShouldSelectItem
metoda, která řídí výběr přímo, ačkoli pokud ShouldHighlightItem
je implementována a vrací false, ShouldSelectItem
není volána.
ShouldSelectItem
umožňuje zapnout nebo vypnout výběr na základě položky po položce, pokud ShouldHighlightItem
není implementována. Umožňuje také zvýraznění bez výběru, pokud ShouldHighlightItem
je implementováno a vrací hodnotu true, zatímco ShouldSelectItem
vrátí hodnotu false.
Nabídky buněk
Každá buňka v buňce UICollectionView
je schopná zobrazit nabídku, která umožňuje volitelnou podporu vyjmutí, kopírování a vložení. Vytvoření nabídky pro úpravy v buňce:
- Přepsat
ShouldShowMenu
a vrátit hodnotu true, pokud by položka měla zobrazit nabídku. - Přepsat
CanPerformAction
a vrátit true pro každou akci, kterou může položka provést, což bude libovolná operace vyjmutí, kopírování nebo vložení. - Přepište
PerformAction
, aby se provedla operace úprav, kopírování vložení.
Následující snímek obrazovky ukazuje nabídku, když je buňka dlouho stisknuta:
Rozložení
UICollectionView
podporuje systém rozložení, který umožňuje umístění všech jeho prvků, buněk, doplňkových zobrazení a dekoračních zobrazení, spravovat nezávisle na UICollectionView
sobě.
Pomocí systému rozložení může aplikace podporovat rozložení, jako je mřížka, která jsme viděli v tomto článku, a také vlastní rozložení.
Základy rozložení
Rozložení v objektu UICollectionView
jsou definována ve třídě, která dědí z UICollectionViewLayout
. Implementace rozložení je zodpovědná za vytvoření atributů rozložení pro každou položku v objektu UICollectionView
. Rozložení můžete vytvořit dvěma způsoby:
- Použijte předdefinovaný soubor
UICollectionViewFlowLayout
. - Zadejte vlastní rozložení zděděním z
UICollectionViewLayout
.
Rozložení toku
Třída UICollectionViewFlowLayout
poskytuje rozložení založené na řádcích, které je vhodné pro uspořádání obsahu v mřížce buněk, jak jsme viděli.
Použití rozložení toku:
- Vytvořte instanci
UICollectionViewFlowLayout
:
var layout = new UICollectionViewFlowLayout ();
- Předejte instanci konstruktoru
UICollectionView
:
simpleCollectionViewController = new SimpleCollectionViewController (layout);
To je vše, co je potřeba k rozložení obsahu v mřížce. Při změně UICollectionViewFlowLayout
orientace také úchyty odpovídajícím způsobem mění uspořádání obsahu, jak je znázorněno níže:
Sada oddílů
Pro poskytnutí nějakého místa kolem UIContentView
, rozložení mají SectionInset
vlastnost typu UIEdgeInsets
. Například následující kód poskytuje vyrovnávací paměť o 50 pixelech kolem každé části UIContentView
v případě, že je rozložena pomocí UICollectionViewFlowLayout
:
var layout = new UICollectionViewFlowLayout ();
layout.SectionInset = new UIEdgeInsets (50,50,50,50);
Výsledkem je mezera kolem oddílu, jak je znázorněno níže:
Podtřídy UICollectionViewFlowLayout
V edici, která se má použít UICollectionViewFlowLayout
přímo, může být také podtříděna pro další přizpůsobení rozložení obsahu podél řádku. Můžete to například použít k vytvoření rozložení, které nezabalí buňky do mřížky, ale místo toho vytvoří jeden řádek s vodorovným posunovacím efektem, jak je znázorněno níže:
K implementaci je potřeba provést podtřídu UICollectionViewFlowLayout
:
- Inicializace všech vlastností rozložení, které se vztahují na samotné rozložení nebo všechny položky v rozložení v konstruktoru.
- Přepsání
ShouldInvalidateLayoutForBoundsChange
, vrácení true tak, aby při hraniciUICollectionView
změn, rozložení buněk bude přepočítáno. V tomto případě se zajistí, že se během posouvání použije kód transformace použité na buňku úplně na střed. - Přepsáním
TargetContentOffset
nastavíte, aby se buňka úplně na střed přichytila ke středu přiUICollectionView
posouvání. - Přepsání
LayoutAttributesForElementsInRect
pro vrácení pole .UICollectionViewLayoutAttributes
KaždáUICollectionViewLayoutAttribute
obsahuje informace o rozložení konkrétní položky, včetně vlastností, jako je jehoCenter
,Size
ZIndex
aTransform3D
.
Následující kód ukazuje takovou implementaci:
using System;
using CoreGraphics;
using Foundation;
using UIKit;
using CoreGraphics;
using CoreAnimation;
namespace SimpleCollectionView
{
public class LineLayout : UICollectionViewFlowLayout
{
public const float ITEM_SIZE = 200.0f;
public const int ACTIVE_DISTANCE = 200;
public const float ZOOM_FACTOR = 0.3f;
public LineLayout ()
{
ItemSize = new CGSize (ITEM_SIZE, ITEM_SIZE);
ScrollDirection = UICollectionViewScrollDirection.Horizontal;
SectionInset = new UIEdgeInsets (400,0,400,0);
MinimumLineSpacing = 50.0f;
}
public override bool ShouldInvalidateLayoutForBoundsChange (CGRect newBounds)
{
return true;
}
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (CGRect rect)
{
var array = base.LayoutAttributesForElementsInRect (rect);
var visibleRect = new CGRect (CollectionView.ContentOffset, CollectionView.Bounds.Size);
foreach (var attributes in array) {
if (attributes.Frame.IntersectsWith (rect)) {
float distance = (float)(visibleRect.GetMidX () - attributes.Center.X);
float normalizedDistance = distance / ACTIVE_DISTANCE;
if (Math.Abs (distance) < ACTIVE_DISTANCE) {
float zoom = 1 + ZOOM_FACTOR * (1 - Math.Abs (normalizedDistance));
attributes.Transform3D = CATransform3D.MakeScale (zoom, zoom, 1.0f);
attributes.ZIndex = 1;
}
}
}
return array;
}
public override CGPoint TargetContentOffset (CGPoint proposedContentOffset, CGPoint scrollingVelocity)
{
float offSetAdjustment = float.MaxValue;
float horizontalCenter = (float)(proposedContentOffset.X + (this.CollectionView.Bounds.Size.Width / 2.0));
CGRect targetRect = new CGRect (proposedContentOffset.X, 0.0f, this.CollectionView.Bounds.Size.Width, this.CollectionView.Bounds.Size.Height);
var array = base.LayoutAttributesForElementsInRect (targetRect);
foreach (var layoutAttributes in array) {
float itemHorizontalCenter = (float)layoutAttributes.Center.X;
if (Math.Abs (itemHorizontalCenter - horizontalCenter) < Math.Abs (offSetAdjustment)) {
offSetAdjustment = itemHorizontalCenter - horizontalCenter;
}
}
return new CGPoint (proposedContentOffset.X + offSetAdjustment, proposedContentOffset.Y);
}
}
}
Vlastní rozložení
Kromě použití UICollectionViewFlowLayout
, rozložení lze také plně přizpůsobit děděním přímo z UICollectionViewLayout
.
Klíčové metody přepsání jsou:
PrepareLayout
– Slouží k provádění počátečních geometrických výpočtů, které budou použity v průběhu procesu rozložení.CollectionViewContentSize
– Vrátí velikost oblasti použité k zobrazení obsahu.LayoutAttributesForElementsInRect
– Stejně jako u příkladu UICollectionViewFlowLayout zobrazeného dříve se tato metoda používá k poskytování informacíUICollectionView
o tom, jak jednotlivé položky rozložení. Na rozdíl od možnostiUICollectionViewFlowLayout
, při vytváření vlastního rozložení však můžete položky umístit, ale zvolíte.
Stejný obsah se například může zobrazit v kruhovém rozložení, jak je znázorněno níže:
Výkonná věc ohledně rozložení je, že změna z rozložení podobné mřížky na vodorovné posouvání a následně k tomuto kruhovém rozložení vyžaduje, aby se UICollectionView
změnila pouze třída rozložení. UICollectionView
V souboru , jeho delegát nebo kód zdroje dat se vůbec nic nemění.
Změny v iOSu 9
V iOSu 9 teď zobrazení kolekce (UICollectionView
) podporuje přeuspořádání položek mimo pole přidáním nového výchozího rozpoznávání gest a několika nových podpůrných metod.
Pomocí těchto nových metod můžete snadno implementovat přetažení a změnit pořadí v zobrazení kolekce a mít možnost přizpůsobit vzhled položek během jakékoli fáze procesu změny pořadí.
V tomto článku se podíváme na implementaci změny pořadí přetažení do pořadí v aplikaci Xamarin.iOS a také na některé další změny, které systém iOS 9 provedl v ovládacím prvku zobrazení kolekce:
Změna pořadí položek
Jak jsme uvedli výše, jedním z nejvýznamnějších změn v zobrazení kolekce v iOSu 9 bylo přidání snadné funkce přetažení do pořadí.
V iOSu 9 je nejrychlejší způsob, jak do zobrazení kolekce přidat změny pořadí, použít UICollectionViewController
.
Kontroler zobrazení kolekce má nyní InstallsStandardGestureForInteractiveMovement
vlastnost, která přidává standardní rozpoznávání gest, které podporuje přetahování položek v kolekci.
Vzhledem k tomu, že výchozí hodnota je true
, musíte implementovat MoveItem
pouze metodu UICollectionViewDataSource
třídy pro podporu přetažení do pořadí. Příklad:
public override void MoveItem (UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
{
// Reorder our list of items
...
}
Příklad jednoduchého změny pořadí
Jako rychlý příklad spusťte nový projekt Xamarin.iOS a upravte soubor Main.storyboard . Lze přetáhnout na návrhovou UICollectionViewController
plochu:
Vyberte zobrazení kolekce (může být nejjednodušší to udělat v osnově dokumentu). Na kartě Rozložení oblasti Vlastnosti nastavte následující velikosti, jak je znázorněno na následujícím snímku obrazovky:
- Velikost buňky: Šířka – 60 | Výška – 60
- Velikost záhlaví: Šířka – 0 | Výška – 0
- Velikost zápatí: Šířka – 0 | Výška – 0
- Minimální mezery: pro buňky – 8 | Pro linky – 8
- Oddíly Inset: Top – 16 | Dole – 16 | Vlevo – 16 | Vpravo – 16
Dále upravte výchozí buňku:
- Změna barvy pozadí na modrou
- Přidání popisku, který bude fungovat jako název buňky
- Nastavení identifikátoru opakovaného použití na buňku
Přidejte omezení pro zarovnání popisku na střed uvnitř buňky při změně velikosti:
V oblasti vlastností CollectionViewCell a nastavte třídu na TextCollectionViewCell
:
Nastavte opakovaně použitelné zobrazení kolekce naCell
:
Nakonec vyberte popisek a pojmenujte ho TextLabel
:
TextCollectionViewCell
Upravte třídu a přidejte následující vlastnosti:
using System;
using Foundation;
using UIKit;
namespace CollectionView
{
public partial class TextCollectionViewCell : UICollectionViewCell
{
#region Computed Properties
public string Title {
get { return TextLabel.Text; }
set { TextLabel.Text = value; }
}
#endregion
#region Constructors
public TextCollectionViewCell (IntPtr handle) : base (handle)
{
}
#endregion
}
}
Text
Zde je vlastnost popisku vystavena jako název buňky, takže ji lze nastavit z kódu.
Přidejte do projektu novou třídu jazyka C# a zavolejte ji WaterfallCollectionSource
. Upravte soubor a udělejte ho takto:
using System;
using Foundation;
using UIKit;
using System.Collections.Generic;
namespace CollectionView
{
public class WaterfallCollectionSource : UICollectionViewDataSource
{
#region Computed Properties
public WaterfallCollectionView CollectionView { get; set;}
public List<int> Numbers { get; set; } = new List<int> ();
#endregion
#region Constructors
public WaterfallCollectionSource (WaterfallCollectionView collectionView)
{
// Initialize
CollectionView = collectionView;
// Init numbers collection
for (int n = 0; n < 100; ++n) {
Numbers.Add (n);
}
}
#endregion
#region Override Methods
public override nint NumberOfSections (UICollectionView collectionView) {
// We only have one section
return 1;
}
public override nint GetItemsCount (UICollectionView collectionView, nint section) {
// Return the number of items
return Numbers.Count;
}
public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
{
// Get a reusable cell and set {~~it's~>its~~} title from the item
var cell = collectionView.DequeueReusableCell ("Cell", indexPath) as TextCollectionViewCell;
cell.Title = Numbers [(int)indexPath.Item].ToString();
return cell;
}
public override bool CanMoveItem (UICollectionView collectionView, NSIndexPath indexPath) {
// We can always move items
return true;
}
public override void MoveItem (UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
{
// Reorder our list of items
var item = Numbers [(int)sourceIndexPath.Item];
Numbers.RemoveAt ((int)sourceIndexPath.Item);
Numbers.Insert ((int)destinationIndexPath.Item, item);
}
#endregion
}
}
Tato třída bude zdrojem dat pro zobrazení kolekce a poskytne informace pro každou buňku v kolekci.
Všimněte si, že metoda je implementována tak MoveItem
, aby položky v kolekci byly přeuspořádané.
Přidejte do projektu další novou třídu jazyka C# a zavolejte ji WaterfallCollectionDelegate
. Upravte tento soubor a udělejte ho takto:
using System;
using Foundation;
using UIKit;
using System.Collections.Generic;
namespace CollectionView
{
public class WaterfallCollectionDelegate : UICollectionViewDelegate
{
#region Computed Properties
public WaterfallCollectionView CollectionView { get; set;}
#endregion
#region Constructors
public WaterfallCollectionDelegate (WaterfallCollectionView collectionView)
{
// Initialize
CollectionView = collectionView;
}
#endregion
#region Overrides Methods
public override bool ShouldHighlightItem (UICollectionView collectionView, NSIndexPath indexPath) {
// Always allow for highlighting
return true;
}
public override void ItemHighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
// Get cell and change to green background
var cell = collectionView.CellForItem(indexPath);
cell.ContentView.BackgroundColor = UIColor.FromRGB(183,208,57);
}
public override void ItemUnhighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
// Get cell and return to blue background
var cell = collectionView.CellForItem(indexPath);
cell.ContentView.BackgroundColor = UIColor.FromRGB(164,205,255);
}
#endregion
}
}
Bude to fungovat jako delegát pro naše zobrazení kolekce. Metody byly přepsány, aby zvýrazňovaly buňku, když s ní uživatel pracuje v zobrazení kolekce.
Přidejte do projektu poslední třídu jazyka C# a zavolejte ji WaterfallCollectionView
. Upravte tento soubor a udělejte ho takto:
using System;
using UIKit;
using System.Collections.Generic;
using Foundation;
namespace CollectionView
{
[Register("WaterfallCollectionView")]
public class WaterfallCollectionView : UICollectionView
{
#region Constructors
public WaterfallCollectionView (IntPtr handle) : base (handle)
{
}
#endregion
#region Override Methods
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Initialize
DataSource = new WaterfallCollectionSource(this);
Delegate = new WaterfallCollectionDelegate(this);
}
#endregion
}
}
Všimněte si, že DataSource
a Delegate
že jsme vytvořili výše, jsou nastaveny při vytváření zobrazení kolekce z jeho scénáře (nebo souboru .xib ).
Znovu upravte soubor Main.storyboard a vyberte zobrazení kolekce a přepněte na vlastnosti. Nastavte třídu na vlastní WaterfallCollectionView
třídu, kterou jsme definovali výše:
Uložte změny provedené v uživatelském rozhraní a spusťte aplikaci. Pokud uživatel vybere položku ze seznamu a přetáhne ji do nového umístění, ostatní položky se automaticky animují, jakmile se přesunou mimo cestu položky. Když uživatel položku zahodí do nového umístění, zůstane na daném místě. Příklad:
Použití vlastního rozpoznávání gest
V případech, kdy nemůžete použít UICollectionViewController
a musíte použít běžnou UIViewController
, nebo pokud chcete převzít větší kontrolu nad gestem přetažení, můžete vytvořit vlastní rozpoznávání gest a přidat ho do zobrazení kolekce při načtení zobrazení. Příklad:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Create a custom gesture recognizer
var longPressGesture = new UILongPressGestureRecognizer ((gesture) => {
// Take action based on state
switch(gesture.State) {
case UIGestureRecognizerState.Began:
var selectedIndexPath = CollectionView.IndexPathForItemAtPoint(gesture.LocationInView(View));
if (selectedIndexPath !=null) {
CollectionView.BeginInteractiveMovementForItem(selectedIndexPath);
}
break;
case UIGestureRecognizerState.Changed:
CollectionView.UpdateInteractiveMovementTargetPosition(gesture.LocationInView(View));
break;
case UIGestureRecognizerState.Ended:
CollectionView.EndInteractiveMovement();
break;
default:
CollectionView.CancelInteractiveMovement();
break;
}
});
// Add the custom recognizer to the collection view
CollectionView.AddGestureRecognizer(longPressGesture);
}
Zde používáme několik nových metod přidaných do zobrazení kolekce k implementaci a řízení operace přetažení:
BeginInteractiveMovementForItem
- Označí začátek operace přesunutí.UpdateInteractiveMovementTargetPosition
– Odesílá se při aktualizaci umístění položky.EndInteractiveMovement
- Označí konec přesunutí položky.CancelInteractiveMovement
– Označí uživatele, který zruší operaci přesunutí.
Při spuštění aplikace bude operace přetažení fungovat stejně jako výchozí rozpoznávání gest přetažení, které je součástí zobrazení kolekce.
Vlastní rozložení a změna pořadí
V iOSu 9 bylo přidáno několik nových metod pro práci s přetahováním myší a vlastními rozloženími v zobrazení kolekce. Pokud chcete tuto funkci prozkoumat, přidáme do kolekce vlastní rozložení.
Nejprve přidejte do projektu novou třídu WaterfallCollectionLayout
jazyka C#. Upravte ho a udělejte ho takto:
using System;
using Foundation;
using UIKit;
using System.Collections.Generic;
using CoreGraphics;
namespace CollectionView
{
[Register("WaterfallCollectionLayout")]
public class WaterfallCollectionLayout : UICollectionViewLayout
{
#region Private Variables
private int columnCount = 2;
private nfloat minimumColumnSpacing = 10;
private nfloat minimumInterItemSpacing = 10;
private nfloat headerHeight = 0.0f;
private nfloat footerHeight = 0.0f;
private UIEdgeInsets sectionInset = new UIEdgeInsets(0, 0, 0, 0);
private WaterfallCollectionRenderDirection itemRenderDirection = WaterfallCollectionRenderDirection.ShortestFirst;
private Dictionary<nint,UICollectionViewLayoutAttributes> headersAttributes = new Dictionary<nint, UICollectionViewLayoutAttributes>();
private Dictionary<nint,UICollectionViewLayoutAttributes> footersAttributes = new Dictionary<nint, UICollectionViewLayoutAttributes>();
private List<CGRect> unionRects = new List<CGRect>();
private List<nfloat> columnHeights = new List<nfloat>();
private List<UICollectionViewLayoutAttributes> allItemAttributes = new List<UICollectionViewLayoutAttributes>();
private List<List<UICollectionViewLayoutAttributes>> sectionItemAttributes = new List<List<UICollectionViewLayoutAttributes>>();
private nfloat unionSize = 20;
#endregion
#region Computed Properties
[Export("ColumnCount")]
public int ColumnCount {
get { return columnCount; }
set {
WillChangeValue ("ColumnCount");
columnCount = value;
DidChangeValue ("ColumnCount");
InvalidateLayout ();
}
}
[Export("MinimumColumnSpacing")]
public nfloat MinimumColumnSpacing {
get { return minimumColumnSpacing; }
set {
WillChangeValue ("MinimumColumnSpacing");
minimumColumnSpacing = value;
DidChangeValue ("MinimumColumnSpacing");
InvalidateLayout ();
}
}
[Export("MinimumInterItemSpacing")]
public nfloat MinimumInterItemSpacing {
get { return minimumInterItemSpacing; }
set {
WillChangeValue ("MinimumInterItemSpacing");
minimumInterItemSpacing = value;
DidChangeValue ("MinimumInterItemSpacing");
InvalidateLayout ();
}
}
[Export("HeaderHeight")]
public nfloat HeaderHeight {
get { return headerHeight; }
set {
WillChangeValue ("HeaderHeight");
headerHeight = value;
DidChangeValue ("HeaderHeight");
InvalidateLayout ();
}
}
[Export("FooterHeight")]
public nfloat FooterHeight {
get { return footerHeight; }
set {
WillChangeValue ("FooterHeight");
footerHeight = value;
DidChangeValue ("FooterHeight");
InvalidateLayout ();
}
}
[Export("SectionInset")]
public UIEdgeInsets SectionInset {
get { return sectionInset; }
set {
WillChangeValue ("SectionInset");
sectionInset = value;
DidChangeValue ("SectionInset");
InvalidateLayout ();
}
}
[Export("ItemRenderDirection")]
public WaterfallCollectionRenderDirection ItemRenderDirection {
get { return itemRenderDirection; }
set {
WillChangeValue ("ItemRenderDirection");
itemRenderDirection = value;
DidChangeValue ("ItemRenderDirection");
InvalidateLayout ();
}
}
#endregion
#region Constructors
public WaterfallCollectionLayout ()
{
}
public WaterfallCollectionLayout(NSCoder coder) : base(coder) {
}
#endregion
#region Public Methods
public nfloat ItemWidthInSectionAtIndex(int section) {
var width = CollectionView.Bounds.Width - SectionInset.Left - SectionInset.Right;
return (nfloat)Math.Floor ((width - ((ColumnCount - 1) * MinimumColumnSpacing)) / ColumnCount);
}
#endregion
#region Override Methods
public override void PrepareLayout ()
{
base.PrepareLayout ();
// Get the number of sections
var numberofSections = CollectionView.NumberOfSections();
if (numberofSections == 0)
return;
// Reset collections
headersAttributes.Clear ();
footersAttributes.Clear ();
unionRects.Clear ();
columnHeights.Clear ();
allItemAttributes.Clear ();
sectionItemAttributes.Clear ();
// Initialize column heights
for (int n = 0; n < ColumnCount; n++) {
columnHeights.Add ((nfloat)0);
}
// Process all sections
nfloat top = 0.0f;
var attributes = new UICollectionViewLayoutAttributes ();
var columnIndex = 0;
for (nint section = 0; section < numberofSections; ++section) {
// Calculate section specific metrics
var minimumInterItemSpacing = (MinimumInterItemSpacingForSection == null) ? MinimumColumnSpacing :
MinimumInterItemSpacingForSection (CollectionView, this, section);
// Calculate widths
var width = CollectionView.Bounds.Width - SectionInset.Left - SectionInset.Right;
var itemWidth = (nfloat)Math.Floor ((width - ((ColumnCount - 1) * MinimumColumnSpacing)) / ColumnCount);
// Calculate section header
var heightHeader = (HeightForHeader == null) ? HeaderHeight :
HeightForHeader (CollectionView, this, section);
if (heightHeader > 0) {
attributes = UICollectionViewLayoutAttributes.CreateForSupplementaryView (UICollectionElementKindSection.Header, NSIndexPath.FromRowSection (0, section));
attributes.Frame = new CGRect (0, top, CollectionView.Bounds.Width, heightHeader);
headersAttributes.Add (section, attributes);
allItemAttributes.Add (attributes);
top = attributes.Frame.GetMaxY ();
}
top += SectionInset.Top;
for (int n = 0; n < ColumnCount; n++) {
columnHeights [n] = top;
}
// Calculate Section Items
var itemCount = CollectionView.NumberOfItemsInSection(section);
List<UICollectionViewLayoutAttributes> itemAttributes = new List<UICollectionViewLayoutAttributes> ();
for (nint n = 0; n < itemCount; n++) {
var indexPath = NSIndexPath.FromRowSection (n, section);
columnIndex = NextColumnIndexForItem (n);
var xOffset = SectionInset.Left + (itemWidth + MinimumColumnSpacing) * (nfloat)columnIndex;
var yOffset = columnHeights [columnIndex];
var itemSize = (SizeForItem == null) ? new CGSize (0, 0) : SizeForItem (CollectionView, this, indexPath);
nfloat itemHeight = 0.0f;
if (itemSize.Height > 0.0f && itemSize.Width > 0.0f) {
itemHeight = (nfloat)Math.Floor (itemSize.Height * itemWidth / itemSize.Width);
}
attributes = UICollectionViewLayoutAttributes.CreateForCell (indexPath);
attributes.Frame = new CGRect (xOffset, yOffset, itemWidth, itemHeight);
itemAttributes.Add (attributes);
allItemAttributes.Add (attributes);
columnHeights [columnIndex] = attributes.Frame.GetMaxY () + MinimumInterItemSpacing;
}
sectionItemAttributes.Add (itemAttributes);
// Calculate Section Footer
nfloat footerHeight = 0.0f;
columnIndex = LongestColumnIndex();
top = columnHeights [columnIndex] - MinimumInterItemSpacing + SectionInset.Bottom;
footerHeight = (HeightForFooter == null) ? FooterHeight : HeightForFooter(CollectionView, this, section);
if (footerHeight > 0) {
attributes = UICollectionViewLayoutAttributes.CreateForSupplementaryView (UICollectionElementKindSection.Footer, NSIndexPath.FromRowSection (0, section));
attributes.Frame = new CGRect (0, top, CollectionView.Bounds.Width, footerHeight);
footersAttributes.Add (section, attributes);
allItemAttributes.Add (attributes);
top = attributes.Frame.GetMaxY ();
}
for (int n = 0; n < ColumnCount; n++) {
columnHeights [n] = top;
}
}
var i =0;
var attrs = allItemAttributes.Count;
while(i < attrs) {
var rect1 = allItemAttributes [i].Frame;
i = (int)Math.Min (i + unionSize, attrs) - 1;
var rect2 = allItemAttributes [i].Frame;
unionRects.Add (CGRect.Union (rect1, rect2));
i++;
}
}
public override CGSize CollectionViewContentSize {
get {
if (CollectionView.NumberOfSections () == 0) {
return new CGSize (0, 0);
}
var contentSize = CollectionView.Bounds.Size;
contentSize.Height = columnHeights [0];
return contentSize;
}
}
public override UICollectionViewLayoutAttributes LayoutAttributesForItem (NSIndexPath indexPath)
{
if (indexPath.Section >= sectionItemAttributes.Count) {
return null;
}
if (indexPath.Item >= sectionItemAttributes [indexPath.Section].Count) {
return null;
}
var list = sectionItemAttributes [indexPath.Section];
return list [(int)indexPath.Item];
}
public override UICollectionViewLayoutAttributes LayoutAttributesForSupplementaryView (NSString kind, NSIndexPath indexPath)
{
var attributes = new UICollectionViewLayoutAttributes ();
switch (kind) {
case "header":
attributes = headersAttributes [indexPath.Section];
break;
case "footer":
attributes = footersAttributes [indexPath.Section];
break;
}
return attributes;
}
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (CGRect rect)
{
var begin = 0;
var end = unionRects.Count;
List<UICollectionViewLayoutAttributes> attrs = new List<UICollectionViewLayoutAttributes> ();
for (int i = 0; i < end; i++) {
if (rect.IntersectsWith(unionRects[i])) {
begin = i * (int)unionSize;
}
}
for (int i = end - 1; i >= 0; i--) {
if (rect.IntersectsWith (unionRects [i])) {
end = (int)Math.Min ((i + 1) * (int)unionSize, allItemAttributes.Count);
break;
}
}
for (int i = begin; i < end; i++) {
var attr = allItemAttributes [i];
if (rect.IntersectsWith (attr.Frame)) {
attrs.Add (attr);
}
}
return attrs.ToArray();
}
public override bool ShouldInvalidateLayoutForBoundsChange (CGRect newBounds)
{
var oldBounds = CollectionView.Bounds;
return (newBounds.Width != oldBounds.Width);
}
#endregion
#region Private Methods
private int ShortestColumnIndex() {
var index = 0;
var shortestHeight = nfloat.MaxValue;
var n = 0;
// Scan each column for the shortest height
foreach (nfloat height in columnHeights) {
if (height < shortestHeight) {
shortestHeight = height;
index = n;
}
++n;
}
return index;
}
private int LongestColumnIndex() {
var index = 0;
var longestHeight = nfloat.MinValue;
var n = 0;
// Scan each column for the shortest height
foreach (nfloat height in columnHeights) {
if (height > longestHeight) {
longestHeight = height;
index = n;
}
++n;
}
return index;
}
private int NextColumnIndexForItem(nint item) {
var index = 0;
switch (ItemRenderDirection) {
case WaterfallCollectionRenderDirection.ShortestFirst:
index = ShortestColumnIndex ();
break;
case WaterfallCollectionRenderDirection.LeftToRight:
index = ColumnCount;
break;
case WaterfallCollectionRenderDirection.RightToLeft:
index = (ColumnCount - 1) - ((int)item / ColumnCount);
break;
}
return index;
}
#endregion
#region Events
public delegate CGSize WaterfallCollectionSizeDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, NSIndexPath indexPath);
public delegate nfloat WaterfallCollectionFloatDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, nint section);
public delegate UIEdgeInsets WaterfallCollectionEdgeInsetsDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, nint section);
public event WaterfallCollectionSizeDelegate SizeForItem;
public event WaterfallCollectionFloatDelegate HeightForHeader;
public event WaterfallCollectionFloatDelegate HeightForFooter;
public event WaterfallCollectionEdgeInsetsDelegate InsetForSection;
public event WaterfallCollectionFloatDelegate MinimumInterItemSpacingForSection;
#endregion
}
}
Tuto třídu lze použít k poskytnutí vlastního dvousloupce, rozložení vodopádového typu do zobrazení kolekce.
Kód používá kódování klíč-hodnota (prostřednictvím WillChangeValue
a DidChangeValue
metod) k poskytování datové vazby pro naše vypočítané vlastnosti v této třídě.
Dále upravte WaterfallCollectionSource
a proveďte následující změny a doplňky:
private Random rnd = new Random();
...
public List<nfloat> Heights { get; set; } = new List<nfloat> ();
...
public WaterfallCollectionSource (WaterfallCollectionView collectionView)
{
// Initialize
CollectionView = collectionView;
// Init numbers collection
for (int n = 0; n < 100; ++n) {
Numbers.Add (n);
Heights.Add (rnd.Next (0, 100) + 40.0f);
}
}
Tím se vytvoří náhodná výška pro každou položku, která se zobrazí v seznamu.
Dále upravte WaterfallCollectionView
třídu a přidejte následující pomocnou vlastnost:
public WaterfallCollectionSource Source {
get { return (WaterfallCollectionSource)DataSource; }
}
To vám usnadní přístup ke zdroji dat (a výšce položek) z vlastního rozložení.
Nakonec upravte kontroler zobrazení a přidejte následující kód:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
var waterfallLayout = new WaterfallCollectionLayout ();
// Wireup events
waterfallLayout.SizeForItem += (collectionView, layout, indexPath) => {
var collection = collectionView as WaterfallCollectionView;
return new CGSize((View.Bounds.Width-40)/3,collection.Source.Heights[(int)indexPath.Item]);
};
// Attach the custom layout to the collection
CollectionView.SetCollectionViewLayout(waterfallLayout, false);
}
Tím se vytvoří instance vlastního rozložení, nastaví událost tak, aby poskytovala velikost každé položky a připojila nové rozložení k zobrazení kolekce.
Pokud znovu spustíme aplikaci Xamarin.iOS, bude zobrazení kolekce vypadat takto:
Položky se stále dají přeuspořádat stejně jako předtím, ale při vyřazení se změní velikost tak, aby odpovídala jejich novému umístění.
Změny zobrazení kolekce
V následujících částech se podrobně podíváme na změny provedené v každé třídě v zobrazení kolekce v iOSu 9.
UICollectionView
Ve třídě pro iOS 9 byly provedeny UICollectionView
následující změny nebo doplňky:
BeginInteractiveMovementForItem
– Označí začátek operace přetažení.CancelInteractiveMovement
– Informuje zobrazení kolekce, že uživatel zrušil operaci přetažení.EndInteractiveMovement
– Informuje zobrazení kolekce, že uživatel dokončil operaci přetažení.GetIndexPathsForVisibleSupplementaryElements
– VrátíindexPath
záhlaví nebo zápatí v oddílu zobrazení kolekce.GetSupplementaryView
– Vrátí dané záhlaví nebo zápatí.GetVisibleSupplementaryViews
– Vrátí seznam všech viditelných záhlaví a zápatí.UpdateInteractiveMovementTargetPosition
– Informuje zobrazení kolekce, že uživatel přesunul nebo přesouvá položku během operace přetažení.
UICollectionViewController
V iOSu UICollectionViewController
9 byly provedeny následující změny nebo doplňky třídy:
InstallsStandardGestureForInteractiveMovement
– Pokudtrue
se použije nový rozpoznávání gest, které automaticky podporuje přeuspořádané přetažení do pořadí.CanMoveItem
– Informuje zobrazení kolekce, pokud lze danou položku přetáhnout.GetTargetContentOffset
– Slouží k získání posunu dané položky zobrazení kolekce.GetTargetIndexPathForMove
– ZískáindexPath
danou položku pro operaci přetažení.MoveItem
– Přesune pořadí dané položky v seznamu.
UICollectionViewDataSource
V iOSu UICollectionViewDataSource
9 byly provedeny následující změny nebo doplňky třídy:
CanMoveItem
– Informuje zobrazení kolekce, pokud lze danou položku přetáhnout.MoveItem
– Přesune pořadí dané položky v seznamu.
UICollectionViewDelegate
V iOSu UICollectionViewDelegate
9 byly provedeny následující změny nebo doplňky třídy:
GetTargetContentOffset
– Slouží k získání posunu dané položky zobrazení kolekce.GetTargetIndexPathForMove
– ZískáindexPath
danou položku pro operaci přetažení.
UICollectionViewFlowLayout
V iOSu UICollectionViewFlowLayout
9 byly provedeny následující změny nebo doplňky třídy:
SectionFootersPinToVisibleBounds
– Připíná zápatí oddílů k viditelným hranicím zobrazení kolekce.SectionHeadersPinToVisibleBounds
– Přilepí záhlaví oddílů k viditelným hranicím zobrazení kolekce.
UICollectionViewLayout
V iOSu UICollectionViewLayout
9 byly provedeny následující změny nebo doplňky třídy:
GetInvalidationContextForEndingInteractiveMovementOfItems
– Vrátí neplatný kontext na konci operace přetažení, když uživatel buď dokončí přetažení, nebo ho zruší.GetInvalidationContextForInteractivelyMovingItems
– Vrátí kontext zneplatnění na začátku operace přetažení.GetLayoutAttributesForInteractivelyMovingItem
– Získá atributy rozložení pro danou položku při přetažení položky.GetTargetIndexPathForInteractivelyMovingItem
– VrátíindexPath
položku, která je v daném bodě při přetažení položky.
UICollectionViewLayoutAttributes
V iOSu UICollectionViewLayoutAttributes
9 byly provedeny následující změny nebo doplňky třídy:
CollisionBoundingPath
– Vrátí kolizní cestu dvou položek během operace přetažení.CollisionBoundsType
– Vrátí typ kolize (jako aUIDynamicItemCollisionBoundsType
), ke které došlo během operace přetažení.
UICollectionViewLayoutInvalidationContext
V iOSu UICollectionViewLayoutInvalidationContext
9 byly provedeny následující změny nebo doplňky třídy:
InteractiveMovementTarget
– Vrátí cílovou položku operace přetažení.PreviousIndexPathsForInteractivelyMovingItems
– VrátíindexPaths
ostatní položky zahrnuté do přetažení, aby se operace přeuspořádaly.TargetIndexPathsForInteractivelyMovingItems
– VrátíindexPaths
položky, které budou v důsledku operace přeuspořádané přetažením na pořadí.
UICollectionViewSource
V iOSu UICollectionViewSource
9 byly provedeny následující změny nebo doplňky třídy:
CanMoveItem
– Informuje zobrazení kolekce, pokud lze danou položku přetáhnout.GetTargetContentOffset
– Vrátí posuny položek, které budou přesunuty pomocí operace přetažení do změny pořadí.GetTargetIndexPathForMove
– VrátíindexPath
položku, která bude přesunuta během operace přetažení do pořadí.MoveItem
– Přesune pořadí dané položky v seznamu.
Shrnutí
Tento článek se zabývá změnami zobrazení kolekcí v iOSu 9 a popisuje, jak je implementovat v Xamarin.iOS. Zahrnoval implementaci jednoduché akce přetažení do pořadí v zobrazení kolekce; pomocí vlastního rozpoznávání gest s přeuspořádaným přetažením do pořadí; a vliv změny pořadí přetažení na vlastní rozložení zobrazení kolekce.