Xamarin.Forms - 摇篮 FAB

Posted

技术标签:

【中文标题】Xamarin.Forms - 摇篮 FAB【英文标题】:Xamarin.Forms - Cradle FAB 【发布时间】:2021-03-01 09:48:36 【问题描述】:

我想放置浮动操作按钮。按钮必须是动态的(它将是动画的,并且底座必须适应它,就像从 material.io 拍摄的视频中一样)。它必须与 iosandroid 一样工作。如果可以,请提供 XAML 示例。也许是自定义渲染器。

我知道这个功能默认存在于 Android Studio 和 Flutter 中。

如果我的问题有问题,请写信给我,并对我的英语感到抱歉。

【问题讨论】:

你应该分享你到目前为止所做的... 我稍后会用我的 XAML 代码更新问题,我现在没有可能 (我把答案移到了这里,因为它更像是一个评论而不是一个解决方案 :P)你也许可以使用 Shapes docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/… 来实现这一点,就像在 @DavidOrtinau 的演示中一样,github.com/davidortinau/LoginShape还要确保查看动画。如果您遇到特定问题,请就您面临的问题发表明确的问题。我们很乐意提供帮助。 【参考方案1】:

您将不得不用代码弄脏您的手。我的解决方案适用于 Android,我也很想知道它是如何在 iOS 中完成的。 我依赖于 nuget 包Xamarin.Forms.Visual.Material。可以在 here 和 here 找到一篇让我入门的好文章。还可以查看 Google 的 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_
    android:layout_
    tools:context=".MainActivity">

             
    <!-- Note: A RecyclerView can also be used -->
    <androidx.core.widget.NestedScrollView
        android:layout_
        android:layout_
        android:paddingBottom="100dp"
        android:clipToPadding="false">

        <!-- Scrollable content -->
        <FrameLayout
            android:id="@+id/bottomtab.navarea"
            android:layout_
            android:layout_
            android:layout_gravity="fill"
            android:layout_weight="1" />

    </androidx.core.widget.NestedScrollView>

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bottomAppBar"
        android:layout_
        android:layout_
        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_
            android:layout_
            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_
        android:layout_
        android:id="@+id/fab"
        app:layout_anchor="@id/bottomAppBar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

并且在 style.xml 文件中,修改其内容如下以使用任何材质主题:

<?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(项目的外壳渲染器)

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 共享项目(带有 Shell 的 .Net 标准)中,随意使用您的 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 的主题和图标可以很容易地完成,这取决于任何有兴趣的人。编码愉快!

【讨论】:

我很久以前就切换到了flutter,但我认为你的回答是对的,所以我接受了它,希望它对其他开发者有帮助。感谢您的努力!

以上是关于Xamarin.Forms - 摇篮 FAB的主要内容,如果未能解决你的问题,请参考以下文章

Xamarin.Forms:Forms.Context 已过时

Xamarin.Forms 手势密码实现

Xamarin.Forms 和 Xamarin Native 有啥区别? [关闭]

如何使用 Xamarin.Forms.Maps(无 Xamarin.Forms.GoogleMaps)在地图中应用样式或更改颜色

Xamarin Forms Prism:prism ResourceDictionary 中已存在具有键“Xamarin.Forms.NavigationPage”的资源

Xamarin.Forms.Forms.Init(e) Onlaunched 中的 FileNotFoundExeception