C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码

前言

系列目录

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的一些动画效果.其实也是很不错的.

本篇就到此结束了.

时间: 2024-08-27 20:00:26

C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码的相关文章

C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码

前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github.com/l2999019/DemoApp 可以Star一下,随意 - - 说点什么.. 嗯,前面讲了那么多,是时候生成一个APK在真机上玩玩了. 今天的学习内容? 也只讲一个,如何打包生成安卓可安装的APK并精简大小. 正文 我记得,之前在写安卓方面的文章的时候,有人就问过我.Xamarin.Android为什么打包出来这么大?随便一个HelloWord就20-30MB? 嗯..今天我们就来解决

使用Xamarin开发手机聊天程序 -- 基础篇(大量图文讲解 step by step,附源码下载)

如果是.NET开发人员,想学习手机应用开发(Android和iOS),Xamarin 无疑是最好的选择,编写一次,即可发布到Android和iOS平台,真是利器中的利器啊!而且,Xamarin已经被微软收购并被大力推广,.NET开发人员将时间投资在Xamarin上,以应对移动开发的热潮,应该是值得的. 好了,废话不多说,就开始吧.本系列文章将详细介绍如何使用Xamarin开发出一个简单的即时通信IM聊天系统(文末有源码下载,可先睹为快),本文作为第一篇基础篇,将着重介绍Xamarin Andro

C#使用Xamarin开发可移植移动应用(3.进阶篇MVVM双向绑定和命令绑定)附源码

前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github.com/l2999019/DemoApp 可以Star一下,随意 - - 说点什么.. 嗯..前面3篇就是基础内容..后面就开始逐渐要加深了,进阶篇开始了. 今天的学习内容? 今天我们讲讲Xamarin中的MVVM双向绑定,嗯..需要有一定的MVVM基础.,具体什么是MVVM - -,请百度,我就不多讲了 效果如下: 正文 1.简单的入门Demo 这个时间的功能很简单,就是一个时间的动态显示.

微信公众平台开发教程(四) 实例入门:机器人(附源码)

微信公众平台开发教程(四) 实例入门:机器人(附源码) 上一篇文章,写了基本框架,可能很多人会觉得晕头转向,这里提供一个简单的例子来予以说明,希望能帮你解开谜团. 一.功能介绍 通过微信公众平台实现在线客服机器人功能.主要的功能包括:简单对话.查询天气等服务. 这里只是提供比较简单的功能,重在通过此实例来说明公众平台的具体研发过程.只是一个简单DEMO,如果需要的话可以在此基础上进行扩展. 当然后续我们还会推出比较复杂的应用实例. 二.具体实现 1.提供访问接口 这里不再赘述,参照上一章,微信公

Web 开发中很实用的10个效果【附源码下载】

在工作中,我们可能会用到各种交互效果.而这些效果在平常翻看文章的时候碰到很多,但是一时半会又想不起来在哪,所以养成知识整理的习惯是很有必要的.这篇文章给大家推荐10个在 Web 开发中很有用的效果,记得收藏:) 超炫的页面切换动画效果 今天我们想与大家分享一组创意的页面切换熊效果集合.我们已经在示例中罗列了一组动画,可以被应用到页面切换过程中,创造出很有趣的导航效果. 立即下载      在线演示 美!视差滚动在图片滑块中的应用 视差滚动(Parallax Scrolling)已经被广泛应用于网

【转载】Web 开发中很实用的10个效果【附源码下载】

超炫的页面切换动画效果 今天我们想与大家分享一组创意的页面切换熊效果集合.我们已经在示例中罗列了一组动画,可以被应用到页面切换过程中,创造出很有趣的导航效果. 立即下载      在线演示 美!视差滚动在图片滑块中的应用 视差滚动(Parallax Scrolling)已经被广泛应用于网页设计中,这种技术能够让原本平面的网页界面产生动感的立体效果.美女很养眼吧 :) 源码下载      在线演示 网页边栏过渡动画 以细微的过渡动画显示一些隐藏的侧边栏,其余的内容也是.通常侧边栏滑入,把其他内容推

【COCOS2D-HTML5 开发之三】示例项目附源码及运行的GIF效果图

本站文章均为李华明Himi原创,转载务必在明显处注明:(作者新浪微博:@李华明Himi) 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/cocos2d-html5/1528.html ? 点击订阅 ? 本博客最新动态!及时将最新博文通知您! Cocos2dx html5开发,对于用过2d Or -x的童鞋来说很容易,Himi这里也没有必要去再跟同学们详细的教学一遍. 所以Himi简单做了一个项目,供给大家参考,源码下载地址及GIF截图在文章最后!

C#/ASP.NET MVC微信公众号接口开发之从零开发(三)回复消息 (附源码)

C#/ASP.NET MVC微信接口开发文章目录: 1.C#/ASP.NET MVC微信公众号接口开发之从零开发(一) 接入微信公众平台 2.C#/ASP.NET MVC微信公众号接口开发之从零开发(二) 接收微信消息并且解析XML(附源码) 一.拼凑回复的XML字符串 微信被动回复的形式有一下六种: 1 回复文本消息 2 回复图片消息 3 回复语音消息 4 回复视频消息 5 回复音乐消息 6 回复图文消息 分别对应不同的XML形式,这里以文本消息和图文为例,读者举一反三其他的类似,不再赘述:

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载) POSTED ON 2014年6月27日 BY 天边的星星 本文内容: 1.横向ListView的所有实现思路; 2.其中一个最通用的思路HorizontalListView,并基于横向ListView开发一个简单的相册: 3.实现的横向ListView在点击.浏览时item背景会变色,并解决了listview里setSelected造成item的选择状态混乱的问题.