Skip to content

Instantly share code, notes, and snippets.

@mzhukovs
Last active June 29, 2018 01:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mzhukovs/4d2a7fc77f2b131e8309c9ae619f5f30 to your computer and use it in GitHub Desktop.
Save mzhukovs/4d2a7fc77f2b131e8309c9ae619f5f30 to your computer and use it in GitHub Desktop.
FlexLayout RepeaterView for Xamarin Forms with Fade In Animation
public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);
/// <summary>
/// Don't set IsVisible to false or you will have a bad time
/// this won't relayout its child elements when you change the visibility
/// </summary>
// in lieu of an actual Xamarin Forms ItemsControl, this is a heavily modified version of code from https://forums.xamarin.com/discussion/21635/xforms-needs-an-itemscontrol
public class RepeaterView : FlexLayout
{
public RepeaterView()
{
Dictionary<Element, IDisposable> activatedViews
= new Dictionary<Element, IDisposable>();
}
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(RepeaterView),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay,
propertyChanged: ItemsChanged);
public static readonly BindableProperty ItemTemplateProperty =
BindableProperty.Create(
nameof(ItemTemplate),
typeof(DataTemplate),
typeof(RepeaterView),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay);
/// <summary>
/// Set to True in order to fade in each child element as it gets added to the repeater view
/// </summary>
public bool ApplyFadingToChildren { get; set; } = true;
public int FadeDuration { get; set; } = 500;
public int AddChildDelay { get; set; } = 180;
bool waitingForBindingContext = false;
public event RepeaterViewItemAddedEventHandler ItemCreated;
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext != null && waitingForBindingContext && ItemsSource != null)
{
ItemsChanged(this, null, ItemsSource);
}
}
private static async void ItemsChanged(BindableObject bindable, object old, object newVal)
{
IEnumerable oldValue = old as IEnumerable;
IEnumerable newValue = newVal as IEnumerable;
var control = (RepeaterView)bindable;
var oldObservableCollection = oldValue as INotifyCollectionChanged;
if (oldObservableCollection != null)
{
oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
}
//HACK:SHANE
if (control.BindingContext == null)
{
control.waitingForBindingContext = true;
//this means this control has been removed from the visual tree
//so don't update it other wise you get random null reference exceptions
return;
}
control.waitingForBindingContext = false;
var newObservableCollection = newValue as INotifyCollectionChanged;
if (newObservableCollection != null)
{
newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
}
try
{
control.Children.Clear();
if (newValue != null)
{
var i = 0;
foreach (var item in newValue)
{
if (control.AddChildDelay > 0 && i > 0)
await Task.Delay(control.AddChildDelay);
var view = control.CreateChildViewFor(item);
control.Children.Add(view);
control.OnItemCreated(view);
i++;
}
}
control.UpdateChildrenLayout();
control.InvalidateLayout();
}
catch (NullReferenceException)
{
try
{
Debug.WriteLine(
String.Format($"RepeaterView: NullReferenceException Parent:{control.Parent} ParentView:{control.Parent} IsVisible:{control.IsVisible}")
);
}
catch (Exception exc)
{
Debug.WriteLine($"NullReferenceException Logging Failed {exc}");
}
}
}
protected virtual void OnItemCreated(View view)
{
if (this.ItemCreated != null)
{
ItemCreated.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));
}
}
private async void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
try
{
var invalidate = false;
List<View> createdViews = new List<View>();
if (e.Action == NotifyCollectionChangedAction.Reset)
{
var list = sender as IEnumerable;
this.Children.SyncList(
list,
(item) =>
{
var view = this.CreateChildViewFor(item);
createdViews.Add(view);
return view;
}, (item, view) => view.BindingContext == item,
null);
foreach (View view in createdViews)
{
OnItemCreated(view);
}
invalidate = true;
}
if (e.OldItems != null)
{
this.Children.RemoveAt(e.OldStartingIndex);
invalidate = true;
}
if (e.NewItems != null)
{
for (var i = 0; i < e.NewItems.Count; ++i)
{
var item = e.NewItems[i];
if (this.AddChildDelay > 0 && i > 0)
await Task.Delay(this.AddChildDelay);
var view = this.CreateChildViewFor(item);
this.Children.Insert(i + e.NewStartingIndex, view);
OnItemCreated(view);
}
invalidate = true;
}
if (invalidate)
{
this.UpdateChildrenLayout();
this.InvalidateLayout();
}
}
catch (NullReferenceException)
{
try
{
Debug.WriteLine(
$"RepeaterView.OnItemsSourceCollectionChanged: NullReferenceException Parent:{Parent} ParentView:{Parent} IsVisible:{IsVisible} BindingContext: {BindingContext}"
);
}
catch (Exception exc)
{
Debug.WriteLine($"OnItemsSourceCollectionChanged: NullReferenceException Logging Failed {exc}");
}
}
}
private View CreateChildViewFor(object item)
{
this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
View view;
if (this.ItemTemplate is DataTemplateSelector)
{
var dts = (DataTemplateSelector)this.ItemTemplate;
view = (View)dts.SelectTemplate(item, null).CreateContent();
}
else
{
view = (View)this.ItemTemplate.CreateContent();
}
if (ApplyFadingToChildren)
{
view.Opacity = 0;
var fadeInTrigger = new Trigger(typeof(View));
fadeInTrigger.Property = View.IsVisibleProperty;
fadeInTrigger.Value = true;
fadeInTrigger.EnterActions.Add(new FadeTriggerAction() { Duration = (uint)this.FadeDuration });
fadeInTrigger.ExitActions.Add(new FadeTriggerAction() { Duration = (uint)this.FadeDuration, FadingOut = true });
view.Triggers.Add(fadeInTrigger);
}
return view;
}
}
public class RepeaterViewItemAddedEventArgs : EventArgs
{
private readonly View view;
private readonly object model;
public RepeaterViewItemAddedEventArgs(View view, object model)
{
this.view = view;
this.model = model;
}
public View View { get { return view; } }
public object Model { get { return model; } }
}
public class FadeTriggerAction : TriggerAction<View>
{
public FadeTriggerAction() { }
public bool FadingOut { set; get; } = false;
public uint Duration { set; get; } = 500;
protected override void Invoke(View visual)
{
if (FadingOut)
visual.FadeTo(0, Duration);
else
visual.FadeTo(1, Duration);
}
}
public static class IListMixIns
{
public static void SyncList<T>(
this IList<T> This,
IEnumerable<T> sourceList)
{
This.SyncList<T, T>(sourceList, x => x, (x, y) => x.Equals(y), null);
}
public static void SyncList<T>(
this IList<T> This,
IEnumerable sourceList,
Func<object, T> selector,
Func<object, T, bool> areEqual,
Action<object, T> updateExisting,
bool dontRemove = false)
{
var sourceListEnum = sourceList.OfType<Object>().ToList();
//passengers
foreach (T dest in This.ToList())
{
var match = sourceListEnum.FirstOrDefault(p => areEqual(p, dest));
if (match != null)
{
if (updateExisting != null)
updateExisting(match, dest);
}
else if (!dontRemove)
{
This.Remove(dest);
}
}
sourceListEnum.Where(x => !This.Any(p => areEqual(x, p)))
.ToList().ForEach(p =>
{
if (This.Count >= sourceListEnum.IndexOf(p))
This.Insert(sourceListEnum.IndexOf(p), selector(p));
else
{
var result = selector(p);
if (!EqualityComparer<T>.Default.Equals(result, default(T)))
This.Add(result);
}
});
}
public static bool IsEmpty<T>(this IEnumerable<T> list)
{
return !list.Any();
}
public static bool IsEmpty<T>(this Array list)
{
return list.Length == 0;
}
public static bool IsEmpty<T>(this List<T> list)
{
return list.Count == 0;
}
public static void ForEach<T>(this IEnumerable<T> list, Action<T> doMe)
{
foreach (var item in list)
{
doMe(item);
}
}
public static void SyncList<T, Source>(
this IList<T> This,
IEnumerable<Source> sourceList,
Func<Source, T> selector,
Func<Source, T, bool> areEqual,
Action<Source, T> updateExisting,
bool dontRemove = false)
{
//passengers
foreach (T dest in This.ToList())
{
var match = sourceList.FirstOrDefault(p => areEqual(p, dest));
if (!EqualityComparer<Source>.Default.Equals(match, default(Source)))
{
updateExisting?.Invoke(match, dest);
}
else if (!dontRemove)
{
This.Remove(dest);
}
}
sourceList.Where(x => !This.Any(p => areEqual(x, p)))
.ToList().ForEach(p =>
{
var result = selector(p);
if (!EqualityComparer<T>.Default.Equals(result, default(T)))
This.Add(result);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment