Skip to content

Instantly share code, notes, and snippets.

@mmierzwa
Created February 23, 2018 08:14
Show Gist options
  • Save mmierzwa/0d19d3e0b5e5c966b734448f7beb061e to your computer and use it in GitHub Desktop.
Save mmierzwa/0d19d3e0b5e5c966b734448f7beb061e to your computer and use it in GitHub Desktop.
PullToRefreshLayout on Android
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Support.V4.Widget;
using Android.Views;
using System.Reflection;
using System.ComponentModel;
using Android.Gms.Maps;
using Android.OS;
using View = Android.Views.View;
namespace example.CustomRenderers
{
public class PullToRefreshLayoutRenderer : SwipeRefreshLayout, IVisualElementRenderer, SwipeRefreshLayout.IOnRefreshListener
{
private bool _init;
private IVisualElementRenderer _visualElementRenderer;
private BindableProperty _rendererProperty;
private bool _refreshing;
public ExtendedMapViewRenderer NestedMapView { get; set; }
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
private BindableProperty RendererProperty
{
get
{
if (_rendererProperty != null)
{
return _rendererProperty;
}
var type = Type.GetType("Xamarin.Forms.Platform.Android.Platform, Xamarin.Forms.Platform.Android");
var prop = type.GetField(nameof(RendererProperty), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
var val = prop.GetValue(null);
_rendererProperty = val as BindableProperty;
return _rendererProperty;
}
}
public override bool IsInLayout => this.SafeExecute(() => base.IsInLayout);
public override bool IsLayoutRequested => this.SafeExecute(() => base.IsLayoutRequested);
public override ViewStates Visibility
{
get => this.SafeExecute(() => base.Visibility);
set => this.SafeExecute(() => base.Visibility = value);
}
public override bool Refreshing
{
get => _refreshing;
set
{
this.SafeExecute(() =>
{
_refreshing = value;
if (RefreshView != null && RefreshView.IsRefreshing != _refreshing)
{
RefreshView.IsRefreshing = _refreshing;
}
if (base.Refreshing == _refreshing)
{
return;
}
base.Refreshing = _refreshing;
});
}
}
public PullToRefreshLayout RefreshView => (PullToRefreshLayout) Element;
public VisualElementTracker Tracker { get; private set; }
public VisualElement Element { get; private set; }
public ViewGroup ViewGroup => this;
public static async void Init()
{
DateTime temp = DateTime.Now;
}
public PullToRefreshLayoutRenderer()
: base(Forms.Context)
{
}
public PullToRefreshLayoutRenderer(IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public void SetElement(VisualElement element)
{
this.SafeExecute(() => SetElementInternal(element));
}
private void SetElementInternal(VisualElement element)
{
var oldElement = Element;
if (oldElement != null)
{
oldElement.PropertyChanged -= SafeHandlePropertyChanged;
}
Element = element;
if (Element != null)
{
UpdateContent();
Element.PropertyChanged += SafeHandlePropertyChanged;
}
if (!_init)
{
_init = true;
Tracker = new VisualElementTracker(this);
SetOnRefreshListener(this);
}
UpdateColors();
UpdateIsRefreshing();
UpdateIsSwipeToRefreshEnabled();
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(oldElement, this.Element));
}
private void UpdateContent()
{
if (RefreshView.Content == null)
{
return;
}
if (_visualElementRenderer != null)
{
RemoveView(_visualElementRenderer.ViewGroup);
}
_visualElementRenderer = Platform.CreateRenderer(RefreshView.Content);
try
{
RefreshView.Content.SetValue(RendererProperty, _visualElementRenderer);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Unable to sent renderer property, maybe an issue: " + ex);
}
AddView(_visualElementRenderer.ViewGroup, LayoutParams.MatchParent);
NestedMapView = FindNestedMapView(_visualElementRenderer.ViewGroup);
if (NestedMapView != null)
{
NestedMapView.UserSwipingOnMap += MapViewOnUserSwipingOnMap;
}
}
private void UpdateColors()
{
if (RefreshView == null)
{
return;
}
if (RefreshView.RefreshColor != Color.Default)
{
SetColorSchemeColors(RefreshView.RefreshColor.ToAndroid());
}
if (RefreshView.RefreshBackgroundColor != Color.Default)
{
SetProgressBackgroundColorSchemeColor(RefreshView.RefreshBackgroundColor.ToAndroid());
}
}
private void UpdateIsRefreshing()
{
Refreshing = RefreshView.IsRefreshing;
}
private void UpdateIsSwipeToRefreshEnabled()
{
Enabled = Element.IsEnabled = RefreshView.IsPullToRefreshEnabled;
}
public override bool CanChildScrollUp()
{
return CanScrollUp(_visualElementRenderer.ViewGroup);
}
private static ExtendedMapViewRenderer FindNestedMapView(ViewGroup viewGroup)
{
if (viewGroup == null)
{
return null;
}
for (var i = 0; i < viewGroup.ChildCount; i++)
{
var child = viewGroup.GetChildAt(i);
if (child is ExtendedMapViewRenderer mapView)
{
return mapView;
}
if (child is ViewGroup childViewGroup)
{
var view = FindNestedMapView(childViewGroup);
if (view != null)
{
return view;
}
}
}
return null;
}
private bool CanScrollUp(ViewGroup viewGroup)
{
if (viewGroup == null)
{
return base.CanChildScrollUp();
}
var sdk = (int)Build.VERSION.SdkInt;
if (sdk >= 16)
{
if (viewGroup.IsScrollContainer)
{
return base.CanChildScrollUp();
}
}
for (var i = 0; i < viewGroup.ChildCount; i++)
{
View child = viewGroup.GetChildAt(i);
if (child is Android.Widget.AbsListView)
{
var list = child as Android.Widget.AbsListView;
if (list.FirstVisiblePosition == 0)
{
View subChild = list.GetChildAt(0);
return subChild != null && subChild.Top != 0;
}
return true;
}
if (child is Android.Widget.ScrollView)
{
var scrollview = child as Android.Widget.ScrollView;
return (scrollview.ScrollY <= 0.0);
}
if (child is Android.Webkit.WebView)
{
var webView = child as Android.Webkit.WebView;
return (webView.ScrollY > 0.0);
}
if (child is SwipeRefreshLayout)
{
return CanScrollUp(child as ViewGroup);
}
}
return false;
}
private void MapViewOnUserSwipingOnMap(bool swipingInProgress)
{
this.SafeExecute(() => Enabled = Element.IsEnabled = RefreshView.IsPullToRefreshEnabled = !swipingInProgress);
}
public void OnRefresh()
{
RefreshView?.RefreshCommand?.Execute(null);
}
private void SafeHandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.SafeExecute(() => HandlePropertyChanged(e));
}
private void HandlePropertyChanged(PropertyChangedEventArgs e)
{
if (e.PropertyName == "Content")
{
UpdateContent();
}
else if (e.PropertyName == PullToRefreshLayout.IsPullToRefreshEnabledProperty.PropertyName)
{
UpdateIsSwipeToRefreshEnabled();
}
else if (e.PropertyName == PullToRefreshLayout.IsRefreshingProperty.PropertyName)
{
UpdateIsRefreshing();
}
else if (e.PropertyName == PullToRefreshLayout.RefreshColorProperty.PropertyName)
{
UpdateColors();
}
else if (e.PropertyName == PullToRefreshLayout.RefreshBackgroundColorProperty.PropertyName)
{
UpdateColors();
}
}
public SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
{
_visualElementRenderer.ViewGroup.Measure(widthConstraint, heightConstraint);
//Measure child here and determine size
return new SizeRequest(new Size(_visualElementRenderer.ViewGroup.MeasuredWidth, _visualElementRenderer.ViewGroup.MeasuredHeight));
}
public void UpdateLayout()
{
this.SafeExecute(() => Tracker?.UpdateLayout());
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (Element != null)
{
Element.PropertyChanged -= SafeHandlePropertyChanged;
}
if (NestedMapView != null)
{
NestedMapView.UserSwipingOnMap -= MapViewOnUserSwipingOnMap;
}
_init = false;
}
public override void RequestLayout()
{
this.SafeExecute(() => base.RequestLayout());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment