Xamarin.Forms - 摇篮工厂
Xamarin.Forms - Cradle FAB
我想要浮动操作按钮。按钮必须是动态的(它将是动画的,并且底座必须像从 material.io 拍摄的视频中那样适应它)。它必须与 as iOS as Android 一起使用。 XAML 示例,如果可以的话。也许是自定义渲染器。
我知道 Android Studio 和 Flutter 默认存在此功能。
如果我的问题有误请写信给我,对不起我的英语。
您将不得不亲自动手编写代码。我的解决方案适用于 Android,我也很想知道它在 iOS 中是如何完成的。
我依赖 nuget 包 Xamarin.Forms.Visual.Material. A great article that got me started can be found here and also here. Also checkout Google's Material design here.
在您的 Android 项目中,创建一个包含以下内容的 BottomTabLayout.xml 文件:
<?xml version="1.0" encoding="utf-8" ?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- Note: A RecyclerView can also be used -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="100dp"
android:clipToPadding="false">
<!-- Scrollable content -->
<FrameLayout
android:id="@+id/bottomtab.navarea"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="fill"
android:layout_weight="1" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:contentInsetRight="0dp"
app:contentInsetEnd="0dp"
app:fabCradleMargin="10dp">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomtab.tabbar"
android:theme="@style/Widget.Design.BottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:layout_marginEnd="0dp"
android:background="@android:color/transparent"/>
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fab"
app:layout_anchor="@id/bottomAppBar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
并且在 style.xml 文件中,如下修改其内容以使用任何 Material 主题:
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MainTheme" parent="Theme.MaterialComponents.Light">
<!-- As of Xamarin.Forms 4.6 the theme has moved into the Forms binary -->
<!-- If you want to override anything you can do that here. -->
<!-- Underneath are a couple of entries to get you started. -->
<!-- Set theme colors from https://aka.ms/material-colors -->
<!-- colorPrimary is used for the default action bar background -->
<!--<item name="colorPrimary">#2196F3</item>-->
<!-- colorPrimaryDark is used for the status bar -->
<!--<item name="colorPrimaryDark">#1976D2</item>-->
<!-- colorAccent is used as the default value for colorControlActivated
which is used to tint widgets -->
<!--<item name="colorAccent">#FF4081</item>-->
<item name="android:windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>
创建一个名为“Renderers”的文件夹并添加以下内容类:
BottomNavigationViewUtils2.cs(处理动态菜单生成)
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using FabTabBarDemo.Droid.Extensions;
using Google.Android.Material.BottomNavigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace FabTabBarDemo.Droid.Renderers
{
internal class BottomNavigationViewUtils2
{
internal const int MoreTabId = 99;
internal static void UpdateEnabled(bool tabEnabled, IMenuItem menuItem)
{
if (menuItem.IsEnabled != tabEnabled)
menuItem.SetEnabled(tabEnabled);
}
internal static async void SetupMenu(IMenu menu, int maxBottomItems,
List<(string title, ImageSource icon, bool tabEnabled)> items,
int currentIndex, BottomNavigationView bottomView, Context context, bool hasFab = false)
{
menu.Clear();
int numberOfMenuItems = items.Count;
bool showMore = numberOfMenuItems > maxBottomItems;
int end = showMore ? maxBottomItems - 1 : numberOfMenuItems;
List<IMenuItem> menuItems = new List<IMenuItem>();
List<Task> loadTasks = new List<Task>();
for (int i = 0; i < end; i++)
{
var item = items[i];
using var title = new Java.Lang.String(item.title);
var menuItem = menu.Add(0, i, 0, title);
menuItems.Add(menuItem);
loadTasks.Add(SetMenuItemIcon(menuItem, item.icon, context));
UpdateEnabled(item.tabEnabled, menuItem);
if (i == currentIndex)
{
menuItem.SetChecked(true);
bottomView.SelectedItemId = i;
}
if (hasFab)
{
int redundantMenus = CanAddRedundantMenu(numberOfMenuItems, i);
for (int x = 0; x < redundantMenus; x++)
{
menuItem = menu.Add(0, x + 20, 0, "");
UpdateEnabled(false, menuItem);
}
}
}
if (showMore)
{
var moreString = context.Resources.GetText(Resource.String.overflow_tab_title);
var menuItem = menu.Add(0, MoreTabId, 0, moreString);
menuItems.Add(menuItem);
menuItem.SetIcon(Resource.Drawable.abc_ic_menu_overflow_material);
if (currentIndex >= maxBottomItems - 1)
menuItem.SetChecked(true);
}
bottomView.SetShiftMode(false, false);
if (loadTasks.Count > 0)
await Task.WhenAll(loadTasks);
foreach (var menuItem in menuItems)
menuItem.Dispose();
}
/// <summary>
/// Gets a value that determines whether a redundant menu can be added to
/// the <see cref="BottomNavigationView"/>.
/// </summary>
/// <param name="menuCount">The total menu entries in the view.</param>
/// <param name="currentIndex">The zero-based menu index.</param>
/// <returns>The total number of redundant menus to add or -1.</returns>
internal static int CanAddRedundantMenu(int menuCount, int currentIndex)
{
return menuCount switch
{
1 => currentIndex == 0 ? 2 : -1,
3 => currentIndex == 1 ? 2 : -1,
2 => currentIndex == 0 ? 1 : -1,
4 => currentIndex == 1 ? 1 : -1,
_ => -1,
};
}
static async Task SetMenuItemIcon(IMenuItem menuItem, ImageSource source, Context context)
{
if (source == null)
return;
var drawable = await source.GetDrawableAsync(context);
//var drawable = await context.GetFormsDrawableAsync(source);
menuItem.SetIcon(drawable);
drawable?.Dispose();
}
}
}
FabBottomNavViewAppearanceTracker.cs(管理 BottomNavigationView 渲染)
using Google.Android.Material.BottomNavigation;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace FabTabBarDemo.Droid.Renderers
{
public class FabBottomNavViewAppearanceTracker : ShellBottomNavViewAppearanceTracker
{
public FabBottomNavViewAppearanceTracker(IShellContext shellContext, ShellItem shellItem) : base(shellContext, shellItem)
{
}
protected override void SetBackgroundColor(BottomNavigationView bottomView, Color color)
{
// Prevent the default xamarin forms background rendering of BottomNavigationView
}
}
}
FabShellItemRenderer.cs(管理 shell 选项卡和处理渲染)
using Android.Content.Res;
using Android.OS;
using Android.Views;
using Android.Widget;
using Google.Android.Material.BottomAppBar;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.FloatingActionButton;
using System.Collections.Generic;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace FabTabBarDemo.Droid.Renderers
{
public class FabShellItemRenderer : ShellItemRenderer
{
private readonly FieldInfo navigationAreaField;
private readonly FieldInfo bottomViewField;
BottomAppBar _bottomAppBar;
FloatingActionButton _fab;
BottomNavigationView _bottomView;
IShellBottomNavViewAppearanceTracker appearanceTracker;
public FabShellItemRenderer(IShellContext shellContext) : base(shellContext)
{
var baseType = typeof(ShellItemRenderer);
navigationAreaField = baseType.GetField("_navigationArea", BindingFlags.Instance | BindingFlags.NonPublic);
bottomViewField = baseType.GetField("_bottomView", BindingFlags.Instance | BindingFlags.NonPublic);
}
protected BottomNavigationView BottomView => (BottomNavigationView)bottomViewField.GetValue(this);
public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var layout = inflater.Inflate(Resource.Layout.BottomTabLayout, null);
var _navigationArea = layout.FindViewById<FrameLayout>(Resource.Id.bottomtab_navarea);
_bottomAppBar = layout.FindViewById<BottomAppBar>(Resource.Id.bottomAppBar);
_fab = layout.FindViewById<FloatingActionButton>(Resource.Id.fab);
_bottomView = layout.FindViewById<BottomNavigationView>(Resource.Id.bottomtab_tabbar);
_bottomView.Background = null;
_bottomView.SetOnNavigationItemSelectedListener(this);
navigationAreaField.SetValue(this, _navigationArea);
bottomViewField.SetValue(this, _bottomView);
HookEvents(ShellItem);
SetupMenu();
appearanceTracker = ShellContext.CreateBottomNavViewAppearanceTracker(ShellItem);
((IShellController)ShellContext.Shell).AddAppearanceObserver(this, ShellItem);
return layout;
}
void SetupMenu()
{
using var menu = _bottomView.Menu;
SetupMenu(menu, _bottomView.MaxItemCount, ShellItem);
}
protected override void ResetAppearance() => appearanceTracker?.ResetAppearance(BottomView);
protected override void SetAppearance(ShellAppearance appearance)
{
IShellAppearanceElement controller = appearance;
var backgroundColor = controller.EffectiveTabBarBackgroundColor.ToAndroid();
_bottomAppBar.BackgroundTintList = ColorStateList.ValueOf(backgroundColor);
_fab.BackgroundTintList = ColorStateList.ValueOf(backgroundColor);
appearanceTracker?.SetAppearance(BottomView, appearance);
}
protected override void SetupMenu(IMenu menu, int maxBottomItems, ShellItem shellItem)
{
var _bottomView = BottomView;
_bottomView.SetBackgroundColor(Android.Graphics.Color.Transparent);
_bottomView.Background = null;
var currentIndex = ((IShellItemController)ShellItem).GetItems().IndexOf(ShellSection);
var items = CreateTabList(shellItem);
BottomNavigationViewUtils2.SetupMenu(menu, maxBottomItems, items, currentIndex, _bottomView, Context, true);
UpdateTabBarVisibility();
}
List<(string title, ImageSource icon, bool tabEnabled)> CreateTabList(ShellItem shellItem)
{
var items = new List<(string title, ImageSource icon, bool tabEnabled)>();
var shellItems = ((IShellItemController)shellItem).GetItems();
for (int i = 0; i < shellItems.Count; i++)
{
var item = shellItems[i];
items.Add((item.Title, item.Icon, item.IsEnabled));
}
return items;
}
}
}
FabShellRenderer.cs(Shell 项目渲染器)
using Android.Content;
using FabTabBarDemo.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Shell), typeof(FabShellRenderer))]
namespace FabTabBarDemo.Droid.Renderers
{
public class FabShellRenderer : ShellRenderer
{
public FabShellRenderer(Context context) : base(context)
{
}
protected override IShellItemRenderer CreateShellItemRenderer(ShellItem shellItem)
{
return new FabShellItemRenderer(this);
}
protected override IShellBottomNavViewAppearanceTracker CreateBottomNavViewAppearanceTracker(ShellItem shellItem)
{
return new FabBottomNavViewAppearanceTracker(this, shellItem);
}
}
}
最后……
ImageSourceExtensions.cs(一些基本的 ImageSource 逻辑)
using Android.Content;
using Android.Graphics.Drawables;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace FabTabBarDemo.Droid.Extensions
{
internal static class ImageSourceExtensions
{
public static IImageSourceHandler GetHandler(this ImageSource imageSource)
{
if (imageSource is UriImageSource)
return new ImageLoaderSourceHandler();
else if (imageSource is FileImageSource)
return new FileImageSourceHandler();
else if (imageSource is StreamImageSource)
return new StreamImagesourceHandler();
else if (imageSource is FontImageSource)
return new FontImageSourceHandler();
return null;
}
public static async Task<Drawable> GetDrawableAsync(this ImageSource imageSource, Context context)
{
var imageHandler = imageSource.GetHandler();
if (imageHandler == null)
return null;
var bitmap = await imageHandler.LoadImageAsync(imageSource, context);
return new BitmapDrawable(context.Resources, bitmap);
}
}
}
在您的 Xamarin 共享项目(.Net Standard with Shell)中,根据需要使用 TabBar。下面是一个例子:
<TabBar>
<ShellContent Title="Home" Route="HomePage" ContentTemplate="{DataTemplate local:HomePage}">
<ShellContent.Icon>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FaSolidIcons.Home}" />
</ShellContent.Icon>
</ShellContent>
<ShellContent Title="Search" Route="SearchPage" ContentTemplate="{DataTemplate local:SearchPage}">
<ShellContent.Icon>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FaSolidIcons.Search}" />
</ShellContent.Icon>
</ShellContent>
<ShellContent Title="Profile" Route="ProfilePage" ContentTemplate="{DataTemplate local:ProfilePage}">
<ShellContent.Icon>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FaSolidIcons.User}" />
</ShellContent.Icon>
</ShellContent>
<ShellContent Title="Settings" Route="SettingsPage" ContentTemplate="{DataTemplate local:SettingsPage}">
<ShellContent.Icon>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FaSolidIcons.Cog}" />
</ShellContent.Icon>
</ShellContent>
</TabBar>
就是这样。结果如下图:
注意:FAB 的主题和图标很容易完成,这取决于任何感兴趣的人。编码愉快!
我想要浮动操作按钮。按钮必须是动态的(它将是动画的,并且底座必须像从 material.io 拍摄的视频中那样适应它)。它必须与 as iOS as Android 一起使用。 XAML 示例,如果可以的话。也许是自定义渲染器。
我知道 Android Studio 和 Flutter 默认存在此功能。
如果我的问题有误请写信给我,对不起我的英语。
您将不得不亲自动手编写代码。我的解决方案适用于 Android,我也很想知道它在 iOS 中是如何完成的。 我依赖 nuget 包 Xamarin.Forms.Visual.Material. A great article that got me started can be found here and also here. Also checkout Google's Material design here.
在您的 Android 项目中,创建一个包含以下内容的 BottomTabLayout.xml 文件:
<?xml version="1.0" encoding="utf-8" ?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- Note: A RecyclerView can also be used -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="100dp"
android:clipToPadding="false">
<!-- Scrollable content -->
<FrameLayout
android:id="@+id/bottomtab.navarea"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="fill"
android:layout_weight="1" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:contentInsetRight="0dp"
app:contentInsetEnd="0dp"
app:fabCradleMargin="10dp">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomtab.tabbar"
android:theme="@style/Widget.Design.BottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:layout_marginEnd="0dp"
android:background="@android:color/transparent"/>
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fab"
app:layout_anchor="@id/bottomAppBar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
并且在 style.xml 文件中,如下修改其内容以使用任何 Material 主题:
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MainTheme" parent="Theme.MaterialComponents.Light">
<!-- As of Xamarin.Forms 4.6 the theme has moved into the Forms binary -->
<!-- If you want to override anything you can do that here. -->
<!-- Underneath are a couple of entries to get you started. -->
<!-- Set theme colors from https://aka.ms/material-colors -->
<!-- colorPrimary is used for the default action bar background -->
<!--<item name="colorPrimary">#2196F3</item>-->
<!-- colorPrimaryDark is used for the status bar -->
<!--<item name="colorPrimaryDark">#1976D2</item>-->
<!-- colorAccent is used as the default value for colorControlActivated
which is used to tint widgets -->
<!--<item name="colorAccent">#FF4081</item>-->
<item name="android:windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>
创建一个名为“Renderers”的文件夹并添加以下内容类:
BottomNavigationViewUtils2.cs(处理动态菜单生成)
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using FabTabBarDemo.Droid.Extensions;
using Google.Android.Material.BottomNavigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace FabTabBarDemo.Droid.Renderers
{
internal class BottomNavigationViewUtils2
{
internal const int MoreTabId = 99;
internal static void UpdateEnabled(bool tabEnabled, IMenuItem menuItem)
{
if (menuItem.IsEnabled != tabEnabled)
menuItem.SetEnabled(tabEnabled);
}
internal static async void SetupMenu(IMenu menu, int maxBottomItems,
List<(string title, ImageSource icon, bool tabEnabled)> items,
int currentIndex, BottomNavigationView bottomView, Context context, bool hasFab = false)
{
menu.Clear();
int numberOfMenuItems = items.Count;
bool showMore = numberOfMenuItems > maxBottomItems;
int end = showMore ? maxBottomItems - 1 : numberOfMenuItems;
List<IMenuItem> menuItems = new List<IMenuItem>();
List<Task> loadTasks = new List<Task>();
for (int i = 0; i < end; i++)
{
var item = items[i];
using var title = new Java.Lang.String(item.title);
var menuItem = menu.Add(0, i, 0, title);
menuItems.Add(menuItem);
loadTasks.Add(SetMenuItemIcon(menuItem, item.icon, context));
UpdateEnabled(item.tabEnabled, menuItem);
if (i == currentIndex)
{
menuItem.SetChecked(true);
bottomView.SelectedItemId = i;
}
if (hasFab)
{
int redundantMenus = CanAddRedundantMenu(numberOfMenuItems, i);
for (int x = 0; x < redundantMenus; x++)
{
menuItem = menu.Add(0, x + 20, 0, "");
UpdateEnabled(false, menuItem);
}
}
}
if (showMore)
{
var moreString = context.Resources.GetText(Resource.String.overflow_tab_title);
var menuItem = menu.Add(0, MoreTabId, 0, moreString);
menuItems.Add(menuItem);
menuItem.SetIcon(Resource.Drawable.abc_ic_menu_overflow_material);
if (currentIndex >= maxBottomItems - 1)
menuItem.SetChecked(true);
}
bottomView.SetShiftMode(false, false);
if (loadTasks.Count > 0)
await Task.WhenAll(loadTasks);
foreach (var menuItem in menuItems)
menuItem.Dispose();
}
/// <summary>
/// Gets a value that determines whether a redundant menu can be added to
/// the <see cref="BottomNavigationView"/>.
/// </summary>
/// <param name="menuCount">The total menu entries in the view.</param>
/// <param name="currentIndex">The zero-based menu index.</param>
/// <returns>The total number of redundant menus to add or -1.</returns>
internal static int CanAddRedundantMenu(int menuCount, int currentIndex)
{
return menuCount switch
{
1 => currentIndex == 0 ? 2 : -1,
3 => currentIndex == 1 ? 2 : -1,
2 => currentIndex == 0 ? 1 : -1,
4 => currentIndex == 1 ? 1 : -1,
_ => -1,
};
}
static async Task SetMenuItemIcon(IMenuItem menuItem, ImageSource source, Context context)
{
if (source == null)
return;
var drawable = await source.GetDrawableAsync(context);
//var drawable = await context.GetFormsDrawableAsync(source);
menuItem.SetIcon(drawable);
drawable?.Dispose();
}
}
}
FabBottomNavViewAppearanceTracker.cs(管理 BottomNavigationView 渲染)
using Google.Android.Material.BottomNavigation;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace FabTabBarDemo.Droid.Renderers
{
public class FabBottomNavViewAppearanceTracker : ShellBottomNavViewAppearanceTracker
{
public FabBottomNavViewAppearanceTracker(IShellContext shellContext, ShellItem shellItem) : base(shellContext, shellItem)
{
}
protected override void SetBackgroundColor(BottomNavigationView bottomView, Color color)
{
// Prevent the default xamarin forms background rendering of BottomNavigationView
}
}
}
FabShellItemRenderer.cs(管理 shell 选项卡和处理渲染)
using Android.Content.Res;
using Android.OS;
using Android.Views;
using Android.Widget;
using Google.Android.Material.BottomAppBar;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.FloatingActionButton;
using System.Collections.Generic;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace FabTabBarDemo.Droid.Renderers
{
public class FabShellItemRenderer : ShellItemRenderer
{
private readonly FieldInfo navigationAreaField;
private readonly FieldInfo bottomViewField;
BottomAppBar _bottomAppBar;
FloatingActionButton _fab;
BottomNavigationView _bottomView;
IShellBottomNavViewAppearanceTracker appearanceTracker;
public FabShellItemRenderer(IShellContext shellContext) : base(shellContext)
{
var baseType = typeof(ShellItemRenderer);
navigationAreaField = baseType.GetField("_navigationArea", BindingFlags.Instance | BindingFlags.NonPublic);
bottomViewField = baseType.GetField("_bottomView", BindingFlags.Instance | BindingFlags.NonPublic);
}
protected BottomNavigationView BottomView => (BottomNavigationView)bottomViewField.GetValue(this);
public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var layout = inflater.Inflate(Resource.Layout.BottomTabLayout, null);
var _navigationArea = layout.FindViewById<FrameLayout>(Resource.Id.bottomtab_navarea);
_bottomAppBar = layout.FindViewById<BottomAppBar>(Resource.Id.bottomAppBar);
_fab = layout.FindViewById<FloatingActionButton>(Resource.Id.fab);
_bottomView = layout.FindViewById<BottomNavigationView>(Resource.Id.bottomtab_tabbar);
_bottomView.Background = null;
_bottomView.SetOnNavigationItemSelectedListener(this);
navigationAreaField.SetValue(this, _navigationArea);
bottomViewField.SetValue(this, _bottomView);
HookEvents(ShellItem);
SetupMenu();
appearanceTracker = ShellContext.CreateBottomNavViewAppearanceTracker(ShellItem);
((IShellController)ShellContext.Shell).AddAppearanceObserver(this, ShellItem);
return layout;
}
void SetupMenu()
{
using var menu = _bottomView.Menu;
SetupMenu(menu, _bottomView.MaxItemCount, ShellItem);
}
protected override void ResetAppearance() => appearanceTracker?.ResetAppearance(BottomView);
protected override void SetAppearance(ShellAppearance appearance)
{
IShellAppearanceElement controller = appearance;
var backgroundColor = controller.EffectiveTabBarBackgroundColor.ToAndroid();
_bottomAppBar.BackgroundTintList = ColorStateList.ValueOf(backgroundColor);
_fab.BackgroundTintList = ColorStateList.ValueOf(backgroundColor);
appearanceTracker?.SetAppearance(BottomView, appearance);
}
protected override void SetupMenu(IMenu menu, int maxBottomItems, ShellItem shellItem)
{
var _bottomView = BottomView;
_bottomView.SetBackgroundColor(Android.Graphics.Color.Transparent);
_bottomView.Background = null;
var currentIndex = ((IShellItemController)ShellItem).GetItems().IndexOf(ShellSection);
var items = CreateTabList(shellItem);
BottomNavigationViewUtils2.SetupMenu(menu, maxBottomItems, items, currentIndex, _bottomView, Context, true);
UpdateTabBarVisibility();
}
List<(string title, ImageSource icon, bool tabEnabled)> CreateTabList(ShellItem shellItem)
{
var items = new List<(string title, ImageSource icon, bool tabEnabled)>();
var shellItems = ((IShellItemController)shellItem).GetItems();
for (int i = 0; i < shellItems.Count; i++)
{
var item = shellItems[i];
items.Add((item.Title, item.Icon, item.IsEnabled));
}
return items;
}
}
}
FabShellRenderer.cs(Shell 项目渲染器)
using Android.Content;
using FabTabBarDemo.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Shell), typeof(FabShellRenderer))]
namespace FabTabBarDemo.Droid.Renderers
{
public class FabShellRenderer : ShellRenderer
{
public FabShellRenderer(Context context) : base(context)
{
}
protected override IShellItemRenderer CreateShellItemRenderer(ShellItem shellItem)
{
return new FabShellItemRenderer(this);
}
protected override IShellBottomNavViewAppearanceTracker CreateBottomNavViewAppearanceTracker(ShellItem shellItem)
{
return new FabBottomNavViewAppearanceTracker(this, shellItem);
}
}
}
最后…… ImageSourceExtensions.cs(一些基本的 ImageSource 逻辑)
using Android.Content;
using Android.Graphics.Drawables;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace FabTabBarDemo.Droid.Extensions
{
internal static class ImageSourceExtensions
{
public static IImageSourceHandler GetHandler(this ImageSource imageSource)
{
if (imageSource is UriImageSource)
return new ImageLoaderSourceHandler();
else if (imageSource is FileImageSource)
return new FileImageSourceHandler();
else if (imageSource is StreamImageSource)
return new StreamImagesourceHandler();
else if (imageSource is FontImageSource)
return new FontImageSourceHandler();
return null;
}
public static async Task<Drawable> GetDrawableAsync(this ImageSource imageSource, Context context)
{
var imageHandler = imageSource.GetHandler();
if (imageHandler == null)
return null;
var bitmap = await imageHandler.LoadImageAsync(imageSource, context);
return new BitmapDrawable(context.Resources, bitmap);
}
}
}
在您的 Xamarin 共享项目(.Net Standard with Shell)中,根据需要使用 TabBar。下面是一个例子:
<TabBar>
<ShellContent Title="Home" Route="HomePage" ContentTemplate="{DataTemplate local:HomePage}">
<ShellContent.Icon>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FaSolidIcons.Home}" />
</ShellContent.Icon>
</ShellContent>
<ShellContent Title="Search" Route="SearchPage" ContentTemplate="{DataTemplate local:SearchPage}">
<ShellContent.Icon>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FaSolidIcons.Search}" />
</ShellContent.Icon>
</ShellContent>
<ShellContent Title="Profile" Route="ProfilePage" ContentTemplate="{DataTemplate local:ProfilePage}">
<ShellContent.Icon>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FaSolidIcons.User}" />
</ShellContent.Icon>
</ShellContent>
<ShellContent Title="Settings" Route="SettingsPage" ContentTemplate="{DataTemplate local:SettingsPage}">
<ShellContent.Icon>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FaSolidIcons.Cog}" />
</ShellContent.Icon>
</ShellContent>
</TabBar>
就是这样。结果如下图:
注意:FAB 的主题和图标很容易完成,这取决于任何感兴趣的人。编码愉快!