2020.09.01.

Xamarin.Android – Bottom Bubble TabBar

By bence960206

Suppose we use an Activity and swap Fragments within the Activity.

Within Acitvity, use RelativeLayout for External Layout and we will place it at the bottom, centering our menu.

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f5f5f5">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:background="@xml/bottom_menu"
        android:id="@+id/bottomMenuRelativeLayout"
        android:gravity="center"
        android:layout_marginHorizontal="15dp"
        android:elevation="30dp">
        <ImageView
            android:src="@drawable/search"
            android:id="@+id/searchmenuItemImageView"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:layout_alignParentLeft="true"
            android:layout_centerInParent="true"
            android:layout_marginHorizontal="50dp"
            android:layout_gravity="center_vertical | center"/>
        <RelativeLayout
            android:id="@+id/searchmenuItemLinearlayout"
            android:background="@xml/menu_item_rounded"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:animateLayoutChanges="true"
            android:layout_marginHorizontal="10dp"
            android:orientation="horizontal"
            android:visibility="gone"
            android:layout_alignParentLeft="true"
            android:layout_centerInParent="true"
            android:layout_gravity="center_vertical | center">
            <ImageView
                android:src="@drawable/whitesearch"
                android:id="@+id/searchmenuItemWhiteImageView"
                android:layout_width="wrap_content"
                android:layout_height="20dp"
                android:layout_marginLeft="10dp"
                android:layout_marginVertical="10dp"
                android:layout_alignParentLeft="true"/>
        </RelativeLayout>

        <ImageView
            android:src="@drawable/personal"
            android:id="@+id/personalmenuItemImageView"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:layout_marginHorizontal="50dp"
            android:layout_centerInParent="true"
            android:layout_gravity="center_vertical | center"/>
        <RelativeLayout
            android:id="@+id/personalmenuItemLinearlayout"
            android:background="@xml/menu_item_rounded"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:animateLayoutChanges="true"
            android:visibility="gone"
            android:layout_centerInParent="true"
            android:layout_marginHorizontal="10dp"
            android:layout_gravity="center_vertical | center">
            <ImageView
                android:src="@drawable/whitepersonal"
                android:id="@+id/personalmenuWhiteItemImageView"
                android:layout_width="wrap_content"
                android:layout_height="20dp"
                android:layout_marginLeft="10dp"
                android:layout_marginVertical="10dp"
                android:layout_alignParentLeft="true"/>
        </RelativeLayout>

        <ImageView
            android:src="@drawable/showmenu"
            android:id="@+id/showmenumenuItemImageView"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:layout_marginHorizontal="50dp"
            android:layout_alignParentRight="true"
            android:layout_centerInParent="true"
            android:layout_gravity="center_vertical | center"/>
        <RelativeLayout
            android:id="@+id/showmenumenuItemLinearlayout"
            android:background="@xml/menu_item_rounded"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:animateLayoutChanges="true"
            android:layout_marginHorizontal="10dp"
            android:orientation="horizontal"
            android:visibility="gone"
            android:layout_alignParentRight="true"
            android:layout_centerInParent="true"
            android:layout_gravity="center_vertical | center">
            <ImageView
                android:src="@drawable/whiteshowmenu"
                android:id="@+id/showmenumenuWhiteItemImageView"
                android:layout_width="wrap_content"
                android:layout_height="20dp"
                android:layout_marginLeft="10dp"
                android:layout_marginVertical="10dp"
                android:layout_alignParentLeft="true"/>
        </RelativeLayout>
    </RelativeLayout>
</RelativeLayout>

Set the Bottom menu as rounded View at: android:background=”@xml/bottom_menu”

<?xml version="1.0" encoding="utf-8" ?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
    <shape android:shape="rectangle"
           android:alpha="0.2">
      <solid android:color="#F4F4F7"/>
      <corners android:topLeftRadius="32dp"
        android:topRightRadius="32dp"/>
    </shape>
  </item>
  <item
      android:left="0dp"
      android:right="0dp"
      android:top="0dp"
      android:bottom="0dp">
    <shape android:shape="rectangle">
      <solid android:color="#fff"/>
      <corners android:topLeftRadius="32dp"
        android:topRightRadius="32dp"/>
    </shape>
  </item>
</layer-list>

In the first rectangle I set a little shadow of the menu, but it is not necessary, if you dont want shadow just use this code in the bottom_menu.xml:

<?xml version="1.0" encoding="utf-8" ?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item
      android:left="0dp"
      android:right="0dp"
      android:top="0dp"
      android:bottom="0dp">
    <shape android:shape="rectangle">
      <solid android:color="#fff"/>
      <corners android:topLeftRadius="32dp"
        android:topRightRadius="32dp"/>
    </shape>
  </item>
</layer-list>

After that we have to define the style of the menu-items. Create a menu_item_rounded.xml:

<?xml version="1.0" encoding="utf-8" ?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
  <solid android:color="#5A63B3"/>
  <corners android:radius="32dp"/>
</shape>

And finally, we will define the animation of the menu. Replace your MainActivity:

using Android.App;
using Android.OS;
using Android.Support.V7.App;
using Android.Runtime;
using Android.Widget;
using System.Collections.Generic;
using Android.Views;
using Android.Animation;
using System;
using System.Threading.Tasks;

namespace App1
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        ImageView searchmenuItemImageView;
        ImageView personalmenuItemImageView;
        ImageView showmenumenuItemImageView;

        ImageView searchmenuItemWhiteImageView;
        ImageView personalmenuWhiteItemImageView;
        ImageView showmenumenuWhiteItemImageView;

        RelativeLayout searchmenuItemLinearlayout;
        RelativeLayout personalmenuItemLinearlayout;
        RelativeLayout showmenumenuItemLinearlayout;

        RelativeLayout bottomMenuRelativeLayout;

        readonly Dictionary<ImageView, RelativeLayout> menuPairsDictionary = new Dictionary<ImageView, RelativeLayout>();
        readonly Dictionary<RelativeLayout, ImageView> menuLayoutWhiteImagePairsDictionary = new Dictionary<RelativeLayout, ImageView>();

        object prevMenuSelected;

        int OpenedMenuIconTransationX { get; set; } = -80;

        int OpenedWhiteMenuIconTransationX { get; set; } = 70;

        float ScaleFOpenedMenu { get; set; } = 0.9f;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            SetContentView(Resource.Layout.activity_main);

            bottomMenuRelativeLayout = FindViewById<RelativeLayout>(Resource.Id.bottomMenuRelativeLayout);

            searchmenuItemWhiteImageView = FindViewById<ImageView>(Resource.Id.searchmenuItemWhiteImageView);
            personalmenuWhiteItemImageView = FindViewById<ImageView>(Resource.Id.personalmenuWhiteItemImageView);
            showmenumenuWhiteItemImageView = FindViewById<ImageView>(Resource.Id.showmenumenuWhiteItemImageView);
            searchmenuItemImageView = FindViewById<ImageView>(Resource.Id.searchmenuItemImageView);
            personalmenuItemImageView = FindViewById<ImageView>(Resource.Id.personalmenuItemImageView);
            showmenumenuItemImageView = FindViewById<ImageView>(Resource.Id.showmenumenuItemImageView);

            searchmenuItemLinearlayout = FindViewById<RelativeLayout>(Resource.Id.searchmenuItemLinearlayout);
            personalmenuItemLinearlayout = FindViewById<RelativeLayout>(Resource.Id.personalmenuItemLinearlayout);
            showmenumenuItemLinearlayout = FindViewById<RelativeLayout>(Resource.Id.showmenumenuItemLinearlayout);

            menuPairsDictionary.Add(searchmenuItemImageView, searchmenuItemLinearlayout);
            menuPairsDictionary.Add(personalmenuItemImageView, personalmenuItemLinearlayout);
            menuPairsDictionary.Add(showmenumenuItemImageView, showmenumenuItemLinearlayout);

            menuLayoutWhiteImagePairsDictionary.Add(searchmenuItemLinearlayout, searchmenuItemWhiteImageView);
            menuLayoutWhiteImagePairsDictionary.Add(personalmenuItemLinearlayout, personalmenuWhiteItemImageView);
            menuLayoutWhiteImagePairsDictionary.Add(showmenumenuItemLinearlayout, showmenumenuWhiteItemImageView);

            searchmenuItemImageView.Click += SearchmenuItemImageView_Click;
            personalmenuItemImageView.Click += PersonalmenuItemImageView_Click;
            showmenumenuItemImageView.Click += ShowmenumenuItemImageView_Click;
            searchmenuItemLinearlayout.Click += SearchmenuItemLinearlayout_Click;
            personalmenuItemLinearlayout.Click += PersonalmenuItemLinearlayout_Click;
            showmenumenuItemLinearlayout.Click += ShowmenumenuItemLinearlayout_Click;

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }


        private async void ShowmenumenuItemLinearlayout_Click(object sender, EventArgs e)
        {
            RunOnUiThread(() =>
            {
                searchmenuItemImageView.Visibility = ViewStates.Visible;
                personalmenuItemImageView.Visibility = ViewStates.Visible;
                showmenumenuItemImageView.Visibility = ViewStates.Visible;

                searchmenuItemLinearlayout.Visibility = ViewStates.Gone;
                personalmenuItemLinearlayout.Visibility = ViewStates.Gone;
                showmenumenuItemLinearlayout.Visibility = ViewStates.Gone;
            });

            await AnimeteMenu(sender);

            RunOnUiThread(() =>
            {
                prevMenuSelected = showmenumenuItemLinearlayout;
            });
        }

        private async void PersonalmenuItemLinearlayout_Click(object sender, EventArgs e)
        {
            RunOnUiThread(() =>
            {
                searchmenuItemImageView.Visibility = ViewStates.Visible;
                personalmenuItemImageView.Visibility = ViewStates.Visible;
                showmenumenuItemImageView.Visibility = ViewStates.Visible;

                searchmenuItemLinearlayout.Visibility = ViewStates.Gone;
                personalmenuItemLinearlayout.Visibility = ViewStates.Gone;
                showmenumenuItemLinearlayout.Visibility = ViewStates.Gone;
            });

            await AnimeteMenu(sender);

            RunOnUiThread(() =>
            {
                prevMenuSelected = personalmenuItemLinearlayout;
            });
        }

        private async void SearchmenuItemLinearlayout_Click(object sender, EventArgs e)
        {
            RunOnUiThread(() =>
            {
                searchmenuItemImageView.Visibility = ViewStates.Visible;
                personalmenuItemImageView.Visibility = ViewStates.Visible;
                showmenumenuItemImageView.Visibility = ViewStates.Visible;

                searchmenuItemLinearlayout.Visibility = ViewStates.Gone;
                personalmenuItemLinearlayout.Visibility = ViewStates.Gone;
                showmenumenuItemLinearlayout.Visibility = ViewStates.Gone;
            });

            await AnimeteMenu(sender);

            RunOnUiThread(() =>
            {
                prevMenuSelected = searchmenuItemLinearlayout;
            });
        }

        private async void ShowmenumenuItemImageView_Click(object sender, EventArgs e)
        {
            RunOnUiThread(() =>
            {
                searchmenuItemWhiteImageView.TranslationX = OpenedWhiteMenuIconTransationX;
                personalmenuWhiteItemImageView.TranslationX = OpenedWhiteMenuIconTransationX;
                showmenumenuWhiteItemImageView.TranslationX = OpenedWhiteMenuIconTransationX;

                searchmenuItemImageView.Visibility = ViewStates.Visible;
                personalmenuItemImageView.Visibility = ViewStates.Visible;
                showmenumenuItemImageView.Visibility = ViewStates.Gone;

                searchmenuItemLinearlayout.Visibility = ViewStates.Gone;
                personalmenuItemLinearlayout.Visibility = ViewStates.Gone;

                showmenumenuItemLinearlayout.ScaleX = ScaleFOpenedMenu;
                showmenumenuItemLinearlayout.Visibility = ViewStates.Visible;
            });

            await AnimeteMenu(sender);

            RunOnUiThread(() =>
            {
                prevMenuSelected = showmenumenuItemImageView;
            });
        }

        private async void PersonalmenuItemImageView_Click(object sender, EventArgs e)
        {
            RunOnUiThread(() =>
            {
                searchmenuItemWhiteImageView.TranslationX = OpenedWhiteMenuIconTransationX;
                personalmenuWhiteItemImageView.TranslationX = OpenedWhiteMenuIconTransationX;
                showmenumenuWhiteItemImageView.TranslationX = OpenedWhiteMenuIconTransationX;

                searchmenuItemImageView.Visibility = ViewStates.Visible;
                personalmenuItemImageView.Visibility = ViewStates.Gone;
                showmenumenuItemImageView.Visibility = ViewStates.Visible;

                searchmenuItemLinearlayout.Visibility = ViewStates.Gone;
                showmenumenuItemLinearlayout.Visibility = ViewStates.Gone;

                personalmenuItemLinearlayout.ScaleX = ScaleFOpenedMenu;
                personalmenuItemLinearlayout.Visibility = ViewStates.Visible;
            });

            await AnimeteMenu(sender);

            RunOnUiThread(() =>
            {
                prevMenuSelected = personalmenuItemImageView;
            });
        }

        private async void SearchmenuItemImageView_Click(object sender, EventArgs e)
        {
            RunOnUiThread(() =>
            {
                searchmenuItemWhiteImageView.TranslationX = OpenedWhiteMenuIconTransationX;
                personalmenuWhiteItemImageView.TranslationX = OpenedWhiteMenuIconTransationX;
                showmenumenuWhiteItemImageView.TranslationX = OpenedWhiteMenuIconTransationX;

                searchmenuItemImageView.Visibility = ViewStates.Gone;
                personalmenuItemImageView.Visibility = ViewStates.Visible;
                showmenumenuItemImageView.Visibility = ViewStates.Visible;

                personalmenuItemLinearlayout.Visibility = ViewStates.Gone;
                showmenumenuItemLinearlayout.Visibility = ViewStates.Gone;

                searchmenuItemLinearlayout.ScaleX = ScaleFOpenedMenu;
                searchmenuItemLinearlayout.Visibility = ViewStates.Visible;
            });

            await AnimeteMenu(sender);

            RunOnUiThread(() =>
            {
                prevMenuSelected = searchmenuItemImageView;
            });
        }

        private async Task AnimeteMenu(object actualSelected)
        {
            if (prevMenuSelected is ImageView && actualSelected is ImageView)
            {
                var relativePrevLayout = menuPairsDictionary[(ImageView)prevMenuSelected];
                var relativeActualLayout = menuPairsDictionary[(ImageView)actualSelected];
                var whiteImageView = menuLayoutWhiteImagePairsDictionary[relativeActualLayout];

                ObjectAnimator anim = ObjectAnimator.OfFloat(relativeActualLayout, "scaleX", ScaleFOpenedMenu, 1f);
                anim.SetDuration(250);

                ObjectAnimator anim1 = ObjectAnimator.OfFloat((ImageView)prevMenuSelected, "translationX", OpenedMenuIconTransationX, 0);
                anim1.SetDuration(250);

                ObjectAnimator anim2 = ObjectAnimator.OfFloat(whiteImageView, "translationX", OpenedWhiteMenuIconTransationX, 0);
                anim2.SetDuration(250);

                await System.Threading.Tasks.Task.Run(() =>
                {
                    RunOnUiThread(() =>
                    {
                        relativeActualLayout.Visibility = ViewStates.Visible;

                        anim.Start();
                    });
                });

                await System.Threading.Tasks.Task.Run(() =>
                {
                    RunOnUiThread(() =>
                    {
                        whiteImageView.Visibility = ViewStates.Visible;

                        anim2.Start();
                    });
                });

                await System.Threading.Tasks.Task.Run(() =>
                {
                    RunOnUiThread(() =>
                    {
                        anim1.Start();
                    });
                });

                RunOnUiThread(() =>
                {
                    ((ImageView)actualSelected).TranslationX = OpenedMenuIconTransationX;
                });
            }
            else if (prevMenuSelected is ImageView && actualSelected is RelativeLayout)
            {
                ObjectAnimator anim1 = ObjectAnimator.OfFloat((ImageView)prevMenuSelected, "translationX", OpenedMenuIconTransationX, 0);
                anim1.SetDuration(250);

                await System.Threading.Tasks.Task.Run(() =>
                {
                    RunOnUiThread(() =>
                    {
                        anim1.Start();
                    });
                });

                prevMenuSelected = null;
            }
            else
            {
                var relativeLayout = menuPairsDictionary[(ImageView)actualSelected];
                var whiteImageView = menuLayoutWhiteImagePairsDictionary[relativeLayout];

                ObjectAnimator selectTionAnimation = ObjectAnimator.OfFloat(relativeLayout, "scaleX", ScaleFOpenedMenu, 1f);
                selectTionAnimation.SetDuration(250);

                ObjectAnimator whiteMenuIconAnimation = ObjectAnimator.OfFloat(whiteImageView, "translationX", OpenedWhiteMenuIconTransationX, 0);
                whiteMenuIconAnimation.SetDuration(250);

                await System.Threading.Tasks.Task.Run(() =>
                {
                    RunOnUiThread(() =>
                    {
                        selectTionAnimation.Start();
                    });
                });

                await System.Threading.Tasks.Task.Run(() =>
                {
                    RunOnUiThread(() =>
                    {
                        whiteMenuIconAnimation.Start();
                    });
                });

                RunOnUiThread(() =>
                {
                    ((ImageView)actualSelected).TranslationX = OpenedMenuIconTransationX;
                });
            }
        }

        private void AnimeteBackMenu()
        {
            ImageView imageView = new ImageView(this);

            if (searchmenuItemImageView.TranslationX != 0)
            {
                imageView = searchmenuItemImageView;
            }
            else if (showmenumenuItemImageView.TranslationX != 0)
            {
                imageView = showmenumenuItemImageView;
            }
            else if (personalmenuItemImageView.TranslationX != 0)
            {
                imageView = personalmenuItemImageView;
            }

            ObjectAnimator anim1 = ObjectAnimator.OfFloat(imageView, "translationX", OpenedMenuIconTransationX, 0);
            anim1.SetDuration(200);

            System.Threading.Tasks.Task.Run(() =>
            {
                RunOnUiThread(() =>
                {
                    anim1.Start();
                });
            });

            prevMenuSelected = null;
        }
    }
}

How does animation work? If you click on a menu-item, this code will decides what you clicked. If you clicked on a white image, it will replace white image with the rounded RelativeLayout. If you clicked on the rounded RelativeLayout, it will collapse and will replace the white image.

Ok, but what is the animation? This animation will do transation on the X-axis (white and black image) and will scale the rounded RelativeLayout.

Some tips for the upgrade:

  • If you want more or less menu-item, you have to code in the activity_main.xml (add new white image and a rounded menu-item) and in the MainActivity.cs – complete the animation code and the dictionary and define new ImageView and RelativeLayout in the OnCreate().
  • You can change the colors too in the two xmls.
  • And you can add some text in the bubble too, just add it in the activity_main.xml in the rounded RelativeLayout.