using System; using System.ComponentModel; using System.Threading.Tasks; using Android.App; using Android.Support.V4.Widget; using Android.Views; using AView = Android.Views.View; using AColor = Android.Graphics.Drawables.ColorDrawable; namespace Xamarin.Forms.Platform.Android { public class MasterDetailRenderer : DrawerLayout, IVisualElementRenderer, DrawerLayout.IDrawerListener { //from Android source code const uint DefaultScrimColor = 0x99000000; int _currentLockMode = -1; MasterDetailContainer _detailLayout; bool _isPresentingFromCore; MasterDetailContainer _masterLayout; MasterDetailPage _page; bool _presented; public MasterDetailRenderer() : base(Forms.Context) { } public bool Presented { get { return _presented; } set { if (value == _presented) return; UpdateSplitViewLayout(); _presented = value; if (_page.MasterBehavior == MasterBehavior.Default && _page.ShouldShowSplitMode) return; if (_presented) OpenDrawer(_masterLayout); else CloseDrawer(_masterLayout); } } public void OnDrawerClosed(AView drawerView) { } public void OnDrawerOpened(AView drawerView) { } public void OnDrawerSlide(AView drawerView, float slideOffset) { } public void OnDrawerStateChanged(int newState) { _presented = IsDrawerVisible(_masterLayout); UpdateIsPresented(); } public VisualElement Element { get { return _page; } } public event EventHandler ElementChanged; public SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint) { Measure(widthConstraint, heightConstraint); return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight)); } public void SetElement(VisualElement element) { MasterDetailPage oldElement = _page; _page = element as MasterDetailPage; _detailLayout = new MasterDetailContainer(_page, false, Context) { LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent) }; _masterLayout = new MasterDetailContainer(_page, true, Context) { LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent) { Gravity = (int)GravityFlags.Start } }; AddView(_detailLayout); AddView(_masterLayout); var activity = Context as Activity; activity.ActionBar.SetDisplayShowHomeEnabled(true); activity.ActionBar.SetHomeButtonEnabled(true); UpdateBackgroundColor(_page); UpdateBackgroundImage(_page); OnElementChanged(oldElement, element); if (oldElement != null) oldElement.BackButtonPressed -= OnBackButtonPressed; if (_page != null) _page.BackButtonPressed += OnBackButtonPressed; if (Tracker == null) Tracker = new VisualElementTracker(this); _page.PropertyChanged += HandlePropertyChanged; _page.Appearing += MasterDetailPageAppearing; _page.Disappearing += MasterDetailPageDisappearing; UpdateMaster(); UpdateDetail(); Device.Info.PropertyChanged += DeviceInfoPropertyChanged; SetGestureState(); Presented = _page.IsPresented; SetDrawerListener(this); if (element != null) element.SendViewInitialized(this); if (element != null && !string.IsNullOrEmpty(element.AutomationId)) ContentDescription = element.AutomationId; } public VisualElementTracker Tracker { get; private set; } public void UpdateLayout() { if (Tracker != null) Tracker.UpdateLayout(); } public ViewGroup ViewGroup { get { return this; } } protected override void Dispose(bool disposing) { if (disposing) { 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 (_page != null) { _page.BackButtonPressed -= OnBackButtonPressed; _page.PropertyChanged -= HandlePropertyChanged; _page.Appearing -= MasterDetailPageAppearing; _page.Disappearing -= MasterDetailPageDisappearing; _page.ClearValue(Platform.RendererProperty); _page = null; } } base.Dispose(disposing); } protected override void OnAttachedToWindow() { base.OnAttachedToWindow(); ((Page)Element).SendAppearing(); } protected override void OnDetachedFromWindow() { base.OnDetachedFromWindow(); ((Page)Element).SendDisappearing(); } protected virtual void OnElementChanged(VisualElement oldElement, VisualElement newElement) { EventHandler changed = ElementChanged; if (changed != null) changed(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 (_page.ShouldShowSplitMode && _masterLayout != null) _masterLayout.Right = r; } async void DeviceInfoPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "CurrentOrientation") { if (!_page.ShouldShowSplitMode && Presented) { _page.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(50); CloseDrawer(_masterLayout); } UpdateSplitViewLayout(); } } void HandleMasterPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == Page.TitleProperty.PropertyName || e.PropertyName == Page.IconProperty.PropertyName) ((Platform)_page.Platform).UpdateMasterDetailToggle(true); } void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "Master") UpdateMaster(); else if (e.PropertyName == "Detail") { UpdateDetail(); ((Platform)_page.Platform).UpdateActionBar(); } else if (e.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName) { _isPresentingFromCore = true; Presented = _page.IsPresented; _isPresentingFromCore = false; } else if (e.PropertyName == "IsGestureEnabled") SetGestureState(); else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName) UpdateBackgroundImage(_page); if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) UpdateBackgroundColor(_page); } void MasterDetailPageAppearing(object sender, EventArgs e) { if (_page.Master != null) _page.Master.SendAppearing(); if (_page.Detail != null) _page.Detail.SendAppearing(); } void MasterDetailPageDisappearing(object sender, EventArgs e) { if (_page.Master != null) _page.Master.SendDisappearing(); if (_page.Detail != null) _page.Detail.SendDisappearing(); } void OnBackButtonPressed(object sender, BackButtonPressedEventArgs backButtonPressedEventArgs) { if (IsDrawerOpen((int)GravityFlags.Start)) { if (_currentLockMode != LockModeLockedOpen) { CloseDrawer((int)GravityFlags.Start); backButtonPressedEventArgs.Handled = true; } } } void SetGestureState() { SetDrawerLockMode(_page.IsGestureEnabled ? LockModeUnlocked : LockModeLockedClosed); } void SetLockMode(int lockMode) { if (_currentLockMode != lockMode) { SetDrawerLockMode(lockMode); _currentLockMode = lockMode; } } void UpdateBackgroundColor(Page view) { if (view.BackgroundColor != Color.Default) SetBackgroundColor(view.BackgroundColor.ToAndroid()); } void UpdateBackgroundImage(Page view) { if (!string.IsNullOrEmpty(view.BackgroundImage)) SetBackgroundDrawable(Context.Resources.GetDrawable(view.BackgroundImage)); } void UpdateDetail() { Context.HideKeyboard(this); _detailLayout.ChildView = _page.Detail; } void UpdateIsPresented() { if (_isPresentingFromCore) return; if (Presented != _page.IsPresented) ((IElementController)_page).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, Presented); } void UpdateMaster() { if (_masterLayout != null && _masterLayout.ChildView != null) _masterLayout.ChildView.PropertyChanged -= HandleMasterPropertyChanged; _masterLayout.ChildView = _page.Master; if (_page.Master != null) _page.Master.PropertyChanged += HandleMasterPropertyChanged; } void UpdateSplitViewLayout() { if (Device.Idiom == TargetIdiom.Tablet) { bool isShowingSplit = _page.ShouldShowSplitMode || (_page.ShouldShowSplitMode && _page.MasterBehavior != MasterBehavior.Default && _page.IsPresented); SetLockMode(isShowingSplit ? LockModeLockedOpen : LockModeUnlocked); unchecked { SetScrimColor(isShowingSplit ? Color.Transparent.ToAndroid() : (int)DefaultScrimColor); } ((Platform)_page.Platform).UpdateMasterDetailToggle(); } } } }