[Xamarin UI Challenge] Art Plant Mall (English Version)

The challenge

I am going for a new user interface challenge with Xamarin.Forms. In this article, we are going to take as reference a Dribbble design (by JIANGGM), and we will try to replicate it using Xamarin.Forms step by step.

Art Plant Mall

The challenges of the sample

Despite being a nice design, there are no very complex challenges, but we have many small details:

  • List of plants: The CollectionView is not only an important improvement in performance when working with collections, also with different layouts (horizontal listings, GridViews, etc.). In this case we need two columns, something simple now using the CollectionView.
  • Sliding menu from the bottom: The sliding menu is one of the most important parts of the sample. We can achieve this result in a simple way with plugins like SlideOverKit. However, in this sample we are going to create something simple from scratch using a ContentView and animations.
  • Content of the sliding menu: The content of the menu changes depending on two states, if the menu is open or closed. To manage two visual states and the change between them we will use VisualStateManager. When having the menu in the closed or collapsed state we show a horizontal list with the photos (circular images) of the plants that we have in the cart. We can get this easily using Bindable Layouts or CollectionView. Expanding the sliding menu we see a list with the details of the cart. We can use again the CollectionView and even a ListView with a DataTemplateSelector to differentiate between the plants added to the cart and other elements such as delivery costs. The buttons with rounded edges can be obtained with a combination of using Layouts (where the Frame with its CornerRadius acquires the weight) or using a community plugin such as PancakeView that facilitates the management of Layouts with rounded edges, etc.
  • Details of a plant: The details page is easy. The complexity of this view is on the necessary controls that we do not have by default in Xamarin.Forms such as the NumericUpDown or the ToggleButton.
  • Parallax: We can create a Parallax effect in a simple way using a ScrollView and the scroll event with animations (mainly translation).
  • NumericUpDown: We have several options to get the NumericUpDown control. We have the option to use a Custom Renderer or create it using SkiaSharp. We have another option that is to create a custom control using a composition of Xamarin.Forms elements. In this case, we will use a ContentView with a Layout and three Labels. The control will have several BindableProperties to set the value, the minimum value, the maximum value, etc.
  • ToggleButton: We will do something similar to the previous case, we will use a ContentView. Again, we will have BindableProperties to set the status, etc. To get the same visual result, we will use images to define each visual state of the control (Checked or UnChecked).

Plants list

Let’s start with the plants list. For the Layout required (two columns) we will use the CollectionView.

The CollectionView is still marked with experimental flag and can only be used by adding the following line of code in the AppDelegate in the case of iOS, as well as in the MainActivity in Android, before calling Forms.Init:

Forms.SetFlags("CollectionView_Experimental");

The use of the CollectionView will be simple:

<CollectionView
     ItemsSource="{Binding Plants}" />
CollectionView has the ItemsLayout property, which allows to specify the Layout to be used.
<CollectionView.ItemsLayout>
     <GridItemsLayout 
          Orientation="Vertical"
          Span="2" />
</CollectionView.ItemsLayout>

We have used GridItemsLayout specifying the orientation and the number of columns using the Span property.

The result:

El listado de plantas

Sliding menu

Our sliding menu is going to be something very simple… a ContentView. We will place it on the main page, but how do we position it?.

We must have in mind that will expand or collpase the menu using a translation animation on the Y axis.

The initial position of the sliding menu (closed or collapsed state) must be at the bottom and only showing the «header» of the menu.

CartPopup.TranslationY = pageHeight - CartPopup.HeaderHeight;

Simple. We move the menu on the Y axis to the bottom of the page plus the height of the header!.

Once the initial position is established, we need to manage two actions:

  • Expand: We will make a translation on the Y axis from the initial position, to the top edge of the screen less the top of the menu.
  • Collapse: We will return to the starting position. That is, height of the page less the menu header height.

Let’s see the logic:

private void OnExpand()
{
     CartPopupFade.IsVisible = true;
     CartPopupFade.FadeTo(1, ExpandAnimationSpeed, Easing.SinInOut);

     var height = pageHeight - CartPopup.HeaderHeight;
     CartPopup.TranslateTo(0, Height - height, ExpandAnimationSpeed, Easing.SinInOut);
}

private void OnCollapse()
{
     CartPopupFade.FadeTo(0, CollapseAnimationSpeed, Easing.SinInOut);
     CartPopupFade.IsVisible = false;
     CartPopup.TranslateTo(0, pageHeight - CartPopup.HeaderHeight, CollapseAnimationSpeed, Easing.SinInOut);
}

In addition to the translation of the sliding menu, we have added a Grid with a translucent dark background to enhance the opening and closing effect of the menu.

But … and what happens with the menu states?. Right. There are elements visible when the menu is collapsed and others visible when expanding it.

How do we manage this?.

We talk about manage different visual states. There is no case where the use of VisualStateManager fits better!.

We will define visual states in certain elements of the sliding menu (buttons to open and close the menu, lists, etc.):

<VisualStateManager.VisualStateGroups>
     <VisualStateGroup x:Name="CommonStates">
          <VisualState x:Name="Expanded">
               <VisualState.Setters>
                    <Setter Property="IsVisible" Value="False" />
               </VisualState.Setters>
          </VisualState>
          <VisualState x:Name="Collapsed">
               <VisualState.Setters>
                    <Setter Property="IsVisible" Value="True" />
               </VisualState.Setters>
          </VisualState> 
     </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

We have created the two necessary visual states and will manage the change of the state using the GoToState method. For example:

VisualStateManager.GoToState(CollapseButton, "Expanded");

Let’s see the result:

El menu deslizante

Content of the sliding menu

The most interesting part of the sliding menu content is the detailed list of items included in the cart. If we look at the design, in the list we have different elements. We can clearly differentiate between plants and other expenses such as the delivery costs.

To achieve this result, we will use a ListView:

<ListView 
     ItemsSource="{Binding Basket}"
     ItemTemplate="{StaticResource BasketItemDataTemplateSelector}" />

Using a DataTemplateSelector:

public class BasketItemDataTemplateSelector : DataTemplateSelector
{
     public DataTemplate PlantTemplate { get; set; }
     public DataTemplate DeliveryTemplate { get; set; }

     protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
     {
          return ((BasketItem)item).BasketItemType == BasketItemType.Plant ? PlantTemplate : DeliveryTemplate;
     }
}

A template selector is implemented creating a class that inherits from DataTemplateSelector. Next, we use the OnSelectTemplate method to return an element of type DataTemplate.

The result:

El contenido del menu

Plant details

The detail view is based on a composition of Layouts in combination with an Image and several Labels.

Detalles de una planta

Parallax

Despite being a design trend (actually has been used for years on the web and also in mobility development) is quite simple to get a Parallax effect. The effect behaves as a «narrative» complement. We play with the movement and depth to highlight something. In our case, scrolling to the bottom, we will highlight the information of the plant downplaying the header image.

Let’s simplify everything by creating a custom control. It is a class that inherits from ScrollView:

public class ParallaxControl : ScrollView
{
     const float ParallaxSpeed = 2.25f;

     double _height;

     public ParallaxControl()
     {
          Scrolled += (sender, e) => Parallax();
     }

     public static readonly BindableProperty ParallaxViewProperty =
          BindableProperty.Create(nameof(ParallaxControl), typeof(CachedImage), typeof(ParallaxControl), null);

     public View ParallaxView
     {
          get { return (View)GetValue(ParallaxViewProperty); }
          set { SetValue(ParallaxViewProperty, value); }
     }

     public void Parallax()
     {
          if (ParallaxView == null)
               return;

          if (_height <= 0)
               _height = ParallaxView.Height;

          var y = -(int)((float)ScrollY / ParallaxSpeed);

          if (y < 0)
               ParallaxView.TranslationY = y;
          else
               ParallaxView.TranslationY = 0;
     }
}

We are going to move the header image at a lower speed than the rest of the content. To do this, we create a BindableProperty to specify which visual element is the header. In the Scrolled event of the ScrollView, we are going to translate (TranslationY) the header.

The result:

Parallax effect

NOTE: We can improve the result!. In addition to translating the header (the image), we can apply other animations such as increasing the scale or applying Blur effect to the image for example.

NumericUpDown

And we only have to create the two custom controls to complete the UI Challenge!. Let’s start with the NumericUpDown. We can create custom controls using other Xamarin.Forms elements and relying on Bindable Properties to get quite surprising results.

In the case of the NumericUpDown, we will create the control with a Layout and three Labels:

<pancakeview:PancakeView
     HeightRequest="24"
     CornerRadius="24"
     BorderThickness="1"
     BackgroundColor="White"
     BorderColor="Gray">
     <Grid>
          <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="Auto" />
          </Grid.ColumnDefinitions>
          <Label 
               x:Name="MinusButton"
               Grid.Column="0"
               Text="-"
               Margin="12, 0, 0, 0">
               <Label.GestureRecognizers>
                    <TapGestureRecognizer 
                         Tapped="MinusTapped" />
               </Label.GestureRecognizers>
          </Label>
          <Label 
               x:Name="ValueText"
               Grid.Column="1" />
          <Label 
               x:Name="PlusButton"
               Grid.Column="2"
               Text="+"
               Margin="0, 0, 12, 0">
               <Label.GestureRecognizers>
                    <TapGestureRecognizer 
                         Tapped="PlusTapped" />
               </Label.GestureRecognizers>
          </Label>
     </Grid>
</pancakeview:PancakeView>

In the code-behind of the control, we will have some Bindable Properties like:

  • Minimum
  • Maximum
  • Value
  • Step
public static readonly BindableProperty ValueProperty =
     BindableProperty.Create(nameof(Value), typeof(double), typeof(NumericUpDown), 1.0,
     propertyChanged: (bindable, oldValue, newValue) =>
     ((NumericUpDown)bindable).Value = (double)newValue,
     defaultBindingMode: BindingMode.TwoWay
);

public double Value
{
     get { return (double)GetValue(ValueProperty); }
     set { SetValue(ValueProperty, value); }
}

...

And we would use it in the following way:

<controls:NumericUpDown
     Minimum="1"
     Maximum="10"/>

NOTE: To have a reusable control we should go further. We would include properties to customize the control (Animate, NumericBackgroundColor, NumericBorderColor, NumericTextColor, NumericBorderThickness, NumericCornerRadius) as well as events (ValueChanged) and/or commands. In this case we have only added the basic properties necessary to replicate the UI.

Do you want to see the result?.

NumericUpDown

ToggleButton

The case of the ToggleButton will be extremely similar to the previous control. We will use a ContentView with some BindableProperties:

public static readonly BindableProperty CheckedImageProperty =
     BindableProperty.Create("CheckedImage", typeof(ImageSource), typeof(ToggleButton), null);

public ImageSource CheckedImage
{
     get { return (ImageSource)GetValue(CheckedImageProperty); }
     set { SetValue(CheckedImageProperty, value); }
}

public static readonly BindableProperty UnCheckedImageProperty =
     BindableProperty.Create("UnCheckedImage", typeof(ImageSource), typeof(ToggleButton), null);

public ImageSource UnCheckedImage
{
     get { return (ImageSource)GetValue(UnCheckedImageProperty); }
     set { SetValue(UnCheckedImageProperty, value); }
}

In this case, we do not use XAML since the visual appearance will be defined in the images of the CheckedImage and UnCheckedImage properties.

We manage the visual state and other properties when changing the value of the Checked property:

private static async void OnCheckedChanged(BindableObject bindable, object oldValue, object newValue)
{

}

And we would use it in the following way:

<controls:ToggleButton
     Checked="False"
     Animate="True"
     CheckedImage="fav.png"
     UnCheckedImage="nofav.png"/>

The result:

El resultado final

The sample is available on GitHub:

Ver GitHub

We have a UI Challenge not excessively complex but with a great variety of details where we have used:

  • Animations
  • Creating custom controls
  • Navigation
  • Styles
  • Parallax
  • PancakeView (shadows, corner radius, etc.)
  • VisualStateManager
  • Etc.

This article has been part of the #XamarinUIJuly initiative:

 

#XamarinUIJuly

It is fantastic to be able to contribute to the #XamarinUIJuly. Ooh, and if this article has been interesting for you, we are only on July 17, do not miss the rest of the incredible articles that will come out during the rest of the month!.

Más información

2 pensamientos en “[Xamarin UI Challenge] Art Plant Mall (English Version)

  1. Pingback: Xamarin.Forms美观的UI示例_Dotnet9

Deja un comentario