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, 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 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(); } 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(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)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)); } } } }