1. 程式人生 > >C#使用Xamarin開發可移植移動應用進階篇(7.使用布局渲染器,修改默認布局),附源碼

C#使用Xamarin開發可移植移動應用進階篇(7.使用布局渲染器,修改默認布局),附源碼

頂部 urn hit .com move 應用開發 當前 namespace int

前言

系列目錄

C#使用Xamarin開發可移植移動應用目錄

源碼地址:https://github.com/l2999019/DemoApp

可以Star一下,隨意 - -

說點什麽..

本篇..基本可以算是Xamarin在應用開發過程中的核心了..真的很很很重要..

想學習的..想用的..建議仔細閱讀..嗯..打醬油的 ..快速滑倒下面點個推薦 - - 哈哈哈...

今天的學習內容?

也只講一個,關於Xamarin.Forms針對各個平臺如何進行可定制化的布局操作.

也就是針對某個平臺的細顆粒化操作.

廢話不多說,我們直接開始.

正文

嗯..今天我會拿一個項目中的例子出來講.

說說原因吧,因為在谷歌的安卓開發建議中,是建議類似tab切換操作,是放在頂部的.

然而蘋果則不然,他建議放在底部..這樣就造成了APP上各個平臺對於TabbedPage視圖的渲染差別

如圖:

技術分享

雖然在墻外..大多數的APP都遵循了這個規則,然而在我們特色的社會主義新中國..幾乎所有的APP都是仿蘋果的建議 將Tab標簽放到了下面..

嗯,入鄉隨俗,我們今天就來把這個tab,在安卓中給移到下面.

效果如圖吧:

技術分享

既然要移動到下面,那麽我們肯定需要重寫相關的內容,我們可以找到開源的Xamarin控件BottomNavigationBar

做過安卓的應該都知道,這個是一個安卓中比較流行的控件,嗯..直接被移植到了Xamarin中

我們在安卓的項目下,通過nuget添加這個包如下:

技術分享

然後我們在可移植的項目中,照常編寫我們的TabbedPage頁面如下:

<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:local="clr-namespace:Xamarin.FormsDemo_CHN.Views;assembly=Xamarin.FormsDemo_CHN"
            x:Class="Xamarin.FormsDemo_CHN.Views.MainPage"
BarBackgroundColor="#7EC0EE" BarTextColor="White"> <local:ItemsPage Icon="ic_Messaget"/> <local:AboutPage Icon="ic_Info"/> <local:BaiDuMapPage Icon="ic_Star" /> </TabbedPage>

我們給這個頁面取名叫MainPage,後臺代碼如下:

[XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MainPage : TabbedPage
    {
        public MainPage()
        {
            InitializeComponent();
        }
        protected override void OnCurrentPageChanged()
        {
            base.OnCurrentPageChanged();

            Title = CurrentPage?.Title;
            
        }
    }

啥也不用幹,就重寫一下頁面變更事件,改寫一下title而已,很常見的代碼.

然後我們回到安卓的項目下.

添加一個類,取名為MainPageRenderer,表示是重新渲染MainPage的

編寫渲染特性如下:

[assembly: ExportRenderer(typeof(MainPage), typeof(MainPageRenderer))]

namespace Xamarin.FormsDemo_CHN.Droid
{
    class MainPageRenderer : VisualElementRenderer<MainPage>, IOnTabClickListener

註意,我們這裏繼承了IOnTabClickListener,這個就是第三方的BottomNavigationBar的事件了,待會我們會用到.

在註意:我們這裏因為是重寫布局,所以要繼承VisualElementRenderer

接下來我們直接上MainPageRenderer 的完整代碼,因為內容較多..涉及的方面也比較多.嗯..包含一些安卓方面的重繪之類的.

所以就不一一講解了.大部分都已經寫在了註釋當中.請仔細看

class MainPageRenderer : VisualElementRenderer<MainPage>, IOnTabClickListener
    {

        private BottomBar _bottomBar;

        private Page _currentPage;

        private int _lastSelectedTabIndex = -1;

        public MainPageRenderer()
        {
            // Required to say packager to not to add child pages automatically
            AutoPackage = false;
        }

        /// <summary>
        /// 選中後,加載新的頁面內容
        /// </summary>
        /// <param name="position"></param>
        public void OnTabSelected(int position)
        {
            LoadPageContent(position);
        }

        public void OnTabReSelected(int position)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<MainPage> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null)
            {
                ClearElement(e.OldElement);
            }

            if (e.NewElement != null)
            {
                InitializeElement(e.NewElement);
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ClearElement(Element);
            }

            base.Dispose(disposing);
        }

        /// <summary>
        /// 重寫布局的方法
        /// </summary>
        /// <param name="changed"></param>
        /// <param name="l"></param>
        /// <param name="t"></param>
        /// <param name="r"></param>
        /// <param name="b"></param>
        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            if (Element == null)
            {
                return;
            }

            int width = r - l;
            int height = b - t;

            _bottomBar.Measure(
                MeasureSpec.MakeMeasureSpec(width, MeasureSpecMode.Exactly),
                MeasureSpec.MakeMeasureSpec(height, MeasureSpecMode.AtMost));

            //這裏需要重新測量位置和尺寸,為了重新布置tab菜單的位置 
            _bottomBar.Measure(
                MeasureSpec.MakeMeasureSpec(width, MeasureSpecMode.Exactly),
                MeasureSpec.MakeMeasureSpec(_bottomBar.ItemContainer.MeasuredHeight, MeasureSpecMode.Exactly));

            int barHeight = _bottomBar.ItemContainer.MeasuredHeight;

            _bottomBar.Layout(0, b - barHeight, width, b);

            float density = Resources.DisplayMetrics.Density;

            double contentWidthConstraint = width / density;
            double contentHeightConstraint = (height - barHeight) / density;

            if (_currentPage != null)
            {
                var renderer = Platform.GetRenderer(_currentPage);

                renderer.Element.Measure(contentWidthConstraint, contentHeightConstraint);
                renderer.Element.Layout(new Rectangle(0, 0, contentWidthConstraint, contentHeightConstraint));

                renderer.UpdateLayout();
            }
        }

        /// <summary>
        /// 初始化方法
        /// </summary>
        /// <param name="element"></param>
        private void InitializeElement(MainPage element)
        {
            PopulateChildren(element);
        }
        /// <summary>
        /// 生成新的底部控件
        /// </summary>
        /// <param name="element"></param>
        private void PopulateChildren(MainPage element)
        {
            //我們需要刪除原有的底部控件,然後添加新的
            _bottomBar?.RemoveFromParent();
            
            _bottomBar = CreateBottomBar(element);
            AddView(_bottomBar);

            LoadPageContent(0);
        }


        /// <summary>
        /// 清除舊的底部控件
        /// </summary>
        /// <param name="element"></param>
        private void ClearElement(MainPage element)
        {
            if (_currentPage != null)
            {
                IVisualElementRenderer renderer = Platform.GetRenderer(_currentPage);

                if (renderer != null)
                {
                    renderer.ViewGroup.RemoveFromParent();
                    renderer.ViewGroup.Dispose();
                    renderer.Dispose();

                    _currentPage = null;
                }

                if (_bottomBar != null)
                {
                    _bottomBar.RemoveFromParent();
                    _bottomBar.Dispose();
                    _bottomBar = null;
                }
            }
        }

        /// <summary>
        /// 創建新的底部控件
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private BottomBar CreateBottomBar(MainPage element)
        {
            var bar = new BottomBar(Context);

            // TODO: Configure the bottom bar here according to your needs

            bar.SetOnTabClickListener(this);
            bar.UseFixedMode();

            PopulateBottomBarItems(bar, element.Children);
            var barcolor = element.BarBackgroundColor;
           // Color a = new Color(Convert.ToByte(barcolor.), Convert.ToByte(barcolor.G), Convert.ToByte(barcolor.B), Convert.ToByte(barcolor.A));


            bar.ItemContainer.SetBackgroundColor(barcolor.ToAndroid());
            bar.SetActiveTabColor(Color.White);
            //bar.ItemContainer.
            //bar.ItemContainer.SetBackgroundColor(Color.Red);

            return bar;
        }

        /// <summary>
        /// 查詢原來底部的菜單,並添加到新的控件
        /// </summary>
        /// <param name="bar"></param>
        /// <param name="pages"></param>
        private void PopulateBottomBarItems(BottomBar bar, IEnumerable<Page> pages)
        {
            
            var barItems = pages.Select(x => new BottomBarTab(Context.Resources.GetDrawable(x.Icon), x.Title));

            bar.SetItems(barItems.ToArray());
        }

        /// <summary>
        /// 通過選擇的下標加載Page
        /// </summary>
        /// <param name="position"></param>
        private void LoadPageContent(int position)
        {
            ShowPage(position);
        }

        /// <summary>
        /// 顯示Page的方法
        /// </summary>
        /// <param name="position"></param>
        private void ShowPage(int position)
        {
            if (position != _lastSelectedTabIndex)
            {
                Element.CurrentPage = Element.Children[position];

                if (Element.CurrentPage != null)
                {
                    LoadPageContent(Element.CurrentPage);
                }
            }

            _lastSelectedTabIndex = position;
        }

        /// <summary>
        /// 加載方法
        /// </summary>
        /// <param name="page"></param>
        private void LoadPageContent(Page page)
        {
            UnloadCurrentPage();

            _currentPage = page;

            LoadCurrentPage();

            Element.CurrentPage = _currentPage;
        }

        /// <summary>
        /// 加載當前Page
        /// </summary>
        private void LoadCurrentPage()
        {
            var renderer = Platform.GetRenderer(_currentPage);

            if (renderer == null)
            {
                renderer = Platform.CreateRenderer(_currentPage);
                Platform.SetRenderer(_currentPage, renderer);

               
            }
            else
            {
                var basePage = _currentPage as BaseContentPage;
                basePage?.SendAppearing();
            }
            
            AddView(renderer.ViewGroup);
            renderer.ViewGroup.Visibility = ViewStates.Visible;
          
        }

        /// <summary>
        /// 釋放上一個Page
        /// </summary>
        private void UnloadCurrentPage()
        {
            if (_currentPage != null)
            {
                var basePage = _currentPage as BaseContentPage;
                basePage?.SendDisappearing();
                var renderer = Platform.GetRenderer(_currentPage);

                if (renderer != null)
                {
                    renderer.ViewGroup.Visibility = ViewStates.Invisible;
                    RemoveView(renderer.ViewGroup);
                }
                
            }
        }
    }

這樣,我們就完成了整個tab菜單的替換工作.當然各位還可以根據需要來直接調用BottomNavigationBar的一些動畫效果.其實也是很不錯的.

本篇就到此結束了.

C#使用Xamarin開發可移植移動應用進階篇(7.使用布局渲染器,修改默認布局),附源碼