App中一般都会有一个底部tab,用于切换不同的功能,在Xamarin中应该制作底部tab了,需要把Android的TabbedPage做一次渲染,IOS的则不用,接下来说下详细步骤:

1、在共享项目代码中添加MainPage,继承自TabbedPage,然后再添加几个测试的切换页面(HomePage、FunctionPage、AccountPage)

在MainPage.xaml页面中添加如下代码:

<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Mobile.Views.MainPage"
xmlns:local="clr-namespace:Mobile.Views"> <local:HomePage Icon="menu_home"/>
<local:FunctionPage Icon="menu_function"/>
<local:AccountPage Icon="menu_account"/> </TabbedPage>

2、在Android项目中添加BottomNavigationBar引用

3、在Android项目中添加渲染类 MainPageRenderer

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using BottomNavigationBar;
using BottomNavigationBar.Listeners;
using Mobile.Controls;
using Mobile.Droid.Renderers;
using Mobile.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android; [assembly: ExportRenderer(typeof(MainPage), typeof(MainPageRenderer))] namespace Mobile.Droid.Renderers
{
/// <summary>
/// MainPage渲染器
/// </summary>
class MainPageRenderer : VisualElementRenderer<MainPage>, IOnTabClickListener
{ private BottomBar _bottomBar; private Page _currentPage; private int _lastSelectedTabIndex = -; public MainPageRenderer()
{
//不添加子页面
AutoPackage = false;
}
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(, 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(, , 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();
} /// <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);
} }
}
}
}

注意上面代码中的MainPage是我们在共享项目中添加MainPage,需要引入相应的命名空间,然后还需要在共享项目中添加BaseContentPage类,

public class BaseContentPage : ContentPage
{
public void SendAppearing()
{
OnAppearing();
} public void SendDisappearing()
{
OnDisappearing();
}
}

Xamarin.Forms 学习系列之底部tab的更多相关文章

  1. Xamarin.Forms学习系列之Android集成极光推送

    一般App都会有消息推送的功能,如果是原生安卓或者IOS集成消息推送很容易,各大推送平台都有相关的Sample,但是关于Xamarin.Forms的消息推送集成的资料非常少,下面就说下Xamarin. ...

  2. Xamarin.Forms 学习系列之优秀UI收集

    1.各种优秀UI例子 https://github.com/jsuarezruiz/xamarin-forms-goodlooking-UI 输入框例子 https://github.com/enis ...

  3. Xamarin.Forms学习系列之Syncfusion 制作图形报表

    Syncfusion是一家微软生态下的第三方组件/控件供应商,除了用于HTML5和JavaScript的控件外,他们产品还涉及如下领域: WEB ASP.NET MVC ASP.NET WebForm ...

  4. Xamarin.Forms学习系列之SQLite

    在App中我们通常不会实时获取服务器数据,会在用户手机中保存历史数据,这个时候就需要用到数据库SQLite,由于微软的封装,在Xamarin中操作SQLite非常简单,类似EF的操作. 1.我们需要在 ...

  5. Xamarin.Forms学习之Platform-specific API和文件操作

    这篇文章的分享原由是由于上篇关于Properties的保存不了,调用SavePropertiesAsync()方法也不行,所以我希望通过操作文件的方式保存我的需要的数据,然后我看了一下电子书中的第二十 ...

  6. Xamarin.Forms学习之Page Navigation(一)

    在最初接触Xamarin.Forms的时候,我是跟着Xamarin官方的名为“learning-xamarin-ebook”的pdf文档进行学习的,我在成功运行Hello world程序之后,我开始跟 ...

  7. Xamarin.Forms学习之XAML命名空间

    大家好,我又悄咪咪的来了,在上一篇的Xamarin文章中简单介绍了Xamarin的安装过程,妈蛋没想到很多小朋友很感激我,让他们成功的安装了Xamarin,然后......成功的显示了经典的两个单词( ...

  8. Xamarin.Forms学习之初

    微软的Build 2016结束的有段时间了,对于一个简单的小屌丝程序员--我来说,关注最大的无疑是Xamarin的免费(开源什么的让大神们上吧),内心激动啊.大会结束的周末我就迫不及待的安装了,然后. ...

  9. Xamarin.Forms学习之位图(二)

    上篇文章分享了如何加载网络图片和PCL中的图片,所以今天继续分享关于如何加载平台的内嵌图片,在这之前说一下上篇文章开头的一个问题:为什么不能加载UWP项目,这是因为我升级了UWP的SDK,而Xamar ...

随机推荐

  1. Springboot与任务整合(四)

    一 异步任务 启动类 @MapperScan("com.topcheer.*.*.dao") @SpringBootApplication @EnableCaching @Enab ...

  2. Elastic search集群新增节点(同一个集群,同一台物理机,基于ES 7.4)

    一开始,在电脑上同一个集群新增节点(node)怎么试也不成功,官网guide又语焉不详?集群健康值yellow(表示主分片全部可用,部分复制分片不可用) 最后,在stackoverflow上找到了答案 ...

  3. [考试反思]0825NOIP模拟测试31:喘息

    好吧,我又活了 大脸又. 240,220,210,200,200... T1是个不会证明的傻子找规律算上看了一遍三道题之后一共20分钟搞定. skyh打的是错的可是成功qj全部测试点得到AC(会被手模 ...

  4. Weed:线段树

    观察复杂度,是log级别以下回答询问的. O(1)?逗我kx呢? 自然而然地想到线段树. 学长讲的原题啊考场上还不会打. 线段树上的每个节点都表示一个操作区间. 线段树上维护的权值有3个:这个子区间一 ...

  5. Java高性能反射工具包ReflectASM

    ReflectASM 使用字节码生成的方式实现了更为高效的反射机制.执行时会生成一个存取类来 set/get 字段,访问方法或创建实例.一看到 ASM 就能领悟到 ReflectASM 会用字节码生成 ...

  6. python多线程总结

    概述 进程与线程 进程:进程是资源(CPU.内存等)分配的最小单位,进程有独立的地址空间与系统资源,一个进程可以包含一个或多个线程 线程:线程是CPU调度的最小单位,是进程的一个执行流,线程依赖于进程 ...

  7. 使用Git-Rebase合并多次提交

    在平时的软件开发中,我们每个team使用一个公共仓库(这里说的是Git仓库).每当有一个新的需求,我们会拉出一个特性分支,然后在这个特性分支上做开发以及提交个人的代码. 我有个习惯就是:为了确保代码的 ...

  8. 类型擦除真的能完全擦除一切信息吗?java 泛型揭秘

    背景 我们都知道泛型本质上是提供类型的"类型参数",它们也被称为参数化类型(parameterized type)或参量多态(parametric polymorphism).其实 ...

  9. [Bootstrap] Bootstrap学习笔记

    1.因为bootstrap用到了html5的特性,为了正常使用,需要在最开头加上<!DOCTYPE html> 2.Bootstrap需要JQuery才能正常工作,所以需要导入jquery ...

  10. 资深架构师Sum的故事:(Mysql)InnoDB下,存储过程中事务的处理

    | 故事背景 话说有一回,X市X公司的产品经理Douni兴致冲冲的跑来和Sum(Sum,X市X公司资历8年程序猿,技能:深思.熟虑.心细.深究.技术过敏.口头禅:嗯,容我想想.坚信:只要赚钱的业务,我 ...