Flashy tabbar
This time we’ve implemented an awesome flashy tabbar concept by Cuberto.
The flashy tabbar
Note:This impementation of a tabbar is not the 100% equal to original one, but quite close.
This example displays a tabbar with a fancy look and feel. When you click on one of the tabs, the circle grow and disapear making an pulse effect. The icon of the selected tab start moving to the top and hides outside the tabbar. In same time, the tab’s title moving in the same direction and become visible, and, after small delay the small dot grow at the bottom.
The composition
Lets look on the source gif and recognise all parts that we need to implement step by step.
- White container with round bottom corners
- 4 tabs with icons and titles
- Pulse animation of the circle when we click on tab
- Set of effects which appears when the tab selected:
- Scrolling up the icon and a title
- Growing of the dot below the title
- Overlapping the icon by a shape with the color of the tabbar’s background
Initial setup
Lets setup a workspace and some resources that we need for further.
First of all we need to tell Fuse that we want to include our images for tabs icons as a part of a Bundle
.
So go to the .unoproj
file and add "**.png:Bundle"
into section Includes
.
{
"Packages": [
"Fuse",
"FuseJS"
],
"Includes": [
"*",
"**.png:Bundle"
]
}
This allows us to use relative path to the image Assets/image.png
.
Note Don’t forget to place the
Assets
folder in the project folder.
Next, go to the MainView.ux
and define color resources for accent and background colors. Than place ClientPanel
container and set Color="{Resource AccentColor}"
.
<App>
<float4 ux:Global="AccentColor" ux:Value="#1E1E6E" />
<float4 ux:Global="BarBgColor" ux:Value="#fff" />
<ClientPanel Color="{Resource AccentColor}">
<!-- tabbar will be here soon -->
</ClientPanel>
</App>
White container with round bottom corners
As we can see from the *.gif, the tabs inside the container align horizontally and place in a line. So we need
a StackPanel
with Orientation="Horizontal"
. Lets place it inside our ClientPanel
.
<StackPanel Orientation="Horizontal">
<Rectangle Layer="Background" Color="{Resource BarBgColor}" CornerRadius="0,0,15,15"/>
</StackPanel>
StackPanel
, by default, don’t have background. This means we need add it. The background has a white color and rounded corners.
The best shape for that is a Rectangle
. It has both needed properties: Color
and CornerRadius
. Color
we will take from resource, and the
CornerRadius
for bottom corners lets set equal to 15.
Note We dont want that our rectangle be computed as a child of the
StackPanel
(as a default behaviour). To prevent this, we need to set propertyLayer
toBackground
.
Now the panel fills the whole screen, so we need to add a bit of cosmetic settings. Lets set space between the tabs by ItemSpacing="15"
, set tabbar height by Height="50"
, place it at the bottom of the screen by Alignment="BottomCenter"
, add a free space around by Margin="10"
and a
bit space inside by Padding="15, 0"
.
<StackPanel Orientation="Horizontal" ItemSpacing="15" Height="50" Alignment="BottomCenter" Margin="10" Padding="15, 0">
<Rectangle Layer="Background" Color="{Resource BarBgColor}" CornerRadius="0,0,15,15"/>
<!-- tabs will be here -->
</StackPanel>
Tabs with icons and titles
What’s the tab bar without tabs? Each of them consisnt of an icon, title and a small dot. They are place one below another. Technically, tab consist of two blocks(icon and title with a dot), so they should placed inside two panels. The title will have accent color.
<StackPanel HitTestMode="LocalBoundsAndChildren">
<Panel Height="50">
<Image Alignment="Center" Size="24" />
</Panel>
<StackPanel Alignment="Center" ItemSpacing="5" Height="50">
<Text Color="{Resource AccentColor}"/>
<Circle ux:Name="dot" Size="0" Color="{Resource AccentColor}"/>
</StackPanel>
</StackPanel>
To make it visible, lets add an image and a text.
<StackPanel HitTestMode="LocalBoundsAndChildren">
<Panel Height="50">
<Image Alignment="Center" Size="24" File="Assets/Events.png"/>
</Panel>
<StackPanel Alignment="Center" ItemSpacing="5" Height="50">
<Text Color="{Resource AccentColor}" Value="Events"/>
<Circle ux:Name="dot" Size="0" Color="{Resource AccentColor}"/>
</StackPanel>
</StackPanel>
For now it’s ok that visible only icon.
Scrolling up the ison and a title
To achive a scrolling effect, the tab should be placed inside the scrollview. Scrolling must be called progammatically by clicking and user not
be able to scroll manually. To prevent user from scrolling we set property UserScroll="false"
. To make a tab clickable add Clicked
trigger and
set HitTestMode="LocalBoundsAndChildren"
to make the whole tab clickabel. Finally, lets allow only vertical scroll direction (just to be sure
that it will looks good) by setting the property AllowedScrollDirections="Vertical"
.
<ScrollView ux:Name="scroll" UserScroll="false" AllowedScrollDirections="Vertical">
<StackPanel ux:Name="tab" HitTestMode="LocalBoundsAndChildren">
<!-- tab body -->
<Clicked>
<!-- do smth on click -->
</Clicked>
</StackPanel>
</ScrollView>
For testing that “scroll-by-click” really works, lets add to Clicked
the ScrollTo
action.
<Clicked>
<ScrollTo Target="scroll" RelativePosition="1" />
</Clicked>
After clicking on a tab, the icon will be changed to the text. But it is not exactly what we want.
Make tabs selectable
One tab is good, but many tabs better. Cause we have several of them, and technically they equal, the best solution would be create a reusable
class. Lets call it TabBarItem
. Root item could be a Panel
. In addition we need to create two string properties for the icon file path and
for the title text, and bind to related attributes by {ReadProperty this.XXX}
expression.
<Panel ux:Class="TabBarItem">
<string ux:Property="IconFilePath"/>
<string ux:Property="Title"/>
<ScrollView ux:Name="scroll" UserScroll="false" AllowedScrollDirections="Vertical">
<StackPanel ItemSpacing="10" Alignment="Top" Width="100%" HitTestMode="LocalBoundsAndChildren">
<Image ux:Name="icon" File="{ReadProperty this.IconFilePath}" Alignment="Center" Size="24"/>
<StackPanel Alignment="Center" ItemSpacing="5" Height="50">
<Text Value="{ReadProperty this.Title}" Color="{Resource AccentColor}"/>
<Circle ux:Name="dot" Size="0" Color="{Resource AccentColor}" Anchor="50%,50%"/>
</StackPanel>
<Clicked>
</Clicked>
</StackPanel>
</ScrollView>
</Panel>
Next, we can use our class to create the needed tabs and place them inside the container.
<ClientPanel Color="{Resource AccentColor}">
<StackPanel Orientation="Horizontal" ItemSpacing="15" Height="50" Alignment="BottomCenter" Margin="10" Padding="15, 0">
<Rectangle Layer="Background" Color="{Resource BarBgColor}" CornerRadius="0,0,15,15"/>
<TabBarItem IconFilePath="Assets/Events.png" Title="Events" />
<TabBarItem IconFilePath="Assets/Search.png" Title="Search" />
<TabBarItem IconFilePath="Assets/Highlights.png" Title="Actions" />
<TabBarItem IconFilePath="Assets/Settings.png" Title="Settings" />
</StackPanel>
</ClientPanel>
To make our tabs selectable, we need to add Selectable
component to the TabBarItem
class
Note If the
Value
property of theSelectable
component not be setted, the selection functionality won’t work at all. For this you can use any property that can guarantee uniqueness of the tab. In our case it isTitle
andIconFilePath
. Lets takeTitle
.
<Panel ux:Class="TabBarItem">
<string ux:Property="IconFilePath"/>
<string ux:Property="Title"/>
<Selectable Value="{ReadProperty this.Title}"/>
<!-- rest markup -->
<Clicked>
<ToggleSelection />
</Clicked>
and the Selection
component to its parent. According to original gif we know that only one tab can be selected, we need to prevent multiple
selectio. For that we set property MaxCount=1
o the Selection
component.
<!-- rest markup -->
<Rectangle Layer="Background" Color="{Resource BarBgColor}" CornerRadius="0,0,15,15"/>
<Selection MaxCount="1"/>
<!-- rest markup -->
To visualize some how that the tab was selected we can use the WhileSelected
trigger. Inside it we place our ScrollTo
action from above.
<Panel ux:Class="TabBarItem">
<string ux:Property="IconFilePath"/>
<string ux:Property="Title"/>
<Selectable Value="{ReadProperty this.Title}"/>
<WhileSelected>
<ScrollTo Target="scroll" RelativePosition="1" />
</WhileSelected>
<!-- rest -->
Nice, but even when the tab change state to deselected (after clicking on the other tab) the visual appearence stay the same. It’s because we don’t
have a trigger that will be activated when the tab become deselected. Lets add one more WhileSelected
trigger but set its Invert
property to True
. Inside it place the ScrollTo
action with RelativePosition="0"
.
<!-- rest -->
<WhileSelected Invert="true">
<ScrollTo Target="scroll" RelativePosition="0" />
</WhileSelected>
<!-- rest -->
Tune up
Technically we achive almost everything. Now it’s time to polish a bit look and feel.
Bounce effect of the scrolling
If we look closer, we can see the bouncing effect after scrolling. As we don’t need such behaviour, lets remove it. By adding a ScrollViewMotion
component to the ScrollView
we can change the default behaviour. For make a snap smooth set property GotoType="SmoothSnap"
, to remove the
bounce effect set property SnapDuration="0"
, to make scrolling a bit faster set GotoDuration="0.3"
and, finally, to make the scroll animation
feels more naturally set property GotoEasing="CubicInOut"
.
<!-- rest -->
<ScrollView ux:Name="scroll" UserScroll="false" AllowedScrollDirections="Vertical">
<ScrollViewMotion GotoType="SmoothSnap" SnapDuration="0" GotoDuration="0.3" GotoEasing="CubicInOut"/>
<!-- rest -->
Growing of the dot below the title
This dot is the Circle
that placed below the title. To make it bigger (initial size is 0) when the tab become selected, let use Change
animator
and animate the circle’s propery Size="5"
. For that we need to set the name of the circle by setting property ux:Name="dot"
.
The Change
animator must be placed inside non-inverted WhileSelected
trigger. Duration
of the growing animation let be equl to 0.2
sec. Because the scrolling is not instance process, we can not see the animation right after it start, so we need to set a small Delay
,
for example 0.2
sec.
<WhileSelected>
<!-- rest -->
<Change dot.Size="5" Duration=".2" Delay="0.2" Easing="CubicIn"/>
<!-- rest -->
and
<Circle ux:Name="dot" Size="0" Color="{Resource AccentColor}" Anchor="50%,50%"/>
Pulse animation of the circle when we click on tab
To make an pulse effect lets take a Circle
and place it at the bottom of a root of our TabBarItem
class(right under the closing tag of the ScrollView
). The initial Size
have to be 0
, the ux:Name
is pulse
and Alignment
is Center
. Beside, our pulse should be as a wave on the
water surface. This mean, circle have to have only a Stroke
with the Width="3"
and Color="{Resource AccentColor}"
.
</ScrollView>
<Circle ux:Name="pulse" Size="0" Layer="Overlay" Alignment="Center">
<Stroke Width="3" Color="{Resource AccentColor}"/>
</Circle>
</Paanel>
To make our Circle
lets add a few Change
’s to the Clicked
. Duration
can be 0.2
sec. The maximum Size
value equla to 80%
of the parents
size (that would be enough). Because we not need a backward animation of the pulse on deselection, lets set DurationBack
to 0
. Changing
Opacity
to 0
during growing, makes the look and feel nicer.
<Clicked>
<ToggleSelection />
<Change pulse.Size="80%" Duration=".2" DurationBack="0"/>
<Change pulse.Opacity="0" Duration=".2" DurationBack="0"/>
</Clicked>
Overlapping the icon by a shape with the color of the tabbar’s background
For overlapping effect we will use a Circle
. Again. Lets create a panel and move inside it our icon and this Circle
. To follow the consistency,
set to Panel
’s Height
value equal to 50
. Set ux:Name="circle"
to overlapper. Align it to a left-bottom corner and make a small offset to the same direction, because without it the small piece of some icons will be visible (not good).
<StackPanel ItemSpacing="10" Alignment="Top" Width="100%" HitTestMode="LocalBoundsAndChildren">
<Panel Height="50">
<Image ux:Name="icon" File="{ReadProperty this.IconFilePath}" Alignment="Center" Size="24">
<Circle ux:Name="circle" Size="0" Color="{Resource BarBgColor}" Layer="Overlay" Alignment="BottomLeft" Offset="-5,5"/>
</Image>
</Panel>
<!-- rest -->
As we did before, add a Change
to the WhileSelected
<Change circle.Size="40" Duration=".3" />
The effect looks better in deselection phase.
That’s it. Hope you enjoyed this journey!
Full code here
<App>
<float4 ux:Global="AccentColor" ux:Value="#1E1E6E" />
<float4 ux:Global="BarBgColor" ux:Value="#fff" />
<Panel ux:Class="TabBarItem">
<string ux:Property="IconFilePath"/>
<string ux:Property="Title"/>
<Selectable Value="{ReadProperty this.Title}"/>
<WhileSelected>
<ScrollTo Target="scroll" RelativePosition="1" />
<Change dot.Size="5" Duration=".2" Delay="0.2" Easing="CubicIn"/>
<Change circle.Size="40" Duration=".3" />
</WhileSelected>
<WhileSelected Invert="true">
<ScrollTo Target="scroll" RelativePosition="0" />
</WhileSelected>
<ScrollView ux:Name="scroll" UserScroll="false" AllowedScrollDirections="Vertical">
<ScrollViewMotion GotoType="SmoothSnap" SnapDuration="0" GotoDuration="0.3" GotoEasing="CubicInOut"/>
<StackPanel ItemSpacing="10" Alignment="Top" Width="100%" HitTestMode="LocalBoundsAndChildren">
<Panel Height="50">
<Image ux:Name="icon" File="{ReadProperty this.IconFilePath}" Alignment="Center" Size="24">
<Circle ux:Name="circle" Size="0" Color="{Resource BarBgColor}" Layer="Overlay" Alignment="BottomLeft" Offset="-5,5"/>
</Image>
</Panel>
<StackPanel Alignment="Center" ItemSpacing="5" Height="50">
<Text Value="{ReadProperty this.Title}" Color="{Resource AccentColor}"/>
<Circle ux:Name="dot" Size="0" Color="{Resource AccentColor}" Anchor="50%,50%"/>
</StackPanel>
<Clicked>
<ToggleSelection />
<Change pulse.Size="80%" Duration=".2" DurationBack="0"/>
<Change pulse.Opacity="0" Duration=".2" DurationBack="0"/>
</Clicked>
</StackPanel>
</ScrollView>
<Circle ux:Name="pulse" Size="0" Layer="Overlay" Alignment="Center">
<Stroke Width="3" Color="{Resource AccentColor}"/>
</Circle>
</Panel>
<ClientPanel Color="{Resource AccentColor}">
<StackPanel Orientation="Horizontal" ItemSpacing="15" Height="50" Alignment="BottomCenter" Margin="10" Padding="15, 0">
<Rectangle Layer="Background" Color="{Resource BarBgColor}" CornerRadius="0,0,15,15"/>
<Selection MaxCount="1"/>
<TabBarItem IconFilePath="Assets/Events.png" Title="Events" />
<TabBarItem IconFilePath="Assets/Search.png" Title="Search" />
<TabBarItem IconFilePath="Assets/Highlights.png" Title="Actions" />
<TabBarItem IconFilePath="Assets/Settings.png" Title="Settings" />
</StackPanel>
</ClientPanel>
</App>