summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.Android/AppCompat
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.Android/AppCompat')
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs243
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs155
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs452
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/FormsFragmentPagerAdapter.cs59
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/FormsViewPager.cs34
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/FragmentContainer.cs111
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs215
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/IManageFragments.cs13
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs355
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs789
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/PickerRenderer.cs141
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/Platform.cs394
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/Resource.cs31
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/SwitchRenderer.cs92
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs322
-rw-r--r--Xamarin.Forms.Platform.Android/AppCompat/ViewRenderer.cs7
16 files changed, 3413 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs
new file mode 100644
index 00000000..c6e31e18
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs
@@ -0,0 +1,243 @@
+using System;
+using System.ComponentModel;
+using Android.Content;
+using Android.Content.Res;
+using Android.Graphics;
+using Android.Graphics.Drawables;
+using Android.Support.V7.Widget;
+using Android.Util;
+using GlobalResource = Android.Resource;
+using Object = Java.Lang.Object;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public class ButtonRenderer : ViewRenderer<Button, AppCompatButton>, global::Android.Views.View.IOnAttachStateChangeListener
+ {
+ static readonly int[][] States = { new[] { GlobalResource.Attribute.StateEnabled }, new[] { -GlobalResource.Attribute.StateEnabled } };
+
+ ColorStateList _buttonDefaulTextColors;
+ Color _currentTextColor;
+ float _defaultFontSize;
+ Typeface _defaultTypeface;
+ bool _isDisposed;
+
+ public ButtonRenderer()
+ {
+ AutoPackage = false;
+ }
+
+ global::Android.Widget.Button NativeButton => Control;
+
+ void IOnAttachStateChangeListener.OnViewAttachedToWindow(global::Android.Views.View attachedView)
+ {
+ UpdateText();
+ }
+
+ void IOnAttachStateChangeListener.OnViewDetachedFromWindow(global::Android.Views.View detachedView)
+ {
+ }
+
+ public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
+ {
+ UpdateText();
+ return base.GetDesiredSize(widthConstraint, heightConstraint);
+ }
+
+ protected override AppCompatButton CreateNativeControl()
+ {
+ return new AppCompatButton(Context);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ return;
+
+ _isDisposed = true;
+
+ if (disposing)
+ {
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.OldElement != null)
+ {
+ }
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ AppCompatButton button = CreateNativeControl();
+
+ button.SetOnClickListener(ButtonClickListener.Instance.Value);
+ button.Tag = this;
+ _buttonDefaulTextColors = button.TextColors;
+ SetNativeControl(button);
+
+ button.AddOnAttachStateChangeListener(this);
+ }
+
+ UpdateAll();
+ UpdateBackgroundColor();
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Button.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == Button.TextColorProperty.PropertyName)
+ UpdateTextColor();
+ else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ UpdateEnabled();
+ else if (e.PropertyName == Button.FontProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == Button.ImageProperty.PropertyName)
+ UpdateBitmap();
+ else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName)
+ UpdateText();
+
+ base.OnElementPropertyChanged(sender, e);
+ }
+
+ protected override void UpdateBackgroundColor()
+ {
+ if (Element == null || Control == null)
+ return;
+
+ Color backgroundColor = Element.BackgroundColor;
+ if (backgroundColor.IsDefault)
+ {
+ if (Control.SupportBackgroundTintList != null)
+ {
+ Context context = Context;
+ int id = GlobalResource.Attribute.ButtonTint;
+ unchecked
+ {
+ using(var value = new TypedValue())
+ {
+ try
+ {
+ Resources.Theme theme = context.Theme;
+ if (theme != null && theme.ResolveAttribute(id, value, true))
+ Control.SupportBackgroundTintList = Resources.GetColorStateList(value.Data);
+ else
+ Control.SupportBackgroundTintList = new ColorStateList(States, new[] { (int)0xffd7d6d6, 0x7fd7d6d6 });
+ }
+ catch (Exception ex)
+ {
+ Control.SupportBackgroundTintList = new ColorStateList(States, new[] { (int)0xffd7d6d6, 0x7fd7d6d6 });
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ int intColor = backgroundColor.ToAndroid().ToArgb();
+ int disableColor = backgroundColor.MultiplyAlpha(0.5).ToAndroid().ToArgb();
+ Control.SupportBackgroundTintList = new ColorStateList(States, new[] { intColor, disableColor });
+ }
+ }
+
+ void UpdateAll()
+ {
+ UpdateFont();
+ UpdateText();
+ UpdateBitmap();
+ UpdateTextColor();
+ UpdateEnabled();
+ }
+
+ void UpdateBitmap()
+ {
+ FileImageSource elementImage = Element.Image;
+ string imageFile = elementImage?.File;
+ if (elementImage != null && !string.IsNullOrEmpty(imageFile))
+ {
+ Drawable image = Context.Resources.GetDrawable(imageFile);
+ Control.SetCompoundDrawablesWithIntrinsicBounds(image, null, null, null);
+ image?.Dispose();
+ }
+ else
+ Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ }
+
+ void UpdateEnabled()
+ {
+ Control.Enabled = Element.IsEnabled;
+ }
+
+ void UpdateFont()
+ {
+ Button button = Element;
+ Font font = button.Font;
+
+ if (font == Font.Default && _defaultFontSize == 0f)
+ return;
+
+ if (_defaultFontSize == 0f)
+ {
+ _defaultTypeface = NativeButton.Typeface;
+ _defaultFontSize = NativeButton.TextSize;
+ }
+
+ if (font == Font.Default)
+ {
+ NativeButton.Typeface = _defaultTypeface;
+ NativeButton.SetTextSize(ComplexUnitType.Px, _defaultFontSize);
+ }
+ else
+ {
+ NativeButton.Typeface = font.ToTypeface();
+ NativeButton.SetTextSize(ComplexUnitType.Sp, font.ToScaledPixel());
+ }
+ }
+
+ void UpdateText()
+ {
+ NativeButton.Text = Element.Text;
+ }
+
+ void UpdateTextColor()
+ {
+ Color color = Element.TextColor;
+ if (color == _currentTextColor)
+ return;
+
+ _currentTextColor = color;
+
+ if (color.IsDefault)
+ NativeButton.SetTextColor(_buttonDefaulTextColors);
+ else
+ {
+ // Set the new enabled state color, preserving the default disabled state color
+ int defaultDisabledColor = _buttonDefaulTextColors.GetColorForState(States[1], color.ToAndroid());
+
+ NativeButton.SetTextColor(new ColorStateList(States, new[] { color.ToAndroid().ToArgb(), defaultDisabledColor }));
+ }
+ }
+
+ class ButtonClickListener : Object, IOnClickListener
+ {
+ #region Statics
+
+ public static readonly Lazy<ButtonClickListener> Instance = new Lazy<ButtonClickListener>(() => new ButtonClickListener());
+
+ #endregion
+
+ public void OnClick(global::Android.Views.View v)
+ {
+ var renderer = v.Tag as ButtonRenderer;
+ ((IButtonController)renderer?.Element)?.SendClicked();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs
new file mode 100644
index 00000000..5e8a1b8a
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs
@@ -0,0 +1,155 @@
+´╗┐using System.Collections.Specialized;
+using System.ComponentModel;
+using Android.Content;
+using Android.Support.V4.View;
+using Android.Views;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public class CarouselPageRenderer : VisualElementRenderer<CarouselPage>, ViewPager.IOnPageChangeListener
+ {
+ bool _disposed;
+ FormsViewPager _viewPager;
+
+ public CarouselPageRenderer()
+ {
+ AutoPackage = false;
+ }
+
+ void ViewPager.IOnPageChangeListener.OnPageScrolled(int position, float positionOffset, int positionOffsetPixels)
+ {
+ }
+
+ void ViewPager.IOnPageChangeListener.OnPageScrollStateChanged(int state)
+ {
+ }
+
+ void ViewPager.IOnPageChangeListener.OnPageSelected(int position)
+ {
+ Element.CurrentPage = Element.Children[position];
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ RemoveAllViews();
+ foreach (ContentPage pageToRemove in Element.Children)
+ {
+ IVisualElementRenderer pageRenderer = Android.Platform.GetRenderer(pageToRemove);
+ if (pageRenderer != null)
+ {
+ pageRenderer.ViewGroup.RemoveFromParent();
+ pageRenderer.Dispose();
+ }
+ pageToRemove.ClearValue(Android.Platform.RendererProperty);
+ }
+
+ if (_viewPager != null)
+ {
+ _viewPager.Adapter.Dispose();
+ _viewPager.Dispose();
+ _viewPager = null;
+ }
+
+ if (Element != null)
+ Element.InternalChildren.CollectionChanged -= OnChildrenCollectionChanged;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnAttachedToWindow()
+ {
+ base.OnAttachedToWindow();
+ Element.SendAppearing();
+ }
+
+ protected override void OnDetachedFromWindow()
+ {
+ base.OnDetachedFromWindow();
+ Element.SendDisappearing();
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<CarouselPage> e)
+ {
+ base.OnElementChanged(e);
+
+ var activity = (FormsAppCompatActivity)Context;
+
+ if (e.OldElement != null)
+ e.OldElement.InternalChildren.CollectionChanged -= OnChildrenCollectionChanged;
+
+ if (e.NewElement != null)
+ {
+ FormsViewPager pager =
+ _viewPager =
+ new FormsViewPager(activity)
+ {
+ OverScrollMode = OverScrollMode.Never,
+ EnableGesture = true,
+ LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent),
+ Adapter = new FormsFragmentPagerAdapter<ContentPage>(e.NewElement, activity.SupportFragmentManager) { CountOverride = e.NewElement.Children.Count }
+ };
+ pager.Id = FormsAppCompatActivity.GetUniqueId();
+ pager.AddOnPageChangeListener(this);
+
+ AddView(pager);
+ CarouselPage carouselPage = e.NewElement;
+ if (carouselPage.CurrentPage != null)
+ ScrollToCurrentPage();
+
+ UpdateIgnoreContainerAreas();
+ carouselPage.InternalChildren.CollectionChanged += OnChildrenCollectionChanged;
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == "CurrentPage")
+ ScrollToCurrentPage();
+ }
+
+ protected override void OnLayout(bool changed, int l, int t, int r, int b)
+ {
+ FormsViewPager pager = _viewPager;
+ Context context = Context;
+ int width = r - l;
+ int height = b - t;
+
+ pager.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.AtMost), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.AtMost));
+
+ if (width > 0 && height > 0)
+ {
+ Element.ContainerArea = new Rectangle(0, 0, context.FromPixels(width), context.FromPixels(height));
+ pager.Layout(0, 0, width, b);
+ }
+
+ base.OnLayout(changed, l, t, r, b);
+ }
+
+ void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ FormsViewPager pager = _viewPager;
+
+ ((FormsFragmentPagerAdapter<ContentPage>)pager.Adapter).CountOverride = Element.Children.Count;
+ pager.Adapter.NotifyDataSetChanged();
+
+ UpdateIgnoreContainerAreas();
+ }
+
+ void ScrollToCurrentPage()
+ {
+ _viewPager.SetCurrentItem(Element.Children.IndexOf(Element.CurrentPage), true);
+ }
+
+ void UpdateIgnoreContainerAreas()
+ {
+ foreach (ContentPage child in Element.Children)
+ child.IgnoresContainerArea = child is NavigationPage;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs
new file mode 100644
index 00000000..916f1859
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs
@@ -0,0 +1,452 @@
+#region
+
+using System;
+using System.ComponentModel;
+using System.Linq;
+using Android.App;
+using Android.Content;
+using Android.Content.Res;
+using Android.OS;
+using Android.Runtime;
+using Android.Support.V7.App;
+using Android.Util;
+using Android.Views;
+using Android.Widget;
+using Xamarin.Forms.Platform.Android.AppCompat;
+using AToolbar = Android.Support.V7.Widget.Toolbar;
+using AColor = Android.Graphics.Color;
+using AlertDialog = Android.Support.V7.App.AlertDialog;
+using ARelativeLayout = Android.Widget.RelativeLayout;
+
+#endregion
+
+namespace Xamarin.Forms.Platform.Android
+{
+ public class FormsAppCompatActivity : AppCompatActivity, IDeviceInfoProvider, IStartActivityForResult
+ {
+ public delegate bool BackButtonPressedEventHandler(object sender, EventArgs e);
+
+ readonly ConcurrentDictionary<int, Action<Result, Intent>> _activityResultCallbacks = new ConcurrentDictionary<int, Action<Result, Intent>>();
+
+ Application _application;
+ int _busyCount;
+ AndroidApplicationLifecycleState _currentState;
+ ARelativeLayout _layout;
+
+ int _nextActivityResultCallbackKey;
+
+ AppCompat.Platform _platform;
+
+ AndroidApplicationLifecycleState _previousState;
+
+ bool _renderersAdded;
+ int _statusBarHeight = -1;
+ global::Android.Views.View _statusBarUnderlay;
+
+ protected FormsAppCompatActivity()
+ {
+ _previousState = AndroidApplicationLifecycleState.Uninitialized;
+ _currentState = AndroidApplicationLifecycleState.Uninitialized;
+ }
+
+ public event EventHandler ConfigurationChanged;
+
+ int IStartActivityForResult.RegisterActivityResultCallback(Action<Result, Intent> callback)
+ {
+ int requestCode = _nextActivityResultCallbackKey;
+
+ while (!_activityResultCallbacks.TryAdd(requestCode, callback))
+ {
+ _nextActivityResultCallbackKey += 1;
+ requestCode = _nextActivityResultCallbackKey;
+ }
+
+ _nextActivityResultCallbackKey += 1;
+
+ return requestCode;
+ }
+
+ void IStartActivityForResult.UnregisterActivityResultCallback(int requestCode)
+ {
+ Action<Result, Intent> callback;
+ _activityResultCallbacks.TryRemove(requestCode, out callback);
+ }
+
+ public override void OnBackPressed()
+ {
+ if (BackPressed != null && BackPressed(this, EventArgs.Empty))
+ return;
+ base.OnBackPressed();
+ }
+
+ public override void OnConfigurationChanged(Configuration newConfig)
+ {
+ base.OnConfigurationChanged(newConfig);
+ ConfigurationChanged?.Invoke(this, new EventArgs());
+ }
+
+ public override bool OnOptionsItemSelected(IMenuItem item)
+ {
+ if (item.ItemId == global::Android.Resource.Id.Home)
+ BackPressed?.Invoke(this, EventArgs.Empty);
+
+ return base.OnOptionsItemSelected(item);
+ }
+
+ public void SetStatusBarColor(AColor color)
+ {
+ _statusBarUnderlay.SetBackgroundColor(color);
+ }
+
+ protected void LoadApplication(Application application)
+ {
+ if (!_renderersAdded)
+ {
+ RegisterHandlerForDefaultRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer), typeof(NavigationRenderer));
+ RegisterHandlerForDefaultRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer), typeof(TabbedRenderer));
+ RegisterHandlerForDefaultRenderer(typeof(MasterDetailPage), typeof(MasterDetailPageRenderer), typeof(MasterDetailRenderer));
+ RegisterHandlerForDefaultRenderer(typeof(Button), typeof(AppCompat.ButtonRenderer), typeof(ButtonRenderer));
+ RegisterHandlerForDefaultRenderer(typeof(Switch), typeof(AppCompat.SwitchRenderer), typeof(SwitchRenderer));
+ RegisterHandlerForDefaultRenderer(typeof(Picker), typeof(AppCompat.PickerRenderer), typeof(PickerRenderer));
+ RegisterHandlerForDefaultRenderer(typeof(Frame), typeof(AppCompat.FrameRenderer), typeof(FrameRenderer));
+ RegisterHandlerForDefaultRenderer(typeof(CarouselPage), typeof(AppCompat.CarouselPageRenderer), typeof(CarouselPageRenderer));
+ }
+
+ if (application == null)
+ throw new ArgumentNullException("application");
+
+ _application = application;
+ Xamarin.Forms.Application.Current = application;
+
+ application.PropertyChanged += AppOnPropertyChanged;
+
+ SetMainPage();
+ }
+
+ protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
+ {
+ base.OnActivityResult(requestCode, resultCode, data);
+
+ Action<Result, Intent> callback;
+
+ if (_activityResultCallbacks.TryGetValue(requestCode, out callback))
+ callback(resultCode, data);
+ }
+
+ protected override void OnCreate(Bundle savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ AToolbar bar;
+ if (ToolbarResource != 0)
+ {
+ bar = LayoutInflater.Inflate(ToolbarResource, null).JavaCast<AToolbar>();
+ if (bar == null)
+ throw new InvalidOperationException("ToolbarResource must be set to a Android.Support.V7.Widget.Toolbar");
+ }
+ else
+ bar = new AToolbar(this);
+
+ SetSupportActionBar(bar);
+
+ Window.SetSoftInputMode(SoftInput.AdjustPan);
+
+ _layout = new ARelativeLayout(BaseContext);
+ SetContentView(_layout);
+
+ Xamarin.Forms.Application.ClearCurrent();
+
+ _previousState = _currentState;
+ _currentState = AndroidApplicationLifecycleState.OnCreate;
+
+ OnStateChanged();
+
+ _statusBarUnderlay = new global::Android.Views.View(this);
+ var layoutParameters = new ARelativeLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, GetStatusBarHeight()) { AlignWithParent = true };
+ layoutParameters.AddRule(LayoutRules.AlignTop);
+ _statusBarUnderlay.LayoutParameters = layoutParameters;
+ _layout.AddView(_statusBarUnderlay);
+
+ if (Forms.IsLollipopOrNewer)
+ {
+ Window.DecorView.SystemUiVisibility = (StatusBarVisibility)(SystemUiFlags.LayoutFullscreen | SystemUiFlags.LayoutStable);
+ Window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
+ Window.SetStatusBarColor(AColor.Transparent);
+
+ int primaryColorDark = GetColorPrimaryDark();
+
+ if (primaryColorDark != 0)
+ {
+ int r = AColor.GetRedComponent(primaryColorDark);
+ int g = AColor.GetGreenComponent(primaryColorDark);
+ int b = AColor.GetBlueComponent(primaryColorDark);
+ int a = AColor.GetAlphaComponent(primaryColorDark);
+ SetStatusBarColor(AColor.Argb(a, r, g, b));
+ }
+ }
+ }
+
+ protected override void OnDestroy()
+ {
+ // may never be called
+ base.OnDestroy();
+
+ MessagingCenter.Unsubscribe<Page, AlertArguments>(this, Page.AlertSignalName);
+ MessagingCenter.Unsubscribe<Page, bool>(this, Page.BusySetSignalName);
+ MessagingCenter.Unsubscribe<Page, ActionSheetArguments>(this, Page.ActionSheetSignalName);
+
+ if (_platform != null)
+ _platform.Dispose();
+ }
+
+ protected override void OnNewIntent(Intent intent)
+ {
+ base.OnNewIntent(intent);
+ }
+
+ protected override void OnPause()
+ {
+ _layout.HideKeyboard(true);
+
+ // Stop animations or other ongoing actions that could consume CPU
+ // Commit unsaved changes, build only if users expect such changes to be permanently saved when thy leave such as a draft email
+ // Release system resources, such as broadcast receivers, handles to sensors (like GPS), or any resources that may affect battery life when your activity is paused.
+ // Avoid writing to permanent storage and CPU intensive tasks
+ base.OnPause();
+
+ _previousState = _currentState;
+ _currentState = AndroidApplicationLifecycleState.OnPause;
+
+ OnStateChanged();
+ }
+
+ protected override void OnRestart()
+ {
+ base.OnRestart();
+
+ _previousState = _currentState;
+ _currentState = AndroidApplicationLifecycleState.OnRestart;
+
+ OnStateChanged();
+ }
+
+ protected override void OnResume()
+ {
+ // counterpart to OnPause
+ base.OnResume();
+
+ _previousState = _currentState;
+ _currentState = AndroidApplicationLifecycleState.OnResume;
+
+ OnStateChanged();
+ }
+
+ protected override void OnStart()
+ {
+ base.OnStart();
+
+ _previousState = _currentState;
+ _currentState = AndroidApplicationLifecycleState.OnStart;
+
+ OnStateChanged();
+ }
+
+ // Scenarios that stop and restart your app
+ // -- Switches from your app to another app, activity restarts when clicking on the app again.
+ // -- Action in your app that starts a new Activity, the current activity is stopped and the second is created, pressing back restarts the activity
+ // -- The user receives a phone call while using your app on his or her phone
+ protected override void OnStop()
+ {
+ // writing to storage happens here!
+ // full UI obstruction
+ // users focus in another activity
+ // perform heavy load shutdown operations
+ // clean up resources
+ // clean up everything that may leak memory
+ base.OnStop();
+
+ _previousState = _currentState;
+ _currentState = AndroidApplicationLifecycleState.OnStop;
+
+ OnStateChanged();
+ }
+
+ internal int GetStatusBarHeight()
+ {
+ if (_statusBarHeight >= 0)
+ return _statusBarHeight;
+
+ var result = 0;
+ int resourceId = Resources.GetIdentifier("status_bar_height", "dimen", "android");
+ if (resourceId > 0)
+ result = Resources.GetDimensionPixelSize(resourceId);
+ return _statusBarHeight = result;
+ }
+
+ void AppOnPropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ if (args.PropertyName == "MainPage")
+ InternalSetPage(_application.MainPage);
+ }
+
+ int GetColorPrimaryDark()
+ {
+ FormsAppCompatActivity context = this;
+ int id = global::Android.Resource.Attribute.ColorPrimaryDark;
+ using(var value = new TypedValue())
+ {
+ try
+ {
+ Resources.Theme theme = context.Theme;
+ if (theme != null && theme.ResolveAttribute(id, value, true))
+ {
+ if (value.Type >= DataType.FirstInt && value.Type <= DataType.LastInt)
+ return value.Data;
+ if (value.Type == DataType.String)
+ return context.Resources.GetColor(value.ResourceId);
+ }
+ }
+ catch (Exception ex)
+ {
+ }
+
+ return -1;
+ }
+ }
+
+ void InternalSetPage(Page page)
+ {
+ if (!Forms.IsInitialized)
+ throw new InvalidOperationException("Call Forms.Init (Activity, Bundle) before this");
+
+ if (_platform != null)
+ {
+ _platform.SetPage(page);
+ return;
+ }
+
+ _busyCount = 0;
+ MessagingCenter.Subscribe<Page, bool>(this, Page.BusySetSignalName, OnPageBusy);
+ MessagingCenter.Subscribe<Page, AlertArguments>(this, Page.AlertSignalName, OnAlertRequested);
+ MessagingCenter.Subscribe<Page, ActionSheetArguments>(this, Page.ActionSheetSignalName, OnActionSheetRequested);
+
+ _platform = new AppCompat.Platform(this);
+ if (_application != null)
+ _application.Platform = _platform;
+ _platform.SetPage(page);
+ _layout.AddView(_platform);
+ _layout.BringToFront();
+ }
+
+ void OnActionSheetRequested(Page sender, ActionSheetArguments arguments)
+ {
+ var builder = new AlertDialog.Builder(this);
+ builder.SetTitle(arguments.Title);
+ string[] items = arguments.Buttons.ToArray();
+ builder.SetItems(items, (o, args) => arguments.Result.TrySetResult(items[args.Which]));
+
+ if (arguments.Cancel != null)
+ builder.SetPositiveButton(arguments.Cancel, (o, args) => arguments.Result.TrySetResult(arguments.Cancel));
+
+ if (arguments.Destruction != null)
+ builder.SetNegativeButton(arguments.Destruction, (o, args) => arguments.Result.TrySetResult(arguments.Destruction));
+
+ AlertDialog dialog = builder.Create();
+ builder.Dispose();
+ //to match current functionality of renderer we set cancelable on outside
+ //and return null
+ dialog.SetCanceledOnTouchOutside(true);
+ dialog.CancelEvent += (o, e) => arguments.SetResult(null);
+ dialog.Show();
+ }
+
+ void OnAlertRequested(Page sender, AlertArguments arguments)
+ {
+ AlertDialog alert = new AlertDialog.Builder(this).Create();
+ alert.SetTitle(arguments.Title);
+ alert.SetMessage(arguments.Message);
+ if (arguments.Accept != null)
+ alert.SetButton((int)DialogButtonType.Positive, arguments.Accept, (o, args) => arguments.SetResult(true));
+ alert.SetButton((int)DialogButtonType.Negative, arguments.Cancel, (o, args) => arguments.SetResult(false));
+ alert.CancelEvent += (o, args) => { arguments.SetResult(false); };
+ alert.Show();
+ }
+
+ void OnPageBusy(Page sender, bool enabled)
+ {
+ _busyCount = Math.Max(0, enabled ? _busyCount + 1 : _busyCount - 1);
+
+ if (!Forms.SupportsProgress)
+ return;
+
+ SetProgressBarIndeterminate(true);
+ UpdateProgressBarVisibility(_busyCount > 0);
+ }
+
+ async void OnStateChanged()
+ {
+ if (_application == null)
+ return;
+
+ if (_previousState == AndroidApplicationLifecycleState.OnCreate && _currentState == AndroidApplicationLifecycleState.OnStart)
+ _application.SendStart();
+ else if (_previousState == AndroidApplicationLifecycleState.OnStop && _currentState == AndroidApplicationLifecycleState.OnRestart)
+ _application.SendResume();
+ else if (_previousState == AndroidApplicationLifecycleState.OnPause && _currentState == AndroidApplicationLifecycleState.OnStop)
+ await _application.SendSleepAsync();
+ }
+
+ void RegisterHandlerForDefaultRenderer(Type target, Type handler, Type filter)
+ {
+ Type current = Registrar.Registered.GetHandlerType(filter);
+ if (current == target)
+ return;
+
+ Registrar.Registered.Register(target, handler);
+ }
+
+ void SetMainPage()
+ {
+ InternalSetPage(_application.MainPage);
+ }
+
+ void UpdateProgressBarVisibility(bool isBusy)
+ {
+ if (!Forms.SupportsProgress)
+ return;
+
+ SetProgressBarIndeterminateVisibility(isBusy);
+ }
+
+ internal class DefaultApplication : Application
+ {
+ }
+
+ #region Statics
+
+ public static event BackButtonPressedEventHandler BackPressed;
+
+ public static int TabLayoutResource { get; set; }
+
+ public static int ToolbarResource { get; set; }
+
+ internal static int GetUniqueId()
+ {
+ // getting unique Id's is an art, and I consider myself the Jackson Pollock of the field
+ if ((int)Build.VERSION.SdkInt >= 17)
+ return global::Android.Views.View.GenerateViewId();
+
+ // Numbers higher than this range reserved for xml
+ // If we roll over, it can be exceptionally problematic for the user if they are still retaining things, android's internal implementation is
+ // basically identical to this except they do a lot of locking we don't have to because we know we only do this
+ // from the UI thread
+ if (s_id >= 0x00ffffff)
+ s_id = 0x00000400;
+ return s_id++;
+ }
+
+ static int s_id = 0x00000400;
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FormsFragmentPagerAdapter.cs b/Xamarin.Forms.Platform.Android/AppCompat/FormsFragmentPagerAdapter.cs
new file mode 100644
index 00000000..99fbb396
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/FormsFragmentPagerAdapter.cs
@@ -0,0 +1,59 @@
+´╗┐using Android.OS;
+using Android.Support.V4.App;
+using Java.Lang;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ internal class FormsFragmentPagerAdapter<T> : FragmentPagerAdapter where T : Page
+ {
+ MultiPage<T> _page;
+
+ public FormsFragmentPagerAdapter(MultiPage<T> page, FragmentManager fragmentManager) : base(fragmentManager)
+ {
+ _page = page;
+ }
+
+ public override int Count => CountOverride;
+
+ public int CountOverride { get; set; }
+
+ public override Fragment GetItem(int position)
+ {
+ return FragmentContainer.CreateInstance(_page.Children[position]);
+ }
+
+ public override long GetItemId(int position)
+ {
+ return _page.Children[position].GetHashCode();
+ }
+
+ public override int GetItemPosition(Object objectValue)
+ {
+ var fragContainer = objectValue as FragmentContainer;
+ if (fragContainer != null && fragContainer.Page != null)
+ {
+ int index = _page.Children.IndexOf(fragContainer.Page);
+ if (index >= 0)
+ return index;
+ }
+ return PositionNone;
+ }
+
+ public override ICharSequence GetPageTitleFormatted(int position)
+ {
+ return new String(_page.Children[position].Title);
+ }
+
+ // http://stackoverflow.com/questions/18642890/fragmentstatepageradapter-with-childfragmentmanager-fragmentmanagerimpl-getfra/19099987#19099987
+ public override void RestoreState(IParcelable state, ClassLoader loader)
+ {
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ _page = null;
+ base.Dispose(disposing);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FormsViewPager.cs b/Xamarin.Forms.Platform.Android/AppCompat/FormsViewPager.cs
new file mode 100644
index 00000000..2327bb4e
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/FormsViewPager.cs
@@ -0,0 +1,34 @@
+using System;
+using Android.Content;
+using Android.Runtime;
+using Android.Support.V4.View;
+using Android.Views;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ internal class FormsViewPager : ViewPager
+ {
+ public FormsViewPager(Context context) : base(context)
+ {
+ }
+
+ protected FormsViewPager(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
+ {
+ }
+
+ public bool EnableGesture { get; set; } = true;
+
+ public override bool OnInterceptTouchEvent(MotionEvent ev)
+ {
+ // Same as:
+ // if (!EnableGesture) return false;
+ // However this is, at least in theory a tidge faster which in this particular area is good
+ return EnableGesture && base.OnInterceptTouchEvent(ev);
+ }
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ return EnableGesture && base.OnTouchEvent(e);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FragmentContainer.cs b/Xamarin.Forms.Platform.Android/AppCompat/FragmentContainer.cs
new file mode 100644
index 00000000..39219188
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/FragmentContainer.cs
@@ -0,0 +1,111 @@
+using System;
+using Android.OS;
+using Android.Runtime;
+using Android.Support.V4.App;
+using Android.Views;
+using AView = Android.Views.View;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ internal class FragmentContainer : Fragment
+ {
+ readonly WeakReference _pageReference;
+
+ bool? _isVisible;
+ PageContainer _pageContainer;
+ IVisualElementRenderer _visualElementRenderer;
+
+ public FragmentContainer()
+ {
+ }
+
+ public FragmentContainer(Page page) : this()
+ {
+ _pageReference = new WeakReference(page);
+ }
+
+ protected FragmentContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
+ {
+ }
+
+ public Page Page => (Page)_pageReference?.Target;
+
+ public override bool UserVisibleHint
+ {
+ get { return base.UserVisibleHint; }
+ set
+ {
+ base.UserVisibleHint = value;
+ if (_isVisible == value)
+ return;
+ _isVisible = value;
+ if (_isVisible.Value)
+ Page?.SendAppearing();
+ else
+ Page?.SendDisappearing();
+ }
+ }
+
+ public static Fragment CreateInstance(Page page)
+ {
+ return new FragmentContainer(page) { Arguments = new Bundle() };
+ }
+
+ public override AView OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ {
+ if (Page != null)
+ {
+ _visualElementRenderer = Android.Platform.CreateRenderer(Page, ChildFragmentManager);
+ Android.Platform.SetRenderer(Page, _visualElementRenderer);
+
+ _pageContainer = new PageContainer(Forms.Context, _visualElementRenderer, true);
+ return _pageContainer;
+ }
+
+ return null;
+ }
+
+ public override void OnDestroyView()
+ {
+ if (Page != null)
+ {
+ IVisualElementRenderer renderer = _visualElementRenderer;
+ PageContainer container = _pageContainer;
+
+ if (container.Handle != IntPtr.Zero && renderer.ViewGroup.Handle != IntPtr.Zero)
+ {
+ container.RemoveFromParent();
+ renderer.ViewGroup.RemoveFromParent();
+ Page.ClearValue(Android.Platform.RendererProperty);
+
+ container.Dispose();
+ renderer.Dispose();
+ }
+ }
+
+ _visualElementRenderer = null;
+ _pageContainer = null;
+
+ base.OnDestroyView();
+ }
+
+ public override void OnHiddenChanged(bool hidden)
+ {
+ base.OnHiddenChanged(hidden);
+
+ if (Page == null)
+ return;
+
+ if (hidden)
+ Page.SendDisappearing();
+ else
+ Page.SendAppearing();
+ }
+
+ public override void OnPause()
+ {
+ Page?.SendDisappearing();
+ base.OnPause();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs
new file mode 100644
index 00000000..7b544b25
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs
@@ -0,0 +1,215 @@
+using System;
+using System.ComponentModel;
+using Android.Content;
+using Android.Support.V4.View;
+using Android.Support.V7.Widget;
+using Android.Views;
+using AColor = Android.Graphics.Color;
+using AView = Android.Views.View;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public class FrameRenderer : CardView, IVisualElementRenderer, AView.IOnClickListener, AView.IOnTouchListener
+ {
+ readonly Lazy<GestureDetector> _gestureDetector;
+ readonly PanGestureHandler _panGestureHandler;
+ readonly PinchGestureHandler _pinchGestureHandler;
+ readonly Lazy<ScaleGestureDetector> _scaleDetector;
+ readonly TapGestureHandler _tapGestureHandler;
+
+ float _defaultElevation = -1f;
+
+ bool _disposed;
+ Frame _element;
+ InnerGestureListener _gestureListener;
+ VisualElementPackager _visualElementPackager;
+ VisualElementTracker _visualElementTracker;
+
+ public FrameRenderer() : base(Forms.Context)
+ {
+ _tapGestureHandler = new TapGestureHandler(() => Element);
+ _panGestureHandler = new PanGestureHandler(() => Element, Context.FromPixels);
+ _pinchGestureHandler = new PinchGestureHandler(() => Element);
+
+ _gestureDetector =
+ new Lazy<GestureDetector>(
+ () =>
+ new GestureDetector(
+ _gestureListener =
+ new InnerGestureListener(_tapGestureHandler.OnTap, _tapGestureHandler.TapGestureRecognizers, _panGestureHandler.OnPan, _panGestureHandler.OnPanStarted, _panGestureHandler.OnPanComplete)));
+
+ _scaleDetector =
+ new Lazy<ScaleGestureDetector>(
+ () => new ScaleGestureDetector(Context, new InnerScaleListener(_pinchGestureHandler.OnPinch, _pinchGestureHandler.OnPinchStarted, _pinchGestureHandler.OnPinchEnded), Handler));
+ }
+
+ protected CardView Control => this;
+
+ protected Frame Element
+ {
+ get { return _element; }
+ set
+ {
+ if (_element == value)
+ return;
+
+ Frame oldElement = _element;
+ _element = value;
+
+ OnElementChanged(new ElementChangedEventArgs<Frame>(oldElement, _element));
+
+ if (_element != null)
+ _element.SendViewInitialized(Control);
+ }
+ }
+
+ void IOnClickListener.OnClick(AView v)
+ {
+ _tapGestureHandler.OnSingleClick();
+ }
+
+ bool IOnTouchListener.OnTouch(AView v, MotionEvent e)
+ {
+ var handled = false;
+ if (_pinchGestureHandler.IsPinchSupported)
+ {
+ if (!_scaleDetector.IsValueCreated)
+ ScaleGestureDetectorCompat.SetQuickScaleEnabled(_scaleDetector.Value, true);
+ handled = _scaleDetector.Value.OnTouchEvent(e);
+ }
+ return _gestureDetector.Value.OnTouchEvent(e) || handled;
+ }
+
+ VisualElement IVisualElementRenderer.Element => Element;
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
+ {
+ Context context = Context;
+ return new SizeRequest(new Size(context.ToPixels(20), context.ToPixels(20)));
+ }
+
+ void IVisualElementRenderer.SetElement(VisualElement element)
+ {
+ var frame = element as Frame;
+ if (frame == null)
+ throw new ArgumentException("Element must be of type Frame");
+ Element = frame;
+
+ if (!string.IsNullOrEmpty(Element.AutomationId))
+ ContentDescription = Element.AutomationId;
+ }
+
+ VisualElementTracker IVisualElementRenderer.Tracker => _visualElementTracker;
+
+ void IVisualElementRenderer.UpdateLayout()
+ {
+ VisualElementTracker tracker = _visualElementTracker;
+ tracker?.UpdateLayout();
+ }
+
+ ViewGroup IVisualElementRenderer.ViewGroup => this;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+
+ if (_gestureListener != null)
+ {
+ _gestureListener.Dispose();
+ _gestureListener = null;
+ }
+
+ if (_visualElementTracker != null)
+ {
+ _visualElementTracker.Dispose();
+ _visualElementTracker = null;
+ }
+
+ if (_visualElementPackager != null)
+ {
+ _visualElementPackager.Dispose();
+ _visualElementPackager = null;
+ }
+
+ if (Element != null)
+ Element.PropertyChanged -= OnElementPropertyChanged;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual void OnElementChanged(ElementChangedEventArgs<Frame> e)
+ {
+ ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
+
+ if (e.OldElement != null)
+ e.OldElement.PropertyChanged -= OnElementPropertyChanged;
+ else
+ {
+ SetOnClickListener(this);
+ SetOnTouchListener(this);
+ }
+
+ if (e.NewElement != null)
+ {
+ if (_visualElementTracker == null)
+ {
+ _visualElementTracker = new VisualElementTracker(this);
+ _visualElementPackager = new VisualElementPackager(this);
+ _visualElementPackager.Load();
+ }
+
+ e.NewElement.PropertyChanged += OnElementPropertyChanged;
+ UpdateShadow();
+ UpdateBackgroundColor();
+ }
+ }
+
+ protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
+ {
+ if (Element == null)
+ return;
+
+ var children = Element.LogicalChildren;
+ for (var i = 0; i < children.Count; i++)
+ {
+ var visualElement = children[i] as VisualElement;
+ if (visualElement == null)
+ continue;
+ IVisualElementRenderer renderer = Android.Platform.GetRenderer(visualElement);
+ renderer?.UpdateLayout();
+ }
+ }
+
+ void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Frame.HasShadowProperty.PropertyName)
+ UpdateShadow();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundColor();
+ }
+
+ void UpdateBackgroundColor()
+ {
+ Color bgColor = Element.BackgroundColor;
+ SetCardBackgroundColor(bgColor.IsDefault ? AColor.White : bgColor.ToAndroid());
+ }
+
+ void UpdateShadow()
+ {
+ float elevation = _defaultElevation;
+
+ if (elevation == -1f)
+ _defaultElevation = elevation = CardElevation;
+
+ if (Element.HasShadow)
+ CardElevation = elevation;
+ else
+ CardElevation = 0f;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/IManageFragments.cs b/Xamarin.Forms.Platform.Android/AppCompat/IManageFragments.cs
new file mode 100644
index 00000000..5b6c4cb7
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/IManageFragments.cs
@@ -0,0 +1,13 @@
+using Android.Support.V4.App;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ /// <summary>
+ /// Allows the platform to inject child fragment managers for renderers
+ /// which do their own fragment management
+ /// </summary>
+ internal interface IManageFragments
+ {
+ void SetFragmentManager(FragmentManager fragmentManager);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs
new file mode 100644
index 00000000..ec6b4395
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs
@@ -0,0 +1,355 @@
+using System;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using Android.Support.V4.Widget;
+using Android.Views;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public class MasterDetailPageRenderer : DrawerLayout, IVisualElementRenderer, DrawerLayout.IDrawerListener
+ {
+ #region Statics
+
+ //from Android source code
+ const uint DefaultScrimColor = 0x99000000;
+
+ #endregion
+
+ int _currentLockMode = -1;
+ MasterDetailContainer _detailLayout;
+
+ bool _disposed;
+ bool _isPresentingFromCore;
+ MasterDetailContainer _masterLayout;
+ bool _presented;
+ VisualElementTracker _tracker;
+
+ public MasterDetailPageRenderer() : base(Forms.Context)
+ {
+ }
+
+ MasterDetailPage Element { get; set; }
+
+ bool Presented
+ {
+ get { return _presented; }
+ set
+ {
+ if (value == _presented)
+ return;
+ UpdateSplitViewLayout();
+ _presented = value;
+ if (Element.MasterBehavior == MasterBehavior.Default && Element.ShouldShowSplitMode)
+ return;
+ if (_presented)
+ OpenDrawer(_masterLayout);
+ else
+ CloseDrawer(_masterLayout);
+ }
+ }
+
+ void IDrawerListener.OnDrawerClosed(global::Android.Views.View drawerView)
+ {
+ }
+
+ void IDrawerListener.OnDrawerOpened(global::Android.Views.View drawerView)
+ {
+ }
+
+ void IDrawerListener.OnDrawerSlide(global::Android.Views.View drawerView, float slideOffset)
+ {
+ }
+
+ void IDrawerListener.OnDrawerStateChanged(int newState)
+ {
+ _presented = IsDrawerVisible(_masterLayout);
+ UpdateIsPresented();
+ }
+
+ VisualElement IVisualElementRenderer.Element => Element;
+
+ event EventHandler<VisualElementChangedEventArgs> IVisualElementRenderer.ElementChanged
+ {
+ add { ElementChanged += value; }
+ remove { ElementChanged -= value; }
+ }
+
+ SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
+ {
+ Measure(widthConstraint, heightConstraint);
+ return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight));
+ }
+
+ void IVisualElementRenderer.SetElement(VisualElement element)
+ {
+ MasterDetailPage oldElement = Element;
+ MasterDetailPage newElement = Element = element as MasterDetailPage;
+
+ if (oldElement != null)
+ {
+ oldElement.BackButtonPressed -= OnBackButtonPressed;
+ oldElement.PropertyChanged -= HandlePropertyChanged;
+ oldElement.Appearing -= MasterDetailPageAppearing;
+ oldElement.Disappearing -= MasterDetailPageDisappearing;
+ }
+
+ var statusBarHeight = 0;
+ if (Forms.IsLollipopOrNewer)
+ statusBarHeight = ((FormsAppCompatActivity)Context).GetStatusBarHeight();
+
+ if (newElement != null)
+ {
+ if (_detailLayout == null)
+ {
+ _detailLayout = new MasterDetailContainer(newElement, false, Context)
+ {
+ TopPadding = statusBarHeight,
+ LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent)
+ };
+
+ _masterLayout = new MasterDetailContainer(newElement, true, Context)
+ {
+ LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent) { Gravity = (int)GravityFlags.Start }
+ };
+
+ AddView(_detailLayout);
+ AddView(_masterLayout);
+
+ Device.Info.PropertyChanged += DeviceInfoPropertyChanged;
+
+ SetDrawerListener(this);
+ }
+
+ UpdateBackgroundColor(newElement);
+ UpdateBackgroundImage(newElement);
+
+ UpdateMaster();
+ UpdateDetail();
+
+ newElement.BackButtonPressed += OnBackButtonPressed;
+ newElement.PropertyChanged += HandlePropertyChanged;
+ newElement.Appearing += MasterDetailPageAppearing;
+ newElement.Disappearing += MasterDetailPageDisappearing;
+
+ SetGestureState();
+
+ Presented = newElement.IsPresented;
+
+ newElement.SendViewInitialized(this);
+ }
+
+ OnElementChanged(oldElement, newElement);
+
+ // Make sure to initialize this AFTER event is fired
+ if (_tracker == null)
+ _tracker = new VisualElementTracker(this);
+ }
+
+ VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
+
+ void IVisualElementRenderer.UpdateLayout()
+ {
+ _tracker?.UpdateLayout();
+ }
+
+ ViewGroup IVisualElementRenderer.ViewGroup => this;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+
+ if (_tracker != null)
+ {
+ _tracker.Dispose();
+ _tracker = null;
+ }
+
+ if (_detailLayout != null)
+ {
+ _detailLayout.Dispose();
+ _detailLayout = null;
+ }
+
+ if (_masterLayout != null)
+ {
+ _masterLayout.Dispose();
+ _masterLayout = null;
+ }
+
+ Device.Info.PropertyChanged -= DeviceInfoPropertyChanged;
+
+ if (Element != null)
+ {
+ Element.BackButtonPressed -= OnBackButtonPressed;
+ Element.PropertyChanged -= HandlePropertyChanged;
+ Element.Appearing -= MasterDetailPageAppearing;
+ Element.Disappearing -= MasterDetailPageDisappearing;
+ Element.ClearValue(Android.Platform.RendererProperty);
+ Element = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnAttachedToWindow()
+ {
+ base.OnAttachedToWindow();
+ Element.SendAppearing();
+ }
+
+ protected override void OnDetachedFromWindow()
+ {
+ base.OnDetachedFromWindow();
+ Element.SendDisappearing();
+ }
+
+ protected virtual void OnElementChanged(VisualElement oldElement, VisualElement newElement)
+ {
+ ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(oldElement, newElement));
+ }
+
+ protected override void OnLayout(bool changed, int l, int t, int r, int b)
+ {
+ base.OnLayout(changed, l, t, r, b);
+ //hack to make the split layout handle touches the full width
+ if (Element.ShouldShowSplitMode && _masterLayout != null)
+ _masterLayout.Right = r;
+ }
+
+ async void DeviceInfoPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (nameof(Device.Info.CurrentOrientation) == e.PropertyName)
+ {
+ if (!Element.ShouldShowSplitMode && Presented)
+ {
+ Element.CanChangeIsPresented = true;
+ //hack : when the orientation changes and we try to close the Master on Android
+ //sometimes Android picks the width of the screen previous to the rotation
+ //this leaves a little of the master visible, the hack is to delay for 50ms closing the drawer
+ await Task.Delay(100);
+ CloseDrawer(_masterLayout);
+ }
+ UpdateSplitViewLayout();
+ }
+ }
+
+ event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ void HandleMasterPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "Master")
+ UpdateMaster();
+ else if (e.PropertyName == "Detail")
+ UpdateDetail();
+ else if (e.PropertyName == "IsGestureEnabled")
+ SetGestureState();
+ else if (e.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName)
+ {
+ _isPresentingFromCore = true;
+ Presented = Element.IsPresented;
+ _isPresentingFromCore = false;
+ }
+ else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName)
+ UpdateBackgroundImage(Element);
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundColor(Element);
+ }
+
+ void MasterDetailPageAppearing(object sender, EventArgs e)
+ {
+ Element.Master?.SendAppearing();
+ Element.Detail?.SendAppearing();
+ }
+
+ void MasterDetailPageDisappearing(object sender, EventArgs e)
+ {
+ Element.Master?.SendDisappearing();
+ Element.Detail?.SendDisappearing();
+ }
+
+ void OnBackButtonPressed(object sender, BackButtonPressedEventArgs backButtonPressedEventArgs)
+ {
+ if (!IsDrawerOpen((int)GravityFlags.Start) || _currentLockMode == LockModeLockedOpen)
+ return;
+
+ CloseDrawer((int)GravityFlags.Start);
+ backButtonPressedEventArgs.Handled = true;
+ }
+
+ void SetGestureState()
+ {
+ SetDrawerLockMode(Element.IsGestureEnabled ? LockModeUnlocked : LockModeLockedClosed);
+ }
+
+ void SetLockMode(int lockMode)
+ {
+ if (_currentLockMode != lockMode)
+ {
+ SetDrawerLockMode(lockMode);
+ _currentLockMode = lockMode;
+ }
+ }
+
+ void UpdateBackgroundColor(Page view)
+ {
+ Color backgroundColor = view.BackgroundColor;
+ if (backgroundColor.IsDefault)
+ SetBackgroundColor(backgroundColor.ToAndroid());
+ }
+
+ void UpdateBackgroundImage(Page view)
+ {
+ string backgroundImage = view.BackgroundImage;
+ if (!string.IsNullOrEmpty(backgroundImage))
+ this.SetBackground(Context.Resources.GetDrawable(backgroundImage));
+ }
+
+ void UpdateDetail()
+ {
+ Context.HideKeyboard(this);
+ _detailLayout.ChildView = Element.Detail;
+ }
+
+ void UpdateIsPresented()
+ {
+ if (_isPresentingFromCore)
+ return;
+ if (Presented != Element.IsPresented)
+ ((IElementController)Element).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, Presented);
+ }
+
+ void UpdateMaster()
+ {
+ MasterDetailContainer masterContainer = _masterLayout;
+ if (masterContainer == null)
+ return;
+
+ if (masterContainer.ChildView != null)
+ masterContainer.ChildView.PropertyChanged -= HandleMasterPropertyChanged;
+
+ masterContainer.ChildView = Element.Master;
+ if (Element.Master != null)
+ Element.Master.PropertyChanged += HandleMasterPropertyChanged;
+ }
+
+ void UpdateSplitViewLayout()
+ {
+ if (Device.Idiom == TargetIdiom.Tablet)
+ {
+ bool isShowingSplit = Element.ShouldShowSplitMode || (Element.ShouldShowSplitMode && Element.MasterBehavior != MasterBehavior.Default && Element.IsPresented);
+ SetLockMode(isShowingSplit ? LockModeLockedOpen : LockModeUnlocked);
+ unchecked
+ {
+ SetScrimColor(isShowingSplit ? Color.Transparent.ToAndroid() : (int)DefaultScrimColor);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs
new file mode 100644
index 00000000..fd4cb72b
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs
@@ -0,0 +1,789 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
+using Android.Animation;
+using Android.App;
+using Android.Content;
+using Android.Content.Res;
+using Android.Graphics;
+using Android.Graphics.Drawables;
+using Android.OS;
+using Android.Runtime;
+using Android.Support.V4.Widget;
+using Android.Support.V7.Graphics.Drawable;
+using Android.Util;
+using Android.Views;
+using ActionBarDrawerToggle = Android.Support.V7.App.ActionBarDrawerToggle;
+using AView = Android.Views.View;
+using AToolbar = Android.Support.V7.Widget.Toolbar;
+using Fragment = Android.Support.V4.App.Fragment;
+using FragmentManager = Android.Support.V4.App.FragmentManager;
+using FragmentTransaction = Android.Support.V4.App.FragmentTransaction;
+using Object = Java.Lang.Object;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public class NavigationPageRenderer : VisualElementRenderer<NavigationPage>, IManageFragments
+ {
+ #region Statics
+
+ // All statics need to be made non-static/bound to platform
+
+ static ViewPropertyAnimator s_currentAnimation;
+
+ #endregion
+
+ readonly List<Fragment> _fragmentStack = new List<Fragment>();
+
+ Drawable _backgroundDrawable;
+ Page _current;
+
+ bool _disposed;
+ ActionBarDrawerToggle _drawerToggle;
+ FragmentManager _fragmentManager;
+ int _lastActionBarHeight = -1;
+ AToolbar _toolbar;
+ ToolbarTracker _toolbarTracker;
+ bool _toolbarVisible;
+
+ public NavigationPageRenderer()
+ {
+ AutoPackage = false;
+ Id = FormsAppCompatActivity.GetUniqueId();
+ Device.Info.PropertyChanged += DeviceInfoPropertyChanged;
+ }
+
+ internal int ContainerPadding { get; set; }
+
+ Page Current
+ {
+ get { return _current; }
+ set
+ {
+ if (_current == value)
+ return;
+
+ if (_current != null)
+ _current.PropertyChanged -= CurrentOnPropertyChanged;
+
+ _current = value;
+
+ if (_current != null)
+ {
+ _current.PropertyChanged += CurrentOnPropertyChanged;
+ ToolbarVisible = NavigationPage.GetHasNavigationBar(_current);
+ }
+ }
+ }
+
+ FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = ((FormsAppCompatActivity)Context).SupportFragmentManager);
+
+ bool ToolbarVisible
+ {
+ get { return _toolbarVisible; }
+ set
+ {
+ if (_toolbarVisible == value)
+ return;
+ _toolbarVisible = value;
+ RequestLayout();
+ }
+ }
+
+ public void SetFragmentManager(FragmentManager childFragmentManager)
+ {
+ if (_fragmentManager == null)
+ _fragmentManager = childFragmentManager;
+ }
+
+ public Task<bool> PopToRootAsync(Page page, bool animated = true)
+ {
+ return OnPopToRootAsync(page, animated);
+ }
+
+ public Task<bool> PopViewAsync(Page page, bool animated = true)
+ {
+ return OnPopViewAsync(page, animated);
+ }
+
+ public Task<bool> PushViewAsync(Page page, bool animated = true)
+ {
+ return OnPushAsync(page, animated);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+
+ var activity = (FormsAppCompatActivity)Context;
+
+ // API only exists on newer android YAY
+ if ((int)Build.VERSION.SdkInt >= 17)
+ {
+ if (!activity.IsDestroyed)
+ {
+ FragmentManager fm = FragmentManager;
+ FragmentTransaction trans = fm.BeginTransaction();
+ foreach (Fragment fragment in _fragmentStack)
+ trans.Remove(fragment);
+ trans.CommitAllowingStateLoss();
+ fm.ExecutePendingTransactions();
+ }
+ }
+
+ if (Element != null)
+ {
+ for (var i = 0; i < Element.InternalChildren.Count; i++)
+ {
+ var child = Element.InternalChildren[i] as VisualElement;
+ if (child == null)
+ continue;
+ IVisualElementRenderer renderer = Android.Platform.GetRenderer(child);
+ renderer?.Dispose();
+ }
+ Element.PushRequested -= OnPushed;
+ Element.PopRequested -= OnPopped;
+ Element.PopToRootRequested -= OnPoppedToRoot;
+ Element.InsertPageBeforeRequested -= OnInsertPageBeforeRequested;
+ Element.RemovePageRequested -= OnRemovePageRequested;
+ Element.SendDisappearing();
+ }
+
+ if (_toolbarTracker != null)
+ {
+ _toolbarTracker.CollectionChanged -= ToolbarTrackerOnCollectionChanged;
+ _toolbarTracker.Target = null;
+ _toolbarTracker = null;
+ }
+
+ if (_toolbar != null)
+ {
+ _toolbar.NavigationClick -= BarOnNavigationClick;
+ _toolbar.Dispose();
+ _toolbar = null;
+ }
+
+ Current = null;
+
+ Device.Info.PropertyChanged -= DeviceInfoPropertyChanged;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnAttachedToWindow()
+ {
+ base.OnAttachedToWindow();
+ Element.SendAppearing();
+ _fragmentStack.Last().UserVisibleHint = true;
+ RegisterToolbar();
+ UpdateToolbar();
+ }
+
+ protected override void OnDetachedFromWindow()
+ {
+ base.OnDetachedFromWindow();
+ Element.SendDisappearing();
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.OldElement != null)
+ {
+ e.OldElement.PushRequested -= OnPushed;
+ e.OldElement.PopRequested -= OnPopped;
+ e.OldElement.PopToRootRequested -= OnPoppedToRoot;
+ e.OldElement.InsertPageBeforeRequested -= OnInsertPageBeforeRequested;
+ e.OldElement.RemovePageRequested -= OnRemovePageRequested;
+
+ RemoveAllViews();
+ if (_toolbar != null)
+ AddView(_toolbar);
+ }
+
+ if (e.NewElement != null)
+ {
+ if (_toolbarTracker == null)
+ {
+ SetupToolbar();
+ _toolbarTracker = new ToolbarTracker();
+ _toolbarTracker.CollectionChanged += ToolbarTrackerOnCollectionChanged;
+ }
+
+ var parents = new List<Page>();
+ Page root = Element;
+ while (!Application.IsApplicationOrNull(root.RealParent))
+ {
+ root = (Page)root.RealParent;
+ parents.Add(root);
+ }
+
+ _toolbarTracker.Target = e.NewElement;
+ _toolbarTracker.AdditionalTargets = parents;
+ UpdateMenu();
+
+ e.NewElement.PushRequested += OnPushed;
+ e.NewElement.PopRequested += OnPopped;
+ e.NewElement.PopToRootRequested += OnPoppedToRoot;
+ e.NewElement.InsertPageBeforeRequested += OnInsertPageBeforeRequested;
+ e.NewElement.RemovePageRequested += OnRemovePageRequested;
+
+ // If there is already stuff on the stack we need to push it
+ e.NewElement.StackCopy.Reverse().ForEach(p => PushViewAsync(p, false));
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName)
+ UpdateToolbar();
+ else if (e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName)
+ UpdateToolbar();
+ }
+
+ protected override void OnLayout(bool changed, int l, int t, int r, int b)
+ {
+ AToolbar bar = _toolbar;
+ // make sure bar stays on top of everything
+ bar.BringToFront();
+
+ base.OnLayout(changed, l, t, r, b);
+
+ int barHeight = ActionBarHeight();
+
+ if (barHeight != _lastActionBarHeight && _lastActionBarHeight > 0)
+ {
+ ResetToolbar();
+ bar = _toolbar;
+ }
+ _lastActionBarHeight = barHeight;
+
+ bar.Measure(MeasureSpecFactory.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(barHeight, MeasureSpecMode.Exactly));
+
+ int internalHeight = b - t - barHeight;
+ int containerHeight = ToolbarVisible ? internalHeight : b - t;
+ containerHeight -= ContainerPadding;
+
+ Element.ContainerArea = new Rectangle(0, 0, Context.FromPixels(r - l), Context.FromPixels(containerHeight));
+ // Potential for optimization here, the exact conditions by which you don't need to do this are complex
+ // and the cost of doing when it's not needed is moderate to low since the layout will short circuit pretty fast
+ Element.ForceLayout();
+
+ for (var i = 0; i < ChildCount; i++)
+ {
+ AView child = GetChildAt(i);
+ bool isBar = JNIEnv.IsSameObject(child.Handle, bar.Handle);
+
+ if (ToolbarVisible)
+ {
+ if (isBar)
+ bar.Layout(0, 0, r - l, barHeight);
+ else
+ child.Layout(0, barHeight + ContainerPadding, r, b);
+ }
+ else
+ {
+ if (isBar)
+ bar.Layout(0, -1000, r, barHeight - 1000);
+ else
+ child.Layout(0, ContainerPadding, r, b);
+ }
+ }
+ }
+
+ protected virtual void SetupPageTransition(FragmentTransaction transaction, bool isPush)
+ {
+ if (isPush)
+ transaction.SetTransition((int)FragmentTransit.FragmentOpen);
+ else
+ transaction.SetTransition((int)FragmentTransit.FragmentClose);
+ }
+
+ internal int GetNavBarHeight()
+ {
+ if (!ToolbarVisible)
+ return 0;
+
+ return ActionBarHeight();
+ }
+
+ int ActionBarHeight()
+ {
+ int attr = Resource.Attribute.actionBarSize;
+
+ int actionBarHeight;
+ using(var tv = new TypedValue())
+ {
+ actionBarHeight = 0;
+ if (Context.Theme.ResolveAttribute(attr, tv, true))
+ actionBarHeight = TypedValue.ComplexToDimensionPixelSize(tv.Data, Resources.DisplayMetrics);
+ }
+
+ if (actionBarHeight <= 0)
+ return Device.Info.CurrentOrientation.IsPortrait() ? (int)Context.ToPixels(56) : (int)Context.ToPixels(48);
+
+ return actionBarHeight;
+ }
+
+ void AnimateArrowIn()
+ {
+ var icon = _toolbar.NavigationIcon as DrawerArrowDrawable;
+ if (icon == null)
+ return;
+
+ ValueAnimator valueAnim = ValueAnimator.OfFloat(0, 1);
+ valueAnim.SetDuration(200);
+ valueAnim.Update += (s, a) => icon.Progress = (float)a.Animation.AnimatedValue;
+ valueAnim.Start();
+ }
+
+ void AnimateArrowOut()
+ {
+ var icon = _toolbar.NavigationIcon as DrawerArrowDrawable;
+ if (icon == null)
+ return;
+
+ ValueAnimator valueAnim = ValueAnimator.OfFloat(1, 0);
+ valueAnim.SetDuration(200);
+ valueAnim.Update += (s, a) => icon.Progress = (float)a.Animation.AnimatedValue;
+ valueAnim.Start();
+ }
+
+ void BarOnNavigationClick(object sender, AToolbar.NavigationClickEventArgs navigationClickEventArgs)
+ {
+ Element?.PopAsync();
+ }
+
+ void CurrentOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName)
+ ToolbarVisible = NavigationPage.GetHasNavigationBar(Current);
+ else if (e.PropertyName == Page.TitleProperty.PropertyName)
+ UpdateToolbar();
+ }
+
+ async void DeviceInfoPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (nameof(Device.Info.CurrentOrientation) == e.PropertyName)
+ ResetToolbar();
+ }
+
+ void FilterPageFragment(Page page)
+ {
+ _fragmentStack.RemoveAll(f => ((FragmentContainer)f).Page == page);
+ }
+
+ void HandleToolbarItemPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == MenuItem.IsEnabledProperty.PropertyName || e.PropertyName == MenuItem.TextProperty.PropertyName || e.PropertyName == MenuItem.IconProperty.PropertyName)
+ UpdateMenu();
+ }
+
+ void InsertPageBefore(Page page, Page before)
+ {
+ UpdateToolbar();
+
+ int index = Element.InternalChildren.IndexOf(before);
+ if (index == -1)
+ throw new InvalidOperationException("This should never happen, please file a bug");
+
+ Fragment fragment = FragmentContainer.CreateInstance(page);
+ _fragmentStack.Insert(index, fragment);
+ }
+
+ void OnInsertPageBeforeRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ InsertPageBefore(e.Page, e.BeforePage);
+ }
+
+ void OnPopped(object sender, NavigationRequestedEventArgs e)
+ {
+ e.Task = PopViewAsync(e.Page, e.Animated);
+ }
+
+ void OnPoppedToRoot(object sender, NavigationRequestedEventArgs e)
+ {
+ e.Task = PopToRootAsync(e.Page, e.Animated);
+ }
+
+ Task<bool> OnPopToRootAsync(Page page, bool animated)
+ {
+ return SwitchContentAsync(page, animated, true, true);
+ }
+
+ Task<bool> OnPopViewAsync(Page page, bool animated)
+ {
+ Page pageToShow = Element.StackCopy.Skip(1).FirstOrDefault();
+ if (pageToShow == null)
+ return Task.FromResult(false);
+
+ return SwitchContentAsync(pageToShow, animated, true);
+ }
+
+ Task<bool> OnPushAsync(Page view, bool animated)
+ {
+ return SwitchContentAsync(view, animated);
+ }
+
+ void OnPushed(object sender, NavigationRequestedEventArgs e)
+ {
+ e.Task = PushViewAsync(e.Page, e.Animated);
+ }
+
+ void OnRemovePageRequested(object sender, NavigationRequestedEventArgs e)
+ {
+ RemovePage(e.Page);
+ }
+
+ void RegisterToolbar()
+ {
+ Context context = Context;
+ AToolbar bar = _toolbar;
+ Element page = Element.RealParent;
+
+ MasterDetailPage masterDetailPage = null;
+ while (page != null)
+ {
+ if (page is MasterDetailPage)
+ {
+ masterDetailPage = page as MasterDetailPage;
+ break;
+ }
+ page = page.RealParent;
+ }
+
+ if (masterDetailPage == null)
+ {
+ masterDetailPage = Element.InternalChildren[0] as MasterDetailPage;
+ if (masterDetailPage == null)
+ return;
+ }
+
+ if (masterDetailPage.ShouldShowSplitMode)
+ return;
+
+ var renderer = Android.Platform.GetRenderer(masterDetailPage) as MasterDetailPageRenderer;
+ if (renderer == null)
+ return;
+
+ var drawerLayout = (DrawerLayout)renderer;
+ _drawerToggle = new ActionBarDrawerToggle((Activity)context, drawerLayout, bar, global::Android.Resource.String.Ok, global::Android.Resource.String.Ok)
+ {
+ ToolbarNavigationClickListener = new ClickListener(Element)
+ };
+
+ drawerLayout.SetDrawerListener(new DrawerMultiplexedListener { Listeners = { _drawerToggle, renderer } });
+ _drawerToggle.DrawerIndicatorEnabled = true;
+ }
+
+ void RemovePage(Page page)
+ {
+ IVisualElementRenderer rendererToRemove = Android.Platform.GetRenderer(page);
+ var containerToRemove = (PageContainer)rendererToRemove?.ViewGroup.Parent;
+
+ // Also remove this page from the fragmentStack
+ FilterPageFragment(page);
+
+ containerToRemove.RemoveFromParent();
+ if (rendererToRemove != null)
+ {
+ rendererToRemove.ViewGroup.RemoveFromParent();
+ rendererToRemove.Dispose();
+ }
+ containerToRemove?.Dispose();
+
+ Device.StartTimer(TimeSpan.FromMilliseconds(10), () =>
+ {
+ UpdateToolbar();
+ return false;
+ });
+ }
+
+ void ResetToolbar()
+ {
+ _toolbar.RemoveFromParent();
+ _toolbar.NavigationClick -= BarOnNavigationClick;
+ _toolbar = null;
+
+ SetupToolbar();
+ RegisterToolbar();
+ UpdateToolbar();
+ UpdateMenu();
+ }
+
+ void SetupToolbar()
+ {
+ Context context = Context;
+ var activity = (FormsAppCompatActivity)context;
+
+ AToolbar bar;
+ if (FormsAppCompatActivity.ToolbarResource != 0)
+ bar = activity.LayoutInflater.Inflate(FormsAppCompatActivity.ToolbarResource, null).JavaCast<AToolbar>();
+ else
+ bar = new AToolbar(context);
+
+ bar.NavigationClick += BarOnNavigationClick;
+
+ AddView(bar);
+ _toolbar = bar;
+ }
+
+ Task<bool> SwitchContentAsync(Page view, bool animated, bool removed = false, bool popToRoot = false)
+ {
+ var activity = (FormsAppCompatActivity)Context;
+ var tcs = new TaskCompletionSource<bool>();
+ Fragment fragment = FragmentContainer.CreateInstance(view);
+ FragmentManager fm = FragmentManager;
+ List<Fragment> fragments = _fragmentStack;
+
+ Current = view;
+
+ FragmentTransaction transaction = fm.BeginTransaction();
+
+ if (animated)
+ SetupPageTransition(transaction, !removed);
+
+ transaction.DisallowAddToBackStack();
+
+ if (fragments.Count == 0)
+ {
+ transaction.Add(Id, fragment);
+ fragments.Add(fragment);
+ }
+ else
+ {
+ if (removed)
+ {
+ // pop only one page, or pop everything to the root
+ var popPage = true;
+ while (fragments.Count > 1 && popPage)
+ {
+ Fragment currentToRemove = fragments.Last();
+ fragments.RemoveAt(fragments.Count - 1);
+ transaction.Remove(currentToRemove);
+ popPage = popToRoot;
+ }
+
+ Fragment toShow = fragments.Last();
+ // Execute pending transactions so that we can be sure the fragment list is accurate.
+ fm.ExecutePendingTransactions();
+ if (fm.Fragments.Contains(toShow))
+ transaction.Show(toShow);
+ else
+ transaction.Add(Id, toShow);
+ }
+ else
+ {
+ // push
+ Fragment currentToHide = fragments.Last();
+ transaction.Hide(currentToHide);
+ transaction.Add(Id, fragment);
+ fragments.Add(fragment);
+ }
+ }
+ transaction.Commit();
+
+ // The fragment transitions don't really SUPPORT telling you when they end
+ // There are some hacks you can do, but they actually are worse than just doing this:
+
+ if (animated)
+ {
+ if (!removed)
+ {
+ UpdateToolbar();
+ if (_drawerToggle != null && Element.StackDepth == 2)
+ AnimateArrowIn();
+ }
+ else if (_drawerToggle != null && Element.StackDepth == 2)
+ AnimateArrowOut();
+
+ Device.StartTimer(TimeSpan.FromMilliseconds(200), () =>
+ {
+ tcs.TrySetResult(true);
+ fragment.UserVisibleHint = true;
+ if (removed)
+ UpdateToolbar();
+ return false;
+ });
+ }
+ else
+ {
+ Device.StartTimer(TimeSpan.FromMilliseconds(1), () =>
+ {
+ tcs.TrySetResult(true);
+ fragment.UserVisibleHint = true;
+ UpdateToolbar();
+ return false;
+ });
+ }
+
+ // 200ms is how long the animations are, and they are "reversible" in the sense that starting another one slightly before it's done is fine
+
+ return tcs.Task;
+ }
+
+ void ToolbarTrackerOnCollectionChanged(object sender, EventArgs eventArgs)
+ {
+ UpdateMenu();
+ }
+
+ void UpdateMenu()
+ {
+ AToolbar bar = _toolbar;
+ Context context = Context;
+ IMenu menu = bar.Menu;
+
+ foreach (ToolbarItem item in _toolbarTracker.ToolbarItems)
+ item.PropertyChanged -= HandleToolbarItemPropertyChanged;
+ menu.Clear();
+
+ foreach (ToolbarItem item in _toolbarTracker.ToolbarItems)
+ {
+ item.PropertyChanged += HandleToolbarItemPropertyChanged;
+ if (item.Order == ToolbarItemOrder.Secondary)
+ {
+ IMenuItem menuItem = menu.Add(item.Text);
+ menuItem.SetEnabled(item.IsEnabled);
+ menuItem.SetOnMenuItemClickListener(new GenericMenuClickListener(item.Activate));
+ }
+ else
+ {
+ IMenuItem menuItem = menu.Add(item.Text);
+ FileImageSource icon = item.Icon;
+ if (!string.IsNullOrEmpty(icon))
+ {
+ Drawable iconBitmap = context.Resources.GetDrawable(icon);
+ if (iconBitmap != null)
+ menuItem.SetIcon(iconBitmap);
+ }
+ menuItem.SetEnabled(item.IsEnabled);
+ menuItem.SetShowAsAction(ShowAsAction.Always);
+ menuItem.SetOnMenuItemClickListener(new GenericMenuClickListener(item.Activate));
+ }
+ }
+ }
+
+ void UpdateToolbar()
+ {
+ if (_disposed)
+ return;
+
+ Context context = Context;
+ var activity = (FormsAppCompatActivity)context;
+ AToolbar bar = _toolbar;
+ ActionBarDrawerToggle toggle = _drawerToggle;
+
+ if (bar == null)
+ return;
+
+ bool isNavigated = Element.StackDepth > 1;
+ bar.NavigationIcon = null;
+
+ if (isNavigated)
+ {
+ if (toggle != null)
+ {
+ toggle.DrawerIndicatorEnabled = false;
+ toggle.SyncState();
+ }
+
+ if (NavigationPage.GetHasBackButton(Element.CurrentPage))
+ {
+ var icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext);
+ icon.Progress = 1;
+ bar.NavigationIcon = icon;
+ }
+ }
+ else
+ {
+ if (toggle != null)
+ {
+ toggle.DrawerIndicatorEnabled = true;
+ toggle.SyncState();
+ }
+ }
+
+ Color tintColor = Element.BarBackgroundColor;
+
+ if (Forms.IsLollipopOrNewer)
+ {
+ if (tintColor.IsDefault)
+ bar.BackgroundTintMode = null;
+ else
+ {
+ bar.BackgroundTintMode = PorterDuff.Mode.Src;
+ bar.BackgroundTintList = ColorStateList.ValueOf(tintColor.ToAndroid());
+ }
+ }
+ else
+ {
+ if (tintColor.IsDefault && _backgroundDrawable != null)
+ bar.SetBackground(_backgroundDrawable);
+ else if (!tintColor.IsDefault)
+ {
+ if (_backgroundDrawable == null)
+ _backgroundDrawable = bar.Background;
+ bar.SetBackgroundColor(tintColor.ToAndroid());
+ }
+ }
+
+ Color textColor = Element.BarTextColor;
+ if (!textColor.IsDefault)
+ bar.SetTitleTextColor(textColor.ToAndroid().ToArgb());
+
+ bar.Title = Element.CurrentPage.Title ?? "";
+ }
+
+ class ClickListener : Object, IOnClickListener
+ {
+ readonly NavigationPage _element;
+
+ public ClickListener(NavigationPage element)
+ {
+ _element = element;
+ }
+
+ public void OnClick(AView v)
+ {
+ _element?.PopAsync();
+ }
+ }
+
+ class DrawerMultiplexedListener : Object, DrawerLayout.IDrawerListener
+ {
+ public List<DrawerLayout.IDrawerListener> Listeners { get; } = new List<DrawerLayout.IDrawerListener>(2);
+
+ public void OnDrawerClosed(AView drawerView)
+ {
+ foreach (DrawerLayout.IDrawerListener listener in Listeners)
+ listener.OnDrawerClosed(drawerView);
+ }
+
+ public void OnDrawerOpened(AView drawerView)
+ {
+ foreach (DrawerLayout.IDrawerListener listener in Listeners)
+ listener.OnDrawerOpened(drawerView);
+ }
+
+ public void OnDrawerSlide(AView drawerView, float slideOffset)
+ {
+ foreach (DrawerLayout.IDrawerListener listener in Listeners)
+ listener.OnDrawerSlide(drawerView, slideOffset);
+ }
+
+ public void OnDrawerStateChanged(int newState)
+ {
+ foreach (DrawerLayout.IDrawerListener listener in Listeners)
+ listener.OnDrawerStateChanged(newState);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/PickerRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/PickerRenderer.cs
new file mode 100644
index 00000000..d59d9f6e
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/PickerRenderer.cs
@@ -0,0 +1,141 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using Android.App;
+using Android.Text;
+using Android.Widget;
+using Object = Java.Lang.Object;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public class PickerRenderer : ViewRenderer<Picker, EditText>
+ {
+ AlertDialog _dialog;
+ bool _disposed;
+
+ public PickerRenderer()
+ {
+ AutoPackage = false;
+ }
+
+ protected override EditText CreateNativeControl()
+ {
+ return new EditText(Context);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+
+ ((ObservableList<string>)Element.Items).CollectionChanged -= RowsCollectionChanged;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
+ {
+ if (e.OldElement != null)
+ ((ObservableList<string>)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
+
+ if (e.NewElement != null)
+ {
+ ((ObservableList<string>)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
+ if (Control == null)
+ {
+ EditText textField = CreateNativeControl();
+ textField.Focusable = false;
+ textField.Clickable = true;
+ textField.Tag = this;
+ textField.InputType = InputTypes.Null;
+ textField.SetOnClickListener(PickerListener.Instance);
+ SetNativeControl(textField);
+ }
+ UpdatePicker();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Picker.TitleProperty.PropertyName)
+ UpdatePicker();
+ if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName)
+ UpdatePicker();
+ }
+
+ internal override void OnFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
+ {
+ base.OnFocusChangeRequested(sender, e);
+
+ if (e.Focus)
+ OnClick();
+ else if (_dialog != null)
+ {
+ _dialog.Hide();
+ ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
+ Control.ClearFocus();
+ _dialog = null;
+ }
+ }
+
+ void OnClick()
+ {
+ Picker model = Element;
+ using(var builder = new AlertDialog.Builder(Context))
+ {
+ builder.SetTitle(model.Title ?? "");
+ string[] items = model.Items.ToArray();
+ builder.SetItems(items, (s, e) => ((IElementController)model).SetValueFromRenderer(Picker.SelectedIndexProperty, e.Which));
+
+ builder.SetNegativeButton(global::Android.Resource.String.Cancel, (o, args) => { });
+
+ _dialog = builder.Create();
+ }
+
+ _dialog.SetCanceledOnTouchOutside(true);
+ _dialog.DismissEvent += (sender, args) =>
+ {
+ _dialog.Dispose();
+ _dialog = null;
+ };
+
+ _dialog.Show();
+ }
+
+ void RowsCollectionChanged(object sender, EventArgs e)
+ {
+ UpdatePicker();
+ }
+
+ void UpdatePicker()
+ {
+ Control.Hint = Element.Title;
+
+ if (Element.SelectedIndex == -1 || Element.Items == null)
+ Control.Text = null;
+ else
+ Control.Text = Element.Items[Element.SelectedIndex];
+ }
+
+ class PickerListener : Object, IOnClickListener
+ {
+ #region Statics
+
+ public static readonly PickerListener Instance = new PickerListener();
+
+ #endregion
+
+ public void OnClick(global::Android.Views.View v)
+ {
+ var renderer = v.Tag as PickerRenderer;
+ renderer?.OnClick();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/Platform.cs b/Xamarin.Forms.Platform.Android/AppCompat/Platform.cs
new file mode 100644
index 00000000..12c6a02a
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/Platform.cs
@@ -0,0 +1,394 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Android.Content;
+using Android.Views;
+using Android.Views.Animations;
+using ARelativeLayout = Android.Widget.RelativeLayout;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ internal class Platform : BindableObject, IPlatform, IPlatformLayout, INavigation, IDisposable
+ {
+ readonly Context _context;
+ readonly PlatformRenderer _renderer;
+ bool _disposed;
+ bool _navAnimationInProgress;
+ NavigationModel _navModel = new NavigationModel();
+
+ public Platform(Context context)
+ {
+ _context = context;
+
+ _renderer = new PlatformRenderer(context, this);
+
+ FormsAppCompatActivity.BackPressed += HandleBackPressed;
+ }
+
+ internal bool NavAnimationInProgress
+ {
+ get { return _navAnimationInProgress; }
+ set
+ {
+ if (_navAnimationInProgress == value)
+ return;
+ _navAnimationInProgress = value;
+ if (value)
+ MessagingCenter.Send(this, CloseContextActionsSignalName);
+ }
+ }
+
+ Page Page { get; set; }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+ _disposed = true;
+
+ SetPage(null);
+
+ FormsAppCompatActivity.BackPressed -= HandleBackPressed;
+ }
+
+ void INavigation.InsertPageBefore(Page page, Page before)
+ {
+ throw new InvalidOperationException("InsertPageBefore is not supported globally on Android, please use a NavigationPage.");
+ }
+
+ IReadOnlyList<Page> INavigation.ModalStack => _navModel.Modals.ToList();
+
+ IReadOnlyList<Page> INavigation.NavigationStack => new List<Page>();
+
+ Task<Page> INavigation.PopAsync()
+ {
+ return ((INavigation)this).PopAsync(true);
+ }
+
+ Task<Page> INavigation.PopAsync(bool animated)
+ {
+ throw new InvalidOperationException("PopAsync is not supported globally on Android, please use a NavigationPage.");
+ }
+
+ Task<Page> INavigation.PopModalAsync()
+ {
+ return ((INavigation)this).PopModalAsync(true);
+ }
+
+ Task<Page> INavigation.PopModalAsync(bool animated)
+ {
+ Page modal = _navModel.PopModal();
+ modal.SendDisappearing();
+ var source = new TaskCompletionSource<Page>();
+
+ IVisualElementRenderer modalRenderer = Android.Platform.GetRenderer(modal);
+ if (modalRenderer != null)
+ {
+ var modalContainer = modalRenderer.ViewGroup.Parent as ModalContainer;
+ if (animated)
+ {
+ modalContainer.Animate().TranslationY(_renderer.Height).SetInterpolator(new AccelerateInterpolator(1)).SetDuration(300).SetListener(new GenericAnimatorListener
+ {
+ OnEnd = a =>
+ {
+ modalContainer.RemoveFromParent();
+ modalContainer.Dispose();
+ source.TrySetResult(modal);
+ _navModel.CurrentPage?.SendAppearing();
+ modalContainer = null;
+ }
+ });
+ }
+ else
+ {
+ modalContainer.RemoveFromParent();
+ modalContainer.Dispose();
+ source.TrySetResult(modal);
+ _navModel.CurrentPage?.SendAppearing();
+ }
+ }
+
+ return source.Task;
+ }
+
+ Task INavigation.PopToRootAsync()
+ {
+ return ((INavigation)this).PopToRootAsync(true);
+ }
+
+ Task INavigation.PopToRootAsync(bool animated)
+ {
+ throw new InvalidOperationException("PopToRootAsync is not supported globally on Android, please use a NavigationPage.");
+ }
+
+ Task INavigation.PushAsync(Page root)
+ {
+ return ((INavigation)this).PushAsync(root, true);
+ }
+
+ Task INavigation.PushAsync(Page root, bool animated)
+ {
+ throw new InvalidOperationException("PushAsync is not supported globally on Android, please use a NavigationPage.");
+ }
+
+ Task INavigation.PushModalAsync(Page modal)
+ {
+ return ((INavigation)this).PushModalAsync(modal, true);
+ }
+
+ async Task INavigation.PushModalAsync(Page modal, bool animated)
+ {
+ _navModel.CurrentPage?.SendDisappearing();
+
+ _navModel.PushModal(modal);
+
+ modal.Platform = this;
+
+ Task presentModal = PresentModal(modal, animated);
+
+ await presentModal;
+
+ // Verify that the modal is still on the stack
+ if (_navModel.CurrentPage == modal)
+ modal.SendAppearing();
+ }
+
+ void INavigation.RemovePage(Page page)
+ {
+ throw new InvalidOperationException("RemovePage is not supported globally on Android, please use a NavigationPage.");
+ }
+
+ SizeRequest IPlatform.GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint)
+ {
+ Performance.Start();
+
+ // FIXME: potential crash
+ IVisualElementRenderer viewRenderer = Android.Platform.GetRenderer(view);
+
+ // negative numbers have special meanings to android they don't to us
+ widthConstraint = widthConstraint <= -1 ? double.PositiveInfinity : _context.ToPixels(widthConstraint);
+ heightConstraint = heightConstraint <= -1 ? double.PositiveInfinity : _context.ToPixels(heightConstraint);
+
+ int width = !double.IsPositiveInfinity(widthConstraint)
+ ? MeasureSpecFactory.MakeMeasureSpec((int)widthConstraint, MeasureSpecMode.AtMost)
+ : MeasureSpecFactory.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
+
+ int height = !double.IsPositiveInfinity(heightConstraint)
+ ? MeasureSpecFactory.MakeMeasureSpec((int)heightConstraint, MeasureSpecMode.AtMost)
+ : MeasureSpecFactory.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
+
+ SizeRequest rawResult = viewRenderer.GetDesiredSize(width, height);
+ if (rawResult.Minimum == Size.Zero)
+ rawResult.Minimum = rawResult.Request;
+ var result = new SizeRequest(new Size(_context.FromPixels(rawResult.Request.Width), _context.FromPixels(rawResult.Request.Height)),
+ new Size(_context.FromPixels(rawResult.Minimum.Width), _context.FromPixels(rawResult.Minimum.Height)));
+
+ Performance.Stop();
+ return result;
+ }
+
+ void IPlatformLayout.OnLayout(bool changed, int l, int t, int r, int b)
+ {
+ if (changed)
+ LayoutRootPage(Page, r - l, b - t);
+
+ Android.Platform.GetRenderer(Page).UpdateLayout();
+
+ for (var i = 0; i < _renderer.ChildCount; i++)
+ {
+ global::Android.Views.View child = _renderer.GetChildAt(i);
+ if (child is ModalContainer)
+ {
+ child.Measure(MeasureSpecFactory.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(t - b, MeasureSpecMode.Exactly));
+ child.Layout(l, t, r, b);
+ }
+ }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ SetInheritedBindingContext(Page, BindingContext);
+
+ base.OnBindingContextChanged();
+ }
+
+ internal void SetPage(Page newRoot)
+ {
+ var layout = false;
+ if (Page != null)
+ {
+ _renderer.RemoveAllViews();
+
+ foreach (IVisualElementRenderer rootRenderer in _navModel.Roots.Select(Android.Platform.GetRenderer))
+ rootRenderer.Dispose();
+ _navModel = new NavigationModel();
+
+ layout = true;
+ }
+
+ if (newRoot == null)
+ return;
+
+ _navModel.Push(newRoot, null);
+
+ Page = newRoot;
+ Page.Platform = this;
+ AddChild(Page, layout);
+
+ ((Application)Page.RealParent).NavigationProxy.Inner = this;
+ }
+
+ void AddChild(Page page, bool layout = false)
+ {
+ if (Android.Platform.GetRenderer(page) != null)
+ return;
+
+ Android.Platform.SetPageContext(page, _context);
+ IVisualElementRenderer renderView = RendererFactory.GetRenderer(page);
+ Android.Platform.SetRenderer(page, renderView);
+
+ if (layout)
+ LayoutRootPage(page, _renderer.Width, _renderer.Height);
+
+ _renderer.AddView(renderView.ViewGroup);
+ }
+
+ bool HandleBackPressed(object sender, EventArgs e)
+ {
+ if (NavAnimationInProgress)
+ return true;
+
+ Page root = _navModel.Roots.Last();
+ bool handled = root.SendBackButtonPressed();
+
+ return handled;
+ }
+
+ void LayoutRootPage(Page page, int width, int height)
+ {
+ var activity = (FormsAppCompatActivity)_context;
+ int statusBarHeight = Forms.IsLollipopOrNewer ? activity.GetStatusBarHeight() : 0;
+
+ if (page is MasterDetailPage)
+ page.Layout(new Rectangle(0, 0, _context.FromPixels(width), _context.FromPixels(height)));
+ else
+ {
+ page.Layout(new Rectangle(0, _context.FromPixels(statusBarHeight), _context.FromPixels(width), _context.FromPixels(height - statusBarHeight)));
+ }
+ }
+
+ Task PresentModal(Page modal, bool animated)
+ {
+ var modalContainer = new ModalContainer(_context, modal);
+
+ _renderer.AddView(modalContainer);
+
+ var source = new TaskCompletionSource<bool>();
+ NavAnimationInProgress = true;
+ if (animated)
+ {
+ modalContainer.TranslationY = _renderer.Height;
+ modalContainer.Animate().TranslationY(0).SetInterpolator(new DecelerateInterpolator(1)).SetDuration(300).SetListener(new GenericAnimatorListener
+ {
+ OnEnd = a =>
+ {
+ source.TrySetResult(false);
+ NavAnimationInProgress = false;
+ modalContainer = null;
+ },
+ OnCancel = a =>
+ {
+ source.TrySetResult(true);
+ NavAnimationInProgress = false;
+ modalContainer = null;
+ }
+ });
+ }
+ else
+ {
+ NavAnimationInProgress = false;
+ source.TrySetResult(true);
+ }
+
+ return source.Task;
+ }
+
+ sealed class ModalContainer : ViewGroup
+ {
+ global::Android.Views.View _backgroundView;
+ bool _disposed;
+ Page _modal;
+ IVisualElementRenderer _renderer;
+
+ public ModalContainer(Context context, Page modal) : base(context)
+ {
+ _modal = modal;
+
+ _backgroundView = new global::Android.Views.View(context);
+ _backgroundView.SetWindowBackground();
+ AddView(_backgroundView);
+
+ Android.Platform.SetPageContext(modal, context);
+ _renderer = RendererFactory.GetRenderer(modal);
+ Android.Platform.SetRenderer(modal, _renderer);
+
+ AddView(_renderer.ViewGroup);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ RemoveAllViews();
+ if (_renderer != null)
+ {
+ _renderer.Dispose();
+ _renderer = null;
+ _modal.ClearValue(Android.Platform.RendererProperty);
+ _modal = null;
+ }
+
+ if (_backgroundView != null)
+ {
+ _backgroundView.Dispose();
+ _backgroundView = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnLayout(bool changed, int l, int t, int r, int b)
+ {
+ var activity = (FormsAppCompatActivity)Context;
+ int statusBarHeight = Forms.IsLollipopOrNewer ? activity.GetStatusBarHeight() : 0;
+ if (changed)
+ {
+ if (_modal is MasterDetailPage)
+ _modal.Layout(new Rectangle(0, 0, activity.FromPixels(r - l), activity.FromPixels(b - t)));
+ else
+ {
+ _modal.Layout(new Rectangle(0, activity.FromPixels(statusBarHeight), activity.FromPixels(r - l), activity.FromPixels(b - t - statusBarHeight)));
+ }
+
+ _backgroundView.Layout(0, statusBarHeight, r - l, b - t);
+ }
+
+ _renderer.UpdateLayout();
+ }
+ }
+
+ #region Statics
+
+ public static implicit operator ViewGroup(Platform canvas)
+ {
+ return canvas._renderer;
+ }
+
+ internal const string CloseContextActionsSignalName = "Xamarin.CloseContextActions";
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/Resource.cs b/Xamarin.Forms.Platform.Android/AppCompat/Resource.cs
new file mode 100644
index 00000000..08d3c5e4
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/Resource.cs
@@ -0,0 +1,31 @@
+using Android.Runtime;
+
+[assembly: ResourceDesigner("Xamarin.Forms.Platform.Android.Resource", IsApplication = false)]
+
+namespace Xamarin.Forms.Platform.Android
+{
+ public class Resource
+ {
+ static Resource()
+ {
+ ResourceIdManager.UpdateIdValues();
+ }
+
+ public class Attribute
+ {
+ // aapt resource value: 0x7f0100a5
+ // ReSharper disable once InconsistentNaming
+ // Android is pretty insistent about this casing
+ public static int actionBarSize = 2130772133;
+
+ static Attribute()
+ {
+ ResourceIdManager.UpdateIdValues();
+ }
+
+ Attribute()
+ {
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/SwitchRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/SwitchRenderer.cs
new file mode 100644
index 00000000..f243c634
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/SwitchRenderer.cs
@@ -0,0 +1,92 @@
+using System;
+using Android.Support.V7.Widget;
+using Android.Widget;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public class SwitchRenderer : ViewRenderer<Switch, SwitchCompat>, CompoundButton.IOnCheckedChangeListener
+ {
+ bool _disposed;
+
+ public SwitchRenderer()
+ {
+ AutoPackage = false;
+ }
+
+ void CompoundButton.IOnCheckedChangeListener.OnCheckedChanged(CompoundButton buttonView, bool isChecked)
+ {
+ ((IViewController)Element).SetValueFromRenderer(Switch.IsToggledProperty, isChecked);
+ }
+
+ public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
+ {
+ SizeRequest sizeConstraint = base.GetDesiredSize(widthConstraint, heightConstraint);
+
+ if (sizeConstraint.Request.Width == 0)
+ {
+ int width = widthConstraint;
+ if (widthConstraint <= 0)
+ width = (int)Context.GetThemeAttributeDp(global::Android.Resource.Attribute.SwitchMinWidth);
+ else if (widthConstraint <= 0)
+ width = 100;
+
+ sizeConstraint = new SizeRequest(new Size(width, sizeConstraint.Request.Height), new Size(width, sizeConstraint.Minimum.Height));
+ }
+
+ return sizeConstraint;
+ }
+
+ protected override SwitchCompat CreateNativeControl()
+ {
+ return new SwitchCompat(Context);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+
+ if (Element != null)
+ Element.Toggled -= HandleToggled;
+
+ Control.SetOnCheckedChangeListener(null);
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.OldElement != null)
+ e.OldElement.Toggled -= HandleToggled;
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SwitchCompat aswitch = CreateNativeControl();
+ aswitch.SetOnCheckedChangeListener(this);
+ SetNativeControl(aswitch);
+ }
+ else
+ UpdateEnabled(); // Normally set by SetNativeControl, but not when the Control is reused.
+
+ e.NewElement.Toggled += HandleToggled;
+ Control.Checked = e.NewElement.IsToggled;
+ }
+ }
+
+ void HandleToggled(object sender, EventArgs e)
+ {
+ Control.Checked = Element.IsToggled;
+ }
+
+ void UpdateEnabled()
+ {
+ Control.Enabled = Element.IsEnabled;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs
new file mode 100644
index 00000000..f978028b
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs
@@ -0,0 +1,322 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Android.Content;
+using Android.OS;
+using Android.Runtime;
+using Android.Support.Design.Widget;
+using Android.Support.V4.App;
+using Android.Support.V4.View;
+using Android.Views;
+
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public class TabbedPageRenderer : VisualElementRenderer<TabbedPage>, TabLayout.IOnTabSelectedListener, ViewPager.IOnPageChangeListener, IManageFragments
+ {
+ bool _disposed;
+ FragmentManager _fragmentManager;
+ TabLayout _tabLayout;
+ bool _useAnimations = true;
+ FormsViewPager _viewPager;
+
+ public TabbedPageRenderer()
+ {
+ AutoPackage = false;
+ }
+
+ public FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = ((FormsAppCompatActivity)Context).SupportFragmentManager);
+
+ internal bool UseAnimations
+ {
+ get { return _useAnimations; }
+ set
+ {
+ FormsViewPager pager = _viewPager;
+
+ _useAnimations = value;
+ if (pager != null)
+ pager.EnableGesture = value;
+ }
+ }
+
+ public void SetFragmentManager(FragmentManager childFragmentManager)
+ {
+ if (_fragmentManager == null)
+ _fragmentManager = childFragmentManager;
+ }
+
+ void ViewPager.IOnPageChangeListener.OnPageScrolled(int position, float positionOffset, int positionOffsetPixels)
+ {
+ UpdateTabBarTranslation(position, positionOffset);
+ }
+
+ void ViewPager.IOnPageChangeListener.OnPageScrollStateChanged(int state)
+ {
+ }
+
+ void ViewPager.IOnPageChangeListener.OnPageSelected(int position)
+ {
+ Element.CurrentPage = Element.Children[position];
+ }
+
+ void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
+ {
+ }
+
+ void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)
+ {
+ if (Element == null)
+ return;
+
+ int selectedIndex = tab.Position;
+ if (Element.Children.Count > selectedIndex && selectedIndex >= 0)
+ Element.CurrentPage = Element.Children[selectedIndex];
+ }
+
+ void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
+ {
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _disposed = true;
+ RemoveAllViews();
+ foreach (Page pageToRemove in Element.Children)
+ {
+ IVisualElementRenderer pageRenderer = Android.Platform.GetRenderer(pageToRemove);
+ if (pageRenderer != null)
+ {
+ pageRenderer.ViewGroup.RemoveFromParent();
+ pageRenderer.Dispose();
+ }
+ pageToRemove.ClearValue(Android.Platform.RendererProperty);
+ }
+
+ if (_viewPager != null)
+ {
+ _viewPager.Adapter.Dispose();
+ _viewPager.Dispose();
+ _viewPager = null;
+ }
+
+ if (_tabLayout != null)
+ {
+ _tabLayout.SetOnTabSelectedListener(null);
+ _tabLayout.Dispose();
+ _tabLayout = null;
+ }
+
+ if (Element != null)
+ Element.InternalChildren.CollectionChanged -= OnChildrenCollectionChanged;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnAttachedToWindow()
+ {
+ base.OnAttachedToWindow();
+ Element.SendAppearing();
+ }
+
+ protected override void OnDetachedFromWindow()
+ {
+ base.OnDetachedFromWindow();
+ Element.SendDisappearing();
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
+ {
+ base.OnElementChanged(e);
+
+ var activity = (FormsAppCompatActivity)Context;
+
+ if (e.OldElement != null)
+ e.OldElement.InternalChildren.CollectionChanged -= OnChildrenCollectionChanged;
+
+ if (e.NewElement != null)
+ {
+ if (_tabLayout == null)
+ {
+ TabLayout tabs;
+ if (FormsAppCompatActivity.TabLayoutResource > 0)
+ {
+ tabs = _tabLayout = activity.LayoutInflater.Inflate(FormsAppCompatActivity.TabLayoutResource, null).JavaCast<TabLayout>();
+ }
+ else
+ tabs = _tabLayout = new TabLayout(activity) { TabMode = TabLayout.ModeFixed, TabGravity = TabLayout.GravityFill };
+ FormsViewPager pager =
+ _viewPager =
+ new FormsViewPager(activity)
+ {
+ OverScrollMode = OverScrollMode.Never,
+ EnableGesture = UseAnimations,
+ LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent),
+ Adapter = new FormsFragmentPagerAdapter<Page>(e.NewElement, FragmentManager) { CountOverride = e.NewElement.Children.Count }
+ };
+ pager.Id = FormsAppCompatActivity.GetUniqueId();
+ pager.AddOnPageChangeListener(this);
+
+ tabs.SetupWithViewPager(pager);
+ UpdateTabIcons();
+ tabs.SetOnTabSelectedListener(this);
+
+ AddView(pager);
+ AddView(tabs);
+ }
+
+ TabbedPage tabbedPage = e.NewElement;
+ if (tabbedPage.CurrentPage != null)
+ ScrollToCurrentPage();
+
+ UpdateIgnoreContainerAreas();
+ tabbedPage.InternalChildren.CollectionChanged += OnChildrenCollectionChanged;
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == "CurrentPage")
+ ScrollToCurrentPage();
+ }
+
+ protected override void OnLayout(bool changed, int l, int t, int r, int b)
+ {
+ TabLayout tabs = _tabLayout;
+ FormsViewPager pager = _viewPager;
+ Context context = Context;
+ int width = r - l;
+ int height = b - t;
+
+ tabs.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.AtMost));
+ var tabsHeight = 0;
+
+ //MinimumHeight is only available on API 16+
+ if ((int)Build.VERSION.SdkInt >= 16)
+ tabsHeight = Math.Min(height, Math.Max(tabs.MeasuredHeight, tabs.MinimumHeight));
+ else
+ tabsHeight = Math.Min(height, tabs.MeasuredHeight);
+
+ pager.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.AtMost), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.AtMost));
+
+ if (width > 0 && height > 0)
+ {
+ Element.ContainerArea = new Rectangle(0, context.FromPixels(tabsHeight), context.FromPixels(width), context.FromPixels(height - tabsHeight));
+
+ for (var i = 0; i < Element.InternalChildren.Count; i++)
+ {
+ var child = Element.InternalChildren[i] as VisualElement;
+ if (child == null)
+ continue;
+ IVisualElementRenderer renderer = Android.Platform.GetRenderer(child);
+ var navigationRenderer = renderer as NavigationPageRenderer;
+ if (navigationRenderer != null)
+ navigationRenderer.ContainerPadding = tabsHeight;
+ }
+
+ pager.Layout(0, 0, width, b);
+ // We need to measure again to ensure that the tabs show up
+ tabs.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(tabsHeight, MeasureSpecMode.Exactly));
+ tabs.Layout(0, 0, width, tabsHeight);
+
+ UpdateTabBarTranslation(pager.CurrentItem, 0);
+ }
+
+ base.OnLayout(changed, l, t, r, b);
+ }
+
+ void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ FormsViewPager pager = _viewPager;
+ TabLayout tabs = _tabLayout;
+
+ ((FormsFragmentPagerAdapter<Page>)pager.Adapter).CountOverride = Element.Children.Count;
+ pager.Adapter.NotifyDataSetChanged();
+
+ if (Element.Children.Count == 0)
+ tabs.RemoveAllTabs();
+ else
+ {
+ tabs.SetupWithViewPager(pager);
+ UpdateTabIcons();
+ tabs.SetOnTabSelectedListener(this);
+ }
+
+ UpdateIgnoreContainerAreas();
+ }
+
+ void ScrollToCurrentPage()
+ {
+ _viewPager.SetCurrentItem(Element.Children.IndexOf(Element.CurrentPage), UseAnimations);
+ }
+
+ void UpdateIgnoreContainerAreas()
+ {
+ foreach (Page child in Element.Children)
+ child.IgnoresContainerArea = child is NavigationPage;
+ }
+
+ void UpdateTabBarTranslation(int position, float offset)
+ {
+ TabLayout tabs = _tabLayout;
+
+ if (position >= Element.InternalChildren.Count)
+ return;
+
+ var leftPage = (Page)Element.InternalChildren[position];
+ IVisualElementRenderer leftRenderer = Android.Platform.GetRenderer(leftPage);
+
+ if (leftRenderer == null)
+ return;
+
+ if (offset <= 0 || position >= Element.InternalChildren.Count - 1)
+ {
+ var leftNavRenderer = leftRenderer as NavigationPageRenderer;
+ if (leftNavRenderer != null)
+ tabs.TranslationY = leftNavRenderer.GetNavBarHeight();
+ else
+ tabs.TranslationY = 0;
+ }
+ else
+ {
+ var rightPage = (Page)Element.InternalChildren[position + 1];
+ IVisualElementRenderer rightRenderer = Android.Platform.GetRenderer(rightPage);
+
+ var leftHeight = 0;
+ var leftNavRenderer = leftRenderer as NavigationPageRenderer;
+ if (leftNavRenderer != null)
+ leftHeight = leftNavRenderer.GetNavBarHeight();
+
+ var rightHeight = 0;
+ var rightNavRenderer = rightRenderer as NavigationPageRenderer;
+ if (rightNavRenderer != null)
+ rightHeight = rightNavRenderer.GetNavBarHeight();
+
+ tabs.TranslationY = leftHeight + (rightHeight - leftHeight) * offset;
+ }
+ }
+
+ void UpdateTabIcons()
+ {
+ TabLayout tabs = _tabLayout;
+
+ if (tabs.TabCount != Element.Children.Count)
+ return;
+
+ for (var i = 0; i < Element.Children.Count; i++)
+ {
+ Page child = Element.Children[i];
+ FileImageSource icon = child.Icon;
+ if (string.IsNullOrEmpty(icon))
+ continue;
+
+ TabLayout.Tab tab = tabs.GetTabAt(i);
+ tab.SetIcon(ResourceManager.IdFromTitle(icon, ResourceManager.DrawableClass));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/ViewRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/ViewRenderer.cs
new file mode 100644
index 00000000..3c869216
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/ViewRenderer.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms.Platform.Android.AppCompat
+{
+ public abstract class ViewRenderer<TView, TControl> : Android.ViewRenderer<TView, TControl> where TView : View where TControl : global::Android.Views.View
+ {
+ protected abstract TControl CreateNativeControl();
+ }
+} \ No newline at end of file