How to make icons to show for Secondary ToolbarItem in Xamarin.Forms

Background

I got one customer who wants to achieve the below effect for Android Secondary Toolbar:

Originally I thought it might be a really bad thing for Xamarin only because I assume that Android native can directly achieve this.

However, after further analyzing, I found that the reason why Xamarin Secondary ToolbarItem cannot do as this is due to Android native is also doing the same.

How does Secondary Toolbar work in native Android or Xamarin.Android?

Actually in native Android development, there is no such term as Secondary Toolbar, it only exists in Xamarin world.

In native Android, we are calling the toolbar item as the MenuItem. The protocol interface in Xamarin.Android is as below:

1
2
3
4
5
6
7
8
9
public interface IMenuItem : IJavaObject, IDisposable
{
// ...
IMenuItem SetTitle(int title); // Text
IMenuItem SetIcon(Drawable icon); // Image
void SetShowAsAction(ShowAsAction actionEnum); // Positioning
ISubMenu SubMenu { get; } // Hierarchy
IMenuItem SetChecked(bool value); // Checked State
}

How we can determine whether the MenuItem should be showing as a separate icon in the Toolbar is decided by SetShowAsAction. There are 3 values for that:

  • always: definitely show the menu as icon in Toolbar
  • ifRoom: check whether there is room in the Toolbar for the MenuItem, if no room, then it will go to the overflow area which is our Secondary Toolbar.
  • never: directly make the MenuItem to the overflow area.

Check below image for demonstrating:

And below is how icon, text and overflow area will be used:

  • If the MenuItem is shown in the toolbar directly, then icon will be shown, and title will be the tooltip.
  • If the MenuItem is in the overflow area, then only the title is used in the overflow area.

How to achieve the icon-shown Secondary Toolbar in native Android?

I have not done any tests yet, but according to what I have researched, we can apply some workaround such as use the SubMenu to make it connect to a Main MenuItem which is shown in the Toolbar. Then make the icon of the main menuitem to the 3 dots so that it will look like the overflow area.

How to achieve it in Xamarin.Forms?

Why not custom renderers?

The first thought to resolve this issue from mine is to use Custom Renderer. The reason why we cannot do this is because there is no renderer to be customized for the type ToolbarItem.

Reference link for all the controls that can be customized: Renderer Base Classes and Native Controls

What is my solution?

I am trying to do this similar as the native Android development. I will use a Primary ToolbarItem whose icon is the 3 dots. And then combine a command to this primary toolbaritem to trigger the IsVisible property of a Custom Control that I define which is a ListView whose item is a StackLayout with an image and a label. Finally I will use RelativeLayout to position this ListView to the right of the screen pane.

Benefits of this solution

Most times we will want Android and iOS to looks exactly the same for cross-platform application. However, Secondary Toolbar in Xamarin.Android and Xamarin.iOS are totally different. iOS is having another line of the toolbar under the Primary Toolbar.

So this solution has a benefit that we do not use Secondary Toolbar at all. It will / should look the same on each platform.

Implementation

I am still trying to use MVVM structure to achieve things in Xamarin.Forms.

  1. The first step is to define the model of our ToolbarItem. It’s really quite straightforward since our ToolbarItem only contains one Image and one Label. In Xamarin.Forms, we are using Image path to identify an image, so in our model, we are only going to use the ImagePath as a string property.

    We create a folder named Models and create a file named ToolbarItemModel.cs.

    Check code below:

    1
    2
    3
    4
    5
    6
    public class ToolbarItemModel
    {
    public string ImagePath { get; set; }

    public string MenuText { get; set; }
    }
  1. Now we are going to create a custom control using a ContentView. You should know that ContentView is different from ContentPage, it’s used to define some UIs that can be re-usable in other pages.

    We create a folder named CustomControls, and then create a file from VS template called ContentView, name the file as CustomToolbarItem. There will be 2 files created under that folder: CustomToolbarItem.xaml and its code behind file CustomToolbarItem.xaml.cs.

    First we need to go to the code behind file to define the BindableProperty with MenuText and ImagePath so that when we declare the UI elements in the XAML file, the control and data will be bind.

    CustomToolbarItem.xaml.cs file’s code as below:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    // This class is genereated from template actually
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class CustomToolbarItem : ContentView
    {
    // Bindable Property definition for MenuText
    public static readonly BindableProperty MenuTextProperty = BindableProperty.Create(
    propertyName: "MenuText",
    returnType: typeof(string),
    declaringType: typeof(CustomToolbarItem),
    defaultValue: "",
    defaultBindingMode: BindingMode.OneWay,
    propertyChanged: TextPropertyChanged);

    // This property name should be the same as the MenuTextProperty's propertyName field
    public string MenuText
    {
    get { return base.GetValue(MenuTextProperty).ToString(); }
    set { base.SetValue(MenuTextProperty, value); }
    }

    // This is the TextPropertyChanged event handler for the MenuTextProperty
    private static void TextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
    var control = (CustomToolbarItem)bindable;
    control.menuText.Text = newValue.ToString();
    }

    // Below is for IconImage, it's the same concept with MenuText
    // They all have 3 steps: BindableProperty, public property, PropertyChanged event handler
    public static readonly BindableProperty IconImageProperty = BindableProperty.Create(
    propertyName: "IconImage",
    returnType: typeof(string),
    declaringType: typeof(CustomToolbarItem),
    defaultValue: "",
    defaultBindingMode: BindingMode.OneWay,
    propertyChanged: ImageSourcePropertyChanged);

    public string IconImage
    {
    get { return base.GetValue(IconImageProperty).ToString(); }
    set { base.SetValue(IconImageProperty, value); }
    }

    private static void ImageSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
    var control = (CustomToolbarItem)bindable;
    // Notice here we are updating the ImageSource of the control's image property
    control.iconImage.Source = ImageSource.FromFile(newValue.ToString());
    }

    // Below is the constructor of the ContentView, nothing special
    public CustomToolbarItem ()
    {
    InitializeComponent();
    }
    }

    Then we go to the XAML file to define the UI elements. It’s just some structured StackLayout to make the elements in one line and with correct layout.

    Write the below code as the Content of the ContentView:

    1
    2
    3
    4
    5
    6
    <ContentView.Content>
    <StackLayout Orientation="Horizontal" Spacing="10" Padding="5,5,5,5">
    <Image x:Name="iconImage" HeightRequest="30" HorizontalOptions="Start" VerticalOptions="Center" />
    <Label x:Name="menuText" FontSize="Medium" VerticalOptions="Center" HorizontalOptions="Start" />
    </StackLayout>
    </ContentView.Content>

    Now we have done the custom control part.

  2. What we left is to use the Custom Control in our Page. In the MainPage.xaml file, we are going to add the ContentPage.ToolbarItems and RelativeLayout as below:

    Remember to add the xmlns:cxcontrols="clr-namespace:AllenCustomSecondaryToolbar.CustomControls" in the header of the ContentPage to use the control in your page.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <ContentPage.ToolbarItems>
    <!-- Other primary toolbaritems -->
    <ToolbarItem Text="More" Icon="menu3dots.png" Priority="1" Order="Primary" x:Name="cmdToolbarItem" />
    </ContentPage.ToolbarItems>

    <RelativeLayout>
    <ListView x:Name="SecondaryToolbarListView" VerticalOptions="Start" HorizontalOptions="Start" WidthRequest="150" IsVisible="False"
    RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width, Factor=1, Constant=-160}">
    <ListView.ItemTemplate>
    <DataTemplate>
    <ViewCell>
    <cxcontrols:CustomToolbarItem IconImage="{Binding ImagePath}" MenuText="{Binding MenuText}" BackgroundColor="Default" />
    </ViewCell>
    </DataTemplate>
    </ListView.ItemTemplate>
    </ListView>
    <Label Text="This is a really really really really really really long label" />
    </RelativeLayout>

    Note: The WidthRequest for the ListView is based on my settings for the Custom ToolbarItem’s font setting and height request. You might need to modify it if you are using different font size and height request.

    Also, the Constant for the RelativeLayout property in the ListView is based on the WidthRequest in the above point. I am making 10 units of width as the space between the ListView and the right edge of the mobile screen.

    Next, let’s go to the code behind file and make the MainPage code as below:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public MainPage()
    {
    InitializeComponenet();

    var items = new List<ToolbarItemModel>
    {
    new ToolbarItemModel {ImagePath = "xxxx.png", MenuText = "First Menu Item"},
    new ToolbarItemModel {ImagePath = "xxxxx.png", MenuText = "Second Menu Item"}
    }

    SecondaryToolbarListView.ItemsSource = items;

    cmdToolbarItem.Command = new Command(SecondaryToolbarCmd);
    }

    void SecondaryToolbarCmd()
    {
    SecondaryToolbarListView.IsVisible = !SecondaryToolbarListView.IsVisible;
    }

    Note: Remember to add the images to the folder in Android and iOS projects not the Xamarin.Forms project.

    In Android, it’s \Resources\drawable.
    In iOS, it’s \Resources.

Now we have done this demo, and below is the basic effect if we run this demo:

Pending things

As we can see from the above screenshot, we need to add some background that can match the theme and cover all the texts that is occupying the space of the Secondary Toolbar that I implemented.

Pain points of Xamarin.Forms using Azure Mobile Apps offline sync feature Run PowerShell command using .NET SDK
You need to set install_url to use ShareThis. Please set it in _config.yml.

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×