maui-linux/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs

1236 строки
33 KiB
C#

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 Xamarin.Forms.Internals;
using ActionBarDrawerToggle = Android.Support.V7.App.ActionBarDrawerToggle;
using AView = Android.Views.View;
using AColor = Android.Graphics.Color;
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;
using static Xamarin.Forms.PlatformConfiguration.AndroidSpecific.AppCompat.NavigationPage;
using static Android.Views.View;
using System.IO;
using Android.Widget;
namespace Xamarin.Forms.Platform.Android.AppCompat
{
public class NavigationPageRenderer : VisualElementRenderer<NavigationPage>, IManageFragments, IOnClickListener, ILifeCycleState
{
const int DefaultDisabledToolbarAlpha = 127;
readonly List<Fragment> _fragmentStack = new List<Fragment>();
Drawable _backgroundDrawable;
Page _current;
bool _disposed;
ActionBarDrawerToggle _drawerToggle;
FragmentManager _fragmentManager;
int _lastActionBarHeight = -1;
int _statusbarHeight;
AToolbar _toolbar;
ToolbarTracker _toolbarTracker;
DrawerMultiplexedListener _drawerListener;
DrawerLayout _drawerLayout;
MasterDetailPage _masterDetailPage;
bool _toolbarVisible;
IVisualElementRenderer _titleViewRenderer;
Container _titleView;
ImageView _titleIconView;
ImageSource _imageSource;
bool _isAttachedToWindow;
Platform _platform;
// The following is based on https://android.googlesource.com/platform/frameworks/support.git/+/4a7e12af4ec095c3a53bb8481d8d92f63157c3b7/v4/java/android/support/v4/app/FragmentManager.java#677
// Must be overriden in a custom renderer to match durations in XML animation resource files
protected virtual int TransitionDuration { get; set; } = 220;
bool ILifeCycleState.MarkedForDispose { get; set; } = false;
public NavigationPageRenderer(Context context) : base(context)
{
AutoPackage = false;
Id = Platform.GenerateViewId();
Device.Info.PropertyChanged += DeviceInfoPropertyChanged;
}
[Obsolete("This constructor is obsolete as of version 2.5. Please use NavigationPageRenderer(Context) instead.")]
public NavigationPageRenderer()
{
AutoPackage = false;
Id = Platform.GenerateViewId();
Device.Info.PropertyChanged += DeviceInfoPropertyChanged;
}
Platform Platform
{
get
{
if (_platform == null)
{
if (Context is FormsAppCompatActivity activity)
{
_platform = activity.Platform;
}
}
return _platform;
}
}
internal int ContainerTopPadding { get; set; }
internal int ContainerBottomPadding { 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);
IPageController PageController => Element;
bool ToolbarVisible
{
get { return _toolbarVisible; }
set
{
if (_toolbarVisible == value)
return;
_toolbarVisible = value;
RequestLayout();
}
}
void IManageFragments.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;
if (_titleViewRenderer != null)
{
Android.Platform.ClearRenderer(_titleViewRenderer.View);
_titleViewRenderer.Dispose();
_titleViewRenderer = null;
}
_toolbar.RemoveView(_titleView);
_titleView?.Dispose();
_titleView = null;
_toolbar.RemoveView(_titleIconView);
_titleIconView?.Dispose();
_titleIconView = null;
_imageSource = null;
if (_toolbarTracker != null)
{
_toolbarTracker.CollectionChanged -= ToolbarTrackerOnCollectionChanged;
_toolbarTracker.Target = null;
_toolbarTracker = null;
}
if (_toolbar != null)
{
_toolbar.SetNavigationOnClickListener(null);
_toolbar.Dispose();
_toolbar = null;
}
if (_drawerLayout != null && _drawerListener != null)
{
_drawerLayout.RemoveDrawerListener(_drawerListener);
}
if (_drawerListener != null)
{
_drawerListener.Dispose();
_drawerListener = null;
}
if (_drawerToggle != null)
{
_drawerToggle.Dispose();
_drawerToggle = null;
}
if (_backgroundDrawable != null)
{
_backgroundDrawable.Dispose();
_backgroundDrawable = null;
}
Current = null;
// We dispose the child renderers after cleaning up everything related to DrawerLayout in case
// one of the children is a MasterDetailPage (which may dispose of the DrawerLayout).
if (Element != null)
{
foreach (Element element in PageController.InternalChildren)
{
var child = element as VisualElement;
if (child == null)
continue;
IVisualElementRenderer renderer = Android.Platform.GetRenderer(child);
renderer?.Dispose();
}
var navController = (INavigationPageController)Element;
navController.PushRequested -= OnPushed;
navController.PopRequested -= OnPopped;
navController.PopToRootRequested -= OnPoppedToRoot;
navController.InsertPageBeforeRequested -= OnInsertPageBeforeRequested;
navController.RemovePageRequested -= OnRemovePageRequested;
}
Device.Info.PropertyChanged -= DeviceInfoPropertyChanged;
// API only exists on newer android YAY
if ((int)Build.VERSION.SdkInt >= 17)
{
FragmentManager fm = FragmentManager;
if (!fm.IsDestroyed)
{
FragmentTransaction trans = fm.BeginTransactionEx();
foreach (Fragment fragment in _fragmentStack)
trans.RemoveEx(fragment);
trans.CommitAllowingStateLossEx();
fm.ExecutePendingTransactionsEx();
}
}
}
base.Dispose(disposing);
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
PageController.SendAppearing();
// If the Appearing handler changed the application's main page for some reason,
// this page may no longer be part of the hierarchy; if so, we need to skip
// updating the toolbar and pushing the pages to avoid crashing the app
if (!Element.IsAttachedToRoot())
return;
RegisterToolbar();
// If there is already stuff on the stack we need to push it
PushCurrentPages();
UpdateToolbar();
_isAttachedToWindow = true;
}
protected override void OnDetachedFromWindow()
{
base.OnDetachedFromWindow();
PageController.SendDisappearing();
_isAttachedToWindow = false;
}
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
var oldNavController = (INavigationPageController)e.OldElement;
oldNavController.PushRequested -= OnPushed;
oldNavController.PopRequested -= OnPopped;
oldNavController.PopToRootRequested -= OnPoppedToRoot;
oldNavController.InsertPageBeforeRequested -= OnInsertPageBeforeRequested;
oldNavController.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();
var navController = (INavigationPageController)e.NewElement;
navController.PushRequested += OnPushed;
navController.PopRequested += OnPopped;
navController.PopToRootRequested += OnPoppedToRoot;
navController.InsertPageBeforeRequested += OnInsertPageBeforeRequested;
navController.RemovePageRequested += OnRemovePageRequested;
if (_isAttachedToWindow && Element.IsAttachedToRoot())
{
PushCurrentPages();
}
}
}
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();
else if (e.PropertyName == BarHeightProperty.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 (Element.IsSet(BarHeightProperty))
barHeight = Element.OnThisPlatform().GetBarHeight();
if (barHeight != _lastActionBarHeight && _lastActionBarHeight > 0)
{
ResetToolbar();
bar = _toolbar;
}
_lastActionBarHeight = barHeight;
bar.Measure(MeasureSpecFactory.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(barHeight, MeasureSpecMode.Exactly));
var barOffset = ToolbarVisible ? barHeight : 0;
int containerHeight = b - t - ContainerTopPadding - barOffset - ContainerBottomPadding;
PageController.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();
bool toolbarLayoutCompleted = false;
for (var i = 0; i < ChildCount; i++)
{
AView child = GetChildAt(i);
Page childPage = (child as PageContainer)?.Child?.Element as Page;
if (childPage == null)
return;
// We need to base the layout of both the child and the bar on the presence of the NavBar on the child Page itself.
// If we layout the bar based on ToolbarVisible, we get a white bar flashing at the top of the screen.
// If we layout the child based on ToolbarVisible, we get a white bar flashing at the bottom of the screen.
bool childHasNavBar = NavigationPage.GetHasNavigationBar(childPage);
if (childHasNavBar)
{
bar.Layout(0, 0, r - l, barHeight);
child.Layout(0, barHeight + ContainerTopPadding, r, b - ContainerBottomPadding);
}
else
{
bar.Layout(0, -1000, r, barHeight - 1000);
child.Layout(0, ContainerTopPadding, r, b - ContainerBottomPadding);
}
toolbarLayoutCompleted = true;
}
// Making the layout of the toolbar dependant on having a child Page could potentially mean that the toolbar is not laid out.
// We'll do one more check to make sure it isn't missed.
if (!toolbarLayoutCompleted)
{
if (ToolbarVisible)
{
bar.Layout(0, 0, r - l, barHeight);
}
else
{
bar.Layout(0, -1000, r, barHeight - 1000);
}
}
}
protected virtual void SetupPageTransition(FragmentTransaction transaction, bool isPush)
{
if (isPush)
transaction.SetTransitionEx((int)FragmentTransit.FragmentOpen);
else
transaction.SetTransitionEx((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);
if (((Activity)Context).Window.Attributes.Flags.HasFlag(WindowManagerFlags.TranslucentStatus) || ((Activity)Context).Window.Attributes.Flags.HasFlag(WindowManagerFlags.TranslucentNavigation))
{
if (_toolbar.PaddingTop == 0)
_toolbar.SetPadding(0, GetStatusBarHeight(), 0, 0);
return actionBarHeight + GetStatusBarHeight();
}
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();
}
int GetStatusBarHeight()
{
if (_statusbarHeight > 0)
return _statusbarHeight;
int resourceId = Resources.GetIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0)
_statusbarHeight = Resources.GetDimensionPixelSize(resourceId);
return _statusbarHeight;
}
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();
}
public void OnClick(AView v)
{
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();
else if (e.PropertyName == NavigationPage.HasBackButtonProperty.PropertyName)
UpdateToolbar();
else if (e.PropertyName == NavigationPage.TitleIconProperty.PropertyName ||
e.PropertyName == NavigationPage.TitleViewProperty.PropertyName)
UpdateToolbar();
}
#pragma warning disable 1998 // considered for removal
async void DeviceInfoPropertyChanged(object sender, PropertyChangedEventArgs e)
#pragma warning restore 1998
{
if (nameof(Device.Info.CurrentOrientation) == e.PropertyName)
ResetToolbar();
}
protected virtual void OnToolbarItemPropertyChanged(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)
{
if (!_isAttachedToWindow)
PushCurrentPages();
UpdateToolbar();
int index = PageController.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);
}
protected virtual Task<bool> OnPopToRootAsync(Page page, bool animated)
{
return SwitchContentAsync(page, animated, true, true);
}
protected virtual Task<bool> OnPopViewAsync(Page page, bool animated)
{
Page pageToShow = ((INavigationPageController)Element).Peek(1);
if (pageToShow == null)
return Task.FromResult(false);
return SwitchContentAsync(pageToShow, animated, true);
}
protected virtual 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 = null;
while (page != null)
{
if (page is MasterDetailPage)
{
_masterDetailPage = page as MasterDetailPage;
break;
}
page = page.RealParent;
}
if (_masterDetailPage == null)
{
_masterDetailPage = PageController.InternalChildren[0] as MasterDetailPage;
if (_masterDetailPage == null)
return;
}
if (((IMasterDetailPageController)_masterDetailPage).ShouldShowSplitMode)
return;
var renderer = Android.Platform.GetRenderer(_masterDetailPage) as MasterDetailPageRenderer;
if (renderer == null)
return;
_drawerLayout = renderer;
_drawerToggle = new ActionBarDrawerToggle((Activity)context, _drawerLayout, bar, global::Android.Resource.String.Ok, global::Android.Resource.String.Ok)
{
ToolbarNavigationClickListener = new ClickListener(Element)
};
if (_drawerListener != null)
{
_drawerLayout.RemoveDrawerListener(_drawerListener);
}
_drawerListener = new DrawerMultiplexedListener { Listeners = { _drawerToggle, renderer } };
_drawerLayout.AddDrawerListener(_drawerListener);
}
Fragment GetPageFragment(Page page)
{
for (int n = 0; n < _fragmentStack.Count; n++)
{
if ((_fragmentStack[n] as FragmentContainer)?.Page == page)
{
return _fragmentStack[n];
}
}
return null;
}
void RemovePage(Page page)
{
if (!_isAttachedToWindow)
PushCurrentPages();
Fragment fragment = GetPageFragment(page);
if (fragment == null)
{
return;
}
#if DEBUG
// Enables logging of moveToState operations to logcat
FragmentManager.EnableDebugLogging(true);
#endif
// Go ahead and take care of the fragment bookkeeping for the page being removed
FragmentTransaction transaction = FragmentManager.BeginTransactionEx();
transaction.RemoveEx(fragment);
transaction.CommitAllowingStateLossEx();
// And remove the fragment from our own stack
_fragmentStack.Remove(fragment);
Device.StartTimer(TimeSpan.FromMilliseconds(10), () =>
{
UpdateToolbar();
return false;
});
}
void ResetToolbar()
{
AToolbar oldToolbar = _toolbar;
if (_titleViewRenderer != null)
{
Android.Platform.ClearRenderer(_titleViewRenderer.View);
_titleViewRenderer = null;
}
_toolbar.RemoveView(_titleView);
_titleView = null;
_toolbar.RemoveView(_titleIconView);
_titleIconView = null;
_imageSource = null;
_toolbar.RemoveFromParent();
_toolbar.SetNavigationOnClickListener(null);
_toolbar = null;
SetupToolbar();
// if the old toolbar had padding from transluscentflags, set it to the new toolbar
if (oldToolbar.PaddingTop != 0)
_toolbar.SetPadding(0, oldToolbar.PaddingTop, 0, 0);
RegisterToolbar();
UpdateToolbar();
UpdateMenu();
// Preserve old values that can't be replicated by calling methods above
if (_toolbar != null)
_toolbar.Subtitle = oldToolbar.Subtitle;
}
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.SetNavigationOnClickListener(this);
AddView(bar);
_toolbar = bar;
}
Task<bool> SwitchContentAsync(Page page, bool animated, bool removed = false, bool popToRoot = false)
{
if (!Element.IsAttachedToRoot())
return Task.FromResult(false);
var tcs = new TaskCompletionSource<bool>();
Fragment fragment = GetFragment(page, removed, popToRoot);
#if DEBUG
// Enables logging of moveToState operations to logcat
FragmentManager.EnableDebugLogging(true);
#endif
Current?.SendDisappearing();
Current = page;
if (Platform != null)
{
Platform.NavAnimationInProgress = true;
}
FragmentTransaction transaction = FragmentManager.BeginTransactionEx();
if (animated)
SetupPageTransition(transaction, !removed);
var fragmentsToRemove = new List<Fragment>();
if (_fragmentStack.Count == 0)
{
transaction.AddEx(Id, fragment);
_fragmentStack.Add(fragment);
}
else
{
if (removed)
{
// pop only one page, or pop everything to the root
var popPage = true;
while (_fragmentStack.Count > 1 && popPage)
{
Fragment currentToRemove = _fragmentStack.Last();
_fragmentStack.RemoveAt(_fragmentStack.Count - 1);
transaction.HideEx(currentToRemove);
fragmentsToRemove.Add(currentToRemove);
popPage = popToRoot;
}
Fragment toShow = _fragmentStack.Last();
// Execute pending transactions so that we can be sure the fragment list is accurate.
FragmentManager.ExecutePendingTransactionsEx();
if (FragmentManager.Fragments.Contains(toShow))
transaction.ShowEx(toShow);
else
transaction.AddEx(Id, toShow);
}
else
{
// push
Fragment currentToHide = _fragmentStack.Last();
transaction.HideEx(currentToHide);
transaction.AddEx(Id, fragment);
_fragmentStack.Add(fragment);
}
}
// We don't currently support fragment restoration, so we don't need to worry about
// whether the commit loses state
transaction.CommitAllowingStateLossEx();
// 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 && ((INavigationPageController)Element).StackDepth == 2)
AnimateArrowIn();
}
else if (_drawerToggle != null && ((INavigationPageController)Element).StackDepth == 2)
AnimateArrowOut();
AddTransitionTimer(tcs, fragment, FragmentManager, fragmentsToRemove, TransitionDuration, removed);
}
else
AddTransitionTimer(tcs, fragment, FragmentManager, fragmentsToRemove, 1, true);
Context.HideKeyboard(this);
if (Platform != null)
{
Platform.NavAnimationInProgress = false;
}
return tcs.Task;
}
Fragment GetFragment(Page page, bool removed, bool popToRoot)
{
// pop
if (removed)
return _fragmentStack[_fragmentStack.Count - 2];
// pop to root
if (popToRoot)
return _fragmentStack[0];
// push
return FragmentContainer.CreateInstance(page);
}
void ToolbarTrackerOnCollectionChanged(object sender, EventArgs eventArgs)
{
UpdateMenu();
}
void UpdateMenu()
{
if (_disposed)
return;
AToolbar bar = _toolbar;
Context context = Context;
IMenu menu = bar.Menu;
foreach (ToolbarItem item in _toolbarTracker.ToolbarItems)
item.PropertyChanged -= OnToolbarItemPropertyChanged;
menu.Clear();
foreach (ToolbarItem item in _toolbarTracker.ToolbarItems)
{
IMenuItemController controller = item;
item.PropertyChanged += OnToolbarItemPropertyChanged;
if (item.Order == ToolbarItemOrder.Secondary)
{
IMenuItem menuItem = menu.Add(item.Text);
menuItem.SetEnabled(controller.IsEnabled);
menuItem.SetOnMenuItemClickListener(new GenericMenuClickListener(controller.Activate));
}
else
{
IMenuItem menuItem = menu.Add(item.Text);
menuItem.SetEnabled(controller.IsEnabled);
UpdateMenuItemIcon(context, menuItem, item);
menuItem.SetShowAsAction(ShowAsAction.Always);
menuItem.SetOnMenuItemClickListener(new GenericMenuClickListener(controller.Activate));
}
}
}
protected virtual void UpdateMenuItemIcon(Context context, IMenuItem menuItem, ToolbarItem toolBarItem)
{
FileImageSource icon = toolBarItem.Icon;
if (!string.IsNullOrEmpty(icon))
{
Drawable iconDrawable = context.GetFormsDrawable(icon);
if (iconDrawable != null)
{
if (!menuItem.IsEnabled)
{
iconDrawable.Mutate().SetAlpha(DefaultDisabledToolbarAlpha);
}
menuItem.SetIcon(iconDrawable);
iconDrawable.Dispose();
}
}
}
void UpdateToolbar()
{
if (_disposed)
return;
Context context = Context;
var activity = (FormsAppCompatActivity)context;
AToolbar bar = _toolbar;
ActionBarDrawerToggle toggle = _drawerToggle;
if (bar == null)
return;
bool isNavigated = ((INavigationPageController)Element).StackDepth > 1;
bar.NavigationIcon = null;
Page currentPage = Element.CurrentPage;
if (isNavigated)
{
if (toggle != null)
{
toggle.DrawerIndicatorEnabled = false;
toggle.SyncState();
}
if (NavigationPage.GetHasBackButton(currentPage))
{
var icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext);
icon.Progress = 1;
bar.NavigationIcon = icon;
}
}
else
{
if (toggle != null && _masterDetailPage != null)
{
toggle.DrawerIndicatorEnabled = _masterDetailPage.ShouldShowToolbarButton();
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 = currentPage.Title ?? "";
UpdateTitleIcon();
UpdateTitleView();
}
void UpdateTitleIcon()
{
Page currentPage = Element.CurrentPage;
var source = NavigationPage.GetTitleIcon(currentPage);
if (source == null)
{
_toolbar.RemoveView(_titleIconView);
_titleIconView?.Dispose();
_titleIconView = null;
_imageSource = null;
return;
}
if (_titleIconView == null)
{
_titleIconView = new ImageView(Context);
_toolbar.AddView(_titleIconView, 0);
}
UpdateBitmap(source, _imageSource);
_imageSource = source;
}
async void UpdateBitmap(ImageSource source, ImageSource previousSource = null)
{
if (Equals(source, previousSource))
return;
_titleIconView.SetImageResource(global::Android.Resource.Color.Transparent);
Bitmap bitmap = null;
IImageSourceHandler handler;
if (source != null && (handler = Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
{
try
{
bitmap = await handler.LoadImageAsync(source, Context);
}
catch (TaskCanceledException)
{
}
catch (IOException ex)
{
Internals.Log.Warning("Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer", "Error updating bitmap: {0}", ex);
}
}
if (bitmap == null && source is FileImageSource)
_titleIconView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
else
_titleIconView.SetImageBitmap(bitmap);
bitmap?.Dispose();
}
void UpdateTitleView()
{
AToolbar bar = _toolbar;
if (bar == null)
return;
Page currentPage = Element.CurrentPage;
VisualElement titleView = NavigationPage.GetTitleView(currentPage);
if (_titleViewRenderer != null)
{
var reflectableType = _titleViewRenderer as System.Reflection.IReflectableType;
var rendererType = reflectableType != null ? reflectableType.GetTypeInfo().AsType() : _titleViewRenderer.GetType();
if (titleView == null || Registrar.Registered.GetHandlerTypeForObject(titleView) != rendererType)
{
if (_titleView != null)
_titleView.Child = null;
Android.Platform.ClearRenderer(_titleViewRenderer.View);
_titleViewRenderer.Dispose();
_titleViewRenderer = null;
}
}
if (titleView == null)
return;
if (_titleViewRenderer != null)
_titleViewRenderer.SetElement(titleView);
else
{
_titleViewRenderer = Android.Platform.CreateRenderer(titleView, Context);
if (_titleView == null)
{
_titleView = new Container(Context);
bar.AddView(_titleView);
}
_titleView.Child = _titleViewRenderer;
}
Android.Platform.SetRenderer(titleView, _titleViewRenderer);
}
void AddTransitionTimer(TaskCompletionSource<bool> tcs, Fragment fragment, FragmentManager fragmentManager, IReadOnlyCollection<Fragment> fragmentsToRemove, int duration, bool shouldUpdateToolbar)
{
Device.StartTimer(TimeSpan.FromMilliseconds(duration), () =>
{
tcs.TrySetResult(true);
Current?.SendAppearing();
if (shouldUpdateToolbar)
UpdateToolbar();
if (fragmentsToRemove.Count > 0)
{
FragmentTransaction fragmentTransaction = fragmentManager.BeginTransactionEx();
foreach (Fragment frag in fragmentsToRemove)
fragmentTransaction.RemoveEx(frag);
fragmentTransaction.CommitAllowingStateLossEx();
}
return false;
});
}
void PushCurrentPages()
{
if (_fragmentStack.Count > 0)
return;
var navController = (INavigationPageController)Element;
foreach (Page page in navController.Pages)
{
PushViewAsync(page, false);
}
}
class ClickListener : Object, IOnClickListener
{
readonly NavigationPage _element;
public ClickListener(NavigationPage element)
{
_element = element;
}
public void OnClick(AView v)
{
_element?.PopAsync();
}
}
internal class Container : ViewGroup
{
IVisualElementRenderer _child;
public Container(IntPtr p, global::Android.Runtime.JniHandleOwnership o) : base(p, o)
{
// Added default constructor to prevent crash in Dispose
}
public Container(Context context) : base(context)
{
}
public IVisualElementRenderer Child
{
set
{
if (_child != null)
RemoveView(_child.View);
_child = value;
if (value != null)
AddView(value.View);
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
if (_child == null)
return;
_child.UpdateLayout();
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if (_child == null)
{
SetMeasuredDimension(0, 0);
return;
}
VisualElement element = _child.Element;
Context ctx = Context;
var width = (int)ctx.FromPixels(MeasureSpecFactory.GetSize(widthMeasureSpec));
SizeRequest request = _child.Element.Measure(width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(_child.Element, new Rectangle(0, 0, width, request.Request.Height));
int widthSpec = MeasureSpecFactory.MakeMeasureSpec((int)ctx.ToPixels(width), MeasureSpecMode.Exactly);
int heightSpec = MeasureSpecFactory.MakeMeasureSpec((int)ctx.ToPixels(request.Request.Height), MeasureSpecMode.Exactly);
_child.View.Measure(widthMeasureSpec, heightMeasureSpec);
SetMeasuredDimension(widthSpec, heightSpec);
}
}
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);
}
}
}
}