Jekyll2022-07-24T23:58:07+10:00https://kymphillpotts.com/Kym’s BlogI write about Azure, Xamarin.Forms, Connected Apps and whatever else might be on my mind.ClassFly UI - XAML vs Blazor - Part 1 - XAML2022-07-23T10:00:00+10:002022-07-23T10:00:00+10:00https://kymphillpotts.com/maui-ui-july<p>.NET MAUI gives you a lot of flexibility in how you create your applications. Not only can you target multiple different Operating System platforms (like iOS, Android, Windows, Mac, etc.), you can also target multiple different Form Factors (like phones, tablets, desktop, tv, watch, etc) and you can write your applications using C# Markup, XAML and even Blazor.</p>
<p>With so much choice at our fingertips it can be hard to work out which path to take.</p>
<iframe src="https://giphy.com/embed/3o85xIO33l7RlmLR4I" width="480" height="361" frameborder="0" class="giphy-embed" allowfullscreen=""></iframe>
<p><a href="https://giphy.com/gifs/yosub-girl-taco-why-not-both-3o85xIO33l7RlmLR4I"></a></p>
<p>Recently <a href="https://twitter.com/lachlanwgordon">Lachlan Gordon</a> and I have been playing around with Blazor and thought it would be interesting to compare the approaches of XAML vs Blazor. So we set about creating the same application UI using XAML and Blazor to discover the pros and cons.</p>
<blockquote>
<p>This is a 2 part blog post by <a href="https://twitter.com/lachlanwgordon">Lachlan Gordon</a> and myself for the <a href="https://goforgoldman.com/2022/05/19/maui-ui-july.html">.NET MAUI UI July</a> initiative kicked off by <a href="https://twitter.com/mattgoldman">Matt Goldman</a>.</p>
</blockquote>
<h3 id="ui-inspiration">UI Inspiration</h3>
<p>The design we’re implementing is a music teacher’s app that we found on dribbble, created by Sumit Choudhary. You can check it out at <a href="https://dribbble.com/shots/17453615-Classfly-Music-app-concept">https://dribbble.com/shots/17453615-Classfly-Music-app-concept</a></p>
<p><img src="assets/images/posts/mauiuijuly2022/design.png" /></p>
<p>It’s a nice looking design, but not anything too crazy - which is exactly what we were after; an app design which represents a pretty common pattern.</p>
<h3 id="the-plan">The Plan</h3>
<p>So here is the plan. I’m going to break down the design of the app from a XAML perspective. Lachlan is going to break down implementing the UI in MAUI Blazor over on his blog <a href="https://www.lachlanwgorgon.com">https://www.lachlanwgorgon.com</a> and then we will provide a blog post summarizing the pros and cons.</p>
<iframe src="https://giphy.com/embed/ZbOXZEugwT26awakGe" width="480" height="480" frameborder="0" class="giphy-embed" allowfullscreen=""></iframe>
<p><br /></p>
<h2 id="project-structure">Project Structure</h2>
<p>One of the beauties of working in a environment like .NET is code sharing, and for us it means we can have a shared NetStandard library that has our Models, Data Services, and any business logic, which is shared between the XAML version and the Blazor version.</p>
<p><img src="assets/images/posts/mauiuijuly2022/solution_structure.png" alt="Solution Structure" /></p>
<blockquote>
<p><strong>TIP:</strong> Use NetStandard libraries to share code independent of UI implementation</p>
</blockquote>
<p>Okay, enough rambling, let’s get into the layouts…</p>
<h2 id="course-list-page">Course List Page</h2>
<p>Looking at the first page, it’s a pretty standard mobile looking screen. It has a title, a list of items and a tab bar at the bottom.</p>
<p><img src="assets/images/posts/mauiuijuly2022/courselistlayout.png" alt="Course List Page" /></p>
<p>Let’s look into some of the interesting bits.</p>
<h3 id="title">Title</h3>
<p>For the purposes of this exercise I used the <code class="highlighter-rouge">Shell.TitleView</code> element with a simple grid containing a <code class="highlighter-rouge">Label</code> and an <code class="highlighter-rouge">Image</code>.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Shell.TitleView></span>
<span class="nt"><Grid</span> <span class="na">Margin=</span><span class="s">"10,0,10,0"</span> <span class="na">ColumnDefinitions=</span><span class="s">"*,30"</span><span class="nt">></span>
<span class="nt"><Label</span>
<span class="na">Style=</span><span class="s">"{StaticResource CourseHeaderText}"</span>
<span class="na">Text=</span><span class="s">"Choose a course"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"><Image</span>
<span class="na">Grid.Column=</span><span class="s">"1"</span>
<span class="na">HeightRequest=</span><span class="s">"28"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Source=</span><span class="s">"menu.png"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span>
<span class="na">WidthRequest=</span><span class="s">"28"</span> <span class="nt">/></span>
<span class="nt"></Grid></span>
<span class="nt"></Shell.TitleView></span>
</code></pre></div></div>
<p>One thing I learnt here is that you should specify the full source name in an image including the extension. It works fine without the extension on Android but on Windows it won’t display. Turns out this is a known issue: <a href="https://github.com/dotnet/maui/issues/3459">#3459 - [Windows] Images are not rendered if the extension is not specified</a></p>
<h3 id="search-bar">Search Bar</h3>
<p>To create the search bar I used a <code class="highlighter-rouge">SearchBar</code> with a <code class="highlighter-rouge">Border</code> around it, which seems obvious, but there were some interesting bits</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Border</span>
<span class="na">Grid.Row=</span><span class="s">"0"</span>
<span class="na">Margin=</span><span class="s">"10,0,10,0"</span>
<span class="na">Stroke=</span><span class="s">"#CCCCCC"</span>
<span class="na">StrokeShape=</span><span class="s">"RoundRectangle 20"</span>
<span class="na">StrokeThickness=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><SearchBar</span> <span class="na">x:Name=</span><span class="s">"CourseSearchBar"</span> <span class="na">Placeholder=</span><span class="s">"Search courses"</span><span class="nt">></span>
<span class="nt"><SearchBar.Behaviors></span>
<span class="nt"><toolkit:EventToCommandBehavior</span>
<span class="na">Command=</span><span class="s">"{Binding SearchTermCommand}"</span>
<span class="na">CommandParameter=</span><span class="s">"{Binding Text, Source={x:Reference CourseSearchBar}}"</span>
<span class="na">EventName=</span><span class="s">"TextChanged"</span> <span class="nt">/></span>
<span class="nt"></SearchBar.Behaviors></span>
<span class="nt"></SearchBar></span>
<span class="nt"></Border></span>
</code></pre></div></div>
<p>First, there is a bug where the search bar does not size correctly on Android <a href="https://github.com/dotnet/maui/issues/7137">#7137 - [Android] [ScenarioDay] The default size of SearchBar is wrong, it does not fill the space</a>. I could work around this with a bit of code which forces a width when the size is allocated</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="n">DeviceInfo</span><span class="p">.</span><span class="n">Platform</span> <span class="p">==</span> <span class="n">DevicePlatform</span><span class="p">.</span><span class="n">Android</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">width</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span>
<span class="n">CourseSearchBar</span><span class="p">.</span><span class="n">WidthRequest</span> <span class="p">=</span> <span class="n">width</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In reality I should probably deal with the padding and margins of the layouts around it. but as a quick hack, did the job.</p>
<p>The other thing I found limiting about the <code class="highlighter-rouge">SearchBar</code> is that even though it has command for Search, it’s not enough, for example, as far as I could tell there isn’t a ClearCommand and there is no command (only an event) for TextChanged. So in the end what worked best was using the <code class="highlighter-rouge">EventToCommandBehaviour</code> in the <code class="highlighter-rouge">Maui Community Tookit</code> to issue the SearchCommand when the <code class="highlighter-rouge">TextChanged</code> event occurred.</p>
<h3 id="collectionview">CollectionView</h3>
<p>If you have ever used the <code class="highlighter-rouge">CollectionView</code> in Xamarin.Forms then you pretty much know how to use the <code class="highlighter-rouge">CollectionView</code> in MAUI.</p>
<p><img src="assets/images/posts/mauiuijuly2022/cells.png" alt="CollectionView Rows" /></p>
<p>The rows (cells) in the design call for a couple of interesting things:</p>
<ol>
<li>Borders and Shadows around the cell</li>
<li>Alternating background colours behind the instrument image.</li>
</ol>
<p>The <code class="highlighter-rouge">DataTemplate</code> for the cells is <code class="highlighter-rouge">Grid</code> of two columns surrounded by a border. Images in the first column and a <code class="highlighter-rouge">StackLayout</code> with 3 labels in the second column</p>
<p><img src="assets/images/posts/mauiuijuly2022/collectionviewcell.png" alt="CollectionView Cell" /></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Border</span>
<span class="na">Padding=</span><span class="s">"16,8"</span>
<span class="na">Background=</span><span class="s">"White"</span>
<span class="na">Stroke=</span><span class="s">"#CCCCCC"</span>
<span class="na">StrokeShape=</span><span class="s">"RoundRectangle 20"</span>
<span class="na">StrokeThickness=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><Border.Shadow></span>
<span class="nt"><Shadow</span>
<span class="na">Brush=</span><span class="s">"{StaticResource Gray500}"</span>
<span class="na">Opacity=</span><span class="s">".15"</span>
<span class="na">Radius=</span><span class="s">"10"</span>
<span class="na">Offset=</span><span class="s">"5,5"</span> <span class="nt">/></span>
<span class="nt"></Border.Shadow></span>
<span class="nt"><Border.GestureRecognizers></span>
<span class="nt"><TapGestureRecognizer</span>
<span class="na">Command=</span><span class="s">"{Binding Source={RelativeSource AncestorType={x:Type vm:ClassListViewModel}},
Path=GoToCourseDetailsCommand}"</span>
<span class="na">CommandParameter=</span><span class="s">"{Binding .}"</span> <span class="nt">/></span>
<span class="nt"></Border.GestureRecognizers></span>
<span class="nt"><Grid</span>
<span class="na">ColumnDefinitions=</span><span class="s">"Auto,*"</span>
<span class="na">ColumnSpacing=</span><span class="s">"20"</span>
<span class="na">InputTransparent=</span><span class="s">"True"</span>
<span class="na">IsClippedToBounds=</span><span class="s">"False"</span><span class="nt">></span>
<span class="nt"><Image</span>
<span class="na">HeightRequest=</span><span class="s">"80"</span>
<span class="na">Source=</span><span class="s">"{Binding ., Converter={StaticResource IndexToBackgroundImageConverter},
ConverterParameter={x:Reference CourseCollectionView}}"</span>
<span class="na">WidthRequest=</span><span class="s">"80"</span> <span class="nt">/></span>
<span class="nt"><Image</span>
<span class="na">HeightRequest=</span><span class="s">"80"</span>
<span class="na">Source=</span><span class="s">"{Binding Image}"</span>
<span class="na">WidthRequest=</span><span class="s">"80"</span> <span class="nt">/></span>
<span class="nt"><VerticalStackLayout</span> <span class="na">Grid.Column=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource CourseHeaderText}"</span> <span class="na">Text=</span><span class="s">"{Binding Name}"</span> <span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource CourseSmallText}"</span> <span class="na">Text=</span><span class="s">"in class"</span> <span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource CourseSmallText}"</span> <span class="na">Text=</span><span class="s">"{Binding MemberCount, StringFormat='{0} Members'}"</span> <span class="nt">/></span>
<span class="nt"></VerticalStackLayout></span>
<span class="nt"></Grid></span>
<span class="nt"></Border></span>
</code></pre></div></div>
<p>The alternating rows have a different colour squiggle behind the instrument which is achieved by having a different background image on odd and even cells determined by a Converter.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ContentPage.Resources></span>
<span class="nt"><ResourceDictionary></span>
<span class="nt"><converters:IndexToObjectConverter</span>
<span class="na">x:Key=</span><span class="s">"IndexToBackgroundImageConverter"</span>
<span class="na">EvenObject=</span><span class="s">"highlight_green.png"</span>
<span class="na">OddObject=</span><span class="s">"highlight_yellow.png"</span> <span class="nt">/></span>
...
</code></pre></div></div>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">IndexToObjectConverter</span> <span class="p">:</span> <span class="n">IValueConverter</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">object</span> <span class="n">EvenObject</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">object</span> <span class="n">OddObject</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">object</span> <span class="nf">Convert</span><span class="p">(</span><span class="kt">object</span> <span class="k">value</span><span class="p">,</span> <span class="n">Type</span> <span class="n">targetType</span><span class="p">,</span> <span class="kt">object</span> <span class="n">parameter</span><span class="p">,</span> <span class="n">CultureInfo</span> <span class="n">culture</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">index</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">itemSource</span> <span class="p">=</span> <span class="p">((</span><span class="n">CollectionView</span><span class="p">)</span><span class="n">parameter</span><span class="p">).</span><span class="n">ItemsSource</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">itemSource</span> <span class="k">is</span> <span class="n">IList</span> <span class="n">list</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">index</span> <span class="p">=</span> <span class="n">list</span><span class="p">.</span><span class="nf">IndexOf</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">index</span> <span class="p">=</span> <span class="p">((</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">Object</span><span class="p">>)</span><span class="n">itemSource</span><span class="p">).</span><span class="nf">ToList</span><span class="p">().</span><span class="nf">IndexOf</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">index</span> <span class="p">%</span> <span class="m">2</span> <span class="p">==</span> <span class="m">0</span> <span class="p">?</span> <span class="n">EvenObject</span> <span class="p">:</span> <span class="n">OddObject</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">object</span> <span class="nf">ConvertBack</span><span class="p">(</span><span class="kt">object</span> <span class="k">value</span><span class="p">,</span> <span class="n">Type</span> <span class="n">targetType</span><span class="p">,</span> <span class="kt">object</span> <span class="n">parameter</span><span class="p">,</span> <span class="n">CultureInfo</span> <span class="n">culture</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Using a converter like this is one way to do alternating rows and because we are just changing one thing (the image source) it’s fine. Another approach is to use a DataTemplateSelector if you want to have completely different styled alternating rows. A third approach is to convince your designer not to do alternating rows. 😀</p>
<h4 id="tab-bar">Tab Bar</h4>
<p>For the purposes of this exercise I did not get around to doing a proper TabBar for the design. Ultimately I’d like to be able to use a customized version of the Shell Tabs but that is for another article. So instead I used just a grid with some buttons and a border with a gradient.</p>
<p><img src="assets/images/posts/mauiuijuly2022/tabbar.png" alt="TabBar" /></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- resources --></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"TabBarDark"</span><span class="nt">></span>#384054<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"TabBarLight"</span><span class="nt">></span>#505A70<span class="nt"></Color></span>
<span class="nt"><LinearGradientBrush</span> <span class="na">x:Key=</span><span class="s">"TabBarGradient"</span> <span class="na">StartPoint=</span><span class="s">"0,0"</span> <span class="na">EndPoint=</span><span class="s">"1,1"</span><span class="nt">></span>
<span class="nt"><GradientStop</span> <span class="na">Offset=</span><span class="s">"0.1"</span> <span class="na">Color=</span><span class="s">"{StaticResource TabBarLight}"</span> <span class="nt">/></span>
<span class="nt"><GradientStop</span> <span class="na">Offset=</span><span class="s">"1.0"</span> <span class="na">Color=</span><span class="s">"{StaticResource TabBarDark}"</span> <span class="nt">/></span>
<span class="nt"></LinearGradientBrush></span>
<span class="c"><!-- tab bar --></span>
<span class="nt"><Border</span>
<span class="na">Grid.Row=</span><span class="s">"2"</span>
<span class="na">Margin=</span><span class="s">"-1"</span>
<span class="na">Padding=</span><span class="s">"20"</span>
<span class="na">Background=</span><span class="s">"{StaticResource TabBarGradient}"</span>
<span class="na">Stroke=</span><span class="s">"#CCCCCC"</span>
<span class="na">StrokeShape=</span><span class="s">"RoundRectangle 20,20,0,0"</span>
<span class="na">StrokeThickness=</span><span class="s">"0"</span><span class="nt">></span>
<span class="nt"><Grid</span>
<span class="na">ColumnDefinitions=</span><span class="s">"*,*,*,*"</span>
<span class="na">HeightRequest=</span><span class="s">"30"</span><span class="nt">></span>
<span class="nt"><ImageButton</span> <span class="err">...</span>
<span class="err"><ImageButton</span> <span class="err">...</span>
<span class="err"><ImageButton</span> <span class="err">...</span>
<span class="err"><ImageButton</span> <span class="err">...</span>
<span class="err"></Grid</span><span class="nt">></span>
<span class="nt"></Border></span>
</code></pre></div></div>
<blockquote>
<p>Notice that Margin of -1 in the border, this is a workaround for a known bug <a href="https://github.com/dotnet/maui/issues/7764">#7764 - Border Margin</a></p>
</blockquote>
<h2 id="course-details-page">Course Details Page</h2>
<p>The basic layout of this page a TitleView (similar to the first page), then a Grid with two rows for the top image and the CollectionView panel at the bottom.</p>
<p><img src="assets/images/posts/mauiuijuly2022/coursedetails.png" alt="Course Details Page" /></p>
<h3 id="titleview">TitleView</h3>
<p>The <code class="highlighter-rouge">TitleView</code> is exactly the same as on the first page with the slight exception that the text on the <code class="highlighter-rouge">Label</code> is bound to the Selected <code class="highlighter-rouge">Course.Name</code> property.</p>
<h3 id="top-section">Top Section</h3>
<p>The top image isn’t particularly interesting other than we can use things like a <code class="highlighter-rouge">LinearGradientBrush</code> and a <code class="highlighter-rouge">Border</code> to overlay some fading over top of the image.</p>
<p><img src="assets/images/posts/mauiuijuly2022/topcoursedetails.png" alt="Top Image" /></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><LinearGradientBrush</span> <span class="na">x:Key=</span><span class="s">"WhiteFadeGradient"</span> <span class="na">StartPoint=</span><span class="s">"0,0"</span> <span class="na">EndPoint=</span><span class="s">"0,1"</span><span class="nt">></span>
<span class="nt"><GradientStop</span> <span class="na">Offset=</span><span class="s">"0.0"</span> <span class="na">Color=</span><span class="s">"White"</span> <span class="nt">/></span>
<span class="nt"><GradientStop</span> <span class="na">Offset=</span><span class="s">"1.0"</span> <span class="na">Color=</span><span class="s">"#00FFFFFF"</span> <span class="nt">/></span>
<span class="nt"></LinearGradientBrush></span>
...
<span class="nt"><Grid</span> <span class="na">RowDefinitions=</span><span class="s">".25*, .75*"</span><span class="nt">></span>
<span class="nt"><Image</span>
<span class="na">Margin=</span><span class="s">"0,0,0,-20"</span>
<span class="na">Aspect=</span><span class="s">"AspectFill"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Fill"</span>
<span class="na">Source=</span><span class="s">"piano_large.jpg"</span>
<span class="na">VerticalOptions=</span><span class="s">"Start"</span> <span class="nt">/></span>
<span class="nt"><Border</span>
<span class="na">Grid.Row=</span><span class="s">"0"</span>
<span class="na">Margin=</span><span class="s">"-2"</span>
<span class="na">Background=</span><span class="s">"{StaticResource WhiteFadeGradient}"</span>
<span class="na">HeightRequest=</span><span class="s">"30"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Fill"</span>
<span class="na">Stroke=</span><span class="s">"#CCCCCC"</span>
<span class="na">StrokeShape=</span><span class="s">"Rectangle"</span>
<span class="na">StrokeThickness=</span><span class="s">"0"</span>
<span class="na">VerticalOptions=</span><span class="s">"Start"</span> <span class="nt">/></span>
</code></pre></div></div>
<h3 id="bottom-card">Bottom Card</h3>
<p><img src="assets/images/posts/mauiuijuly2022/bottomcard.png" alt="Bottom Card" /></p>
<p>The bottom card shape is done with yet another <code class="highlighter-rouge">Border</code> to create the card shape.</p>
<p>The contents of the bottom card are contained within a grid with rows for the Card Header and the CollectionView</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Border</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span>
<span class="na">Margin=</span><span class="s">"-2"</span>
<span class="na">Padding=</span><span class="s">"16,16"</span>
<span class="na">Background=</span><span class="s">"White"</span>
<span class="na">Stroke=</span><span class="s">"#CCCCCC"</span>
<span class="na">StrokeShape=</span><span class="s">"RoundRectangle 20,20,0,0"</span>
<span class="na">StrokeThickness=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><Border.Shadow></span>
<span class="nt"><Shadow</span>
<span class="na">Brush=</span><span class="s">"Black"</span>
<span class="na">Opacity=</span><span class="s">"0.2"</span>
<span class="na">Radius=</span><span class="s">"20"</span>
<span class="na">Offset=</span><span class="s">"-20,-20"</span> <span class="nt">/></span>
<span class="nt"></Border.Shadow></span>
<span class="c"><!-- Card Contents--></span>
<span class="nt"><Grid</span> <span class="na">RowDefinitions=</span><span class="s">"60,*"</span><span class="nt">></span>
<span class="c"><!-- Card Header --></span>
<span class="nt"><Image</span>
<span class="na">Aspect=</span><span class="s">"Fill"</span>
<span class="na">HeightRequest=</span><span class="s">"20"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Start"</span>
<span class="na">Source=</span><span class="s">"highlight_heading.png"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span>
<span class="na">WidthRequest=</span><span class="s">"200"</span> <span class="nt">/></span>
<span class="nt"><Label</span>
<span class="na">Grid.Row=</span><span class="s">"0"</span>
<span class="na">Style=</span><span class="s">"{StaticResource CourseHeaderText}"</span>
<span class="na">Text=</span><span class="s">"Select Mentor"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"><Rectangle</span>
<span class="na">Fill=</span><span class="s">"{StaticResource Gray100}"</span>
<span class="na">HeightRequest=</span><span class="s">"2"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">VerticalOptions=</span><span class="s">"End"</span> <span class="nt">/></span>
<span class="c"><!-- Mentor List --></span>
<span class="nt"><CollectionView</span> <span class="err">...</span>
<span class="na">x:Name=</span><span class="s">"MentorsCollectionView"</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span>
<span class="na">Margin=</span><span class="s">"0,20,0,0"</span>
<span class="na">ItemsSource=</span><span class="s">"{Binding Course.Mentors}"</span><span class="nt">></span>
<span class="nt"><CollectionView.ItemsLayout></span>
<span class="nt"><LinearItemsLayout</span> <span class="na">ItemSpacing=</span><span class="s">"10"</span> <span class="na">Orientation=</span><span class="s">"Vertical"</span> <span class="nt">/></span>
<span class="nt"></CollectionView.ItemsLayout></span>
<span class="nt"><CollectionView.ItemTemplate></span>
<span class="nt"><DataTemplate></span>
...
<span class="nt"></DataTemplate></span>
<span class="nt"></CollectionView.ItemTemplate></span>
<span class="nt"></CollectionView></span>
<span class="nt"></Grid></span>
<span class="nt"></Border></span>
</code></pre></div></div>
<h3 id="mentor-cell">Mentor cell</h3>
<p><img src="assets/images/posts/mauiuijuly2022/mentorcell.png" alt="Mentor Cell" /></p>
<p>The layout of the Mentor cell is a pretty straight forward <code class="highlighter-rouge">Grid</code> so not much to discuss there. The interesting parts are the alternating background colors and “cutout” behind the profile photos.</p>
<p>The colors are done using a alternating row converter just like in the first page, but this time it returns a color rather than an image.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"EvenRowColor"</span><span class="nt">></span>#278C7E<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"OddRowColor"</span><span class="nt">></span>#FFD051<span class="nt"></Color></span>
...
<span class="nt"><converters:IndexToObjectConverter</span>
<span class="na">x:Key=</span><span class="s">"IndexToBackgroundColorConverter"</span>
<span class="na">EvenObject=</span><span class="s">"{StaticResource EvenRowColor}"</span>
<span class="na">OddObject=</span><span class="s">"{StaticResource OddRowColor}"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>The “cutout” section is done through an <code class="highlighter-rouge">Image.Clip</code> which clips just the bottom corners of the image to match the corners of the background <code class="highlighter-rouge">RoundRectangle</code>.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><DataTemplate></span>
<span class="nt"><Grid</span> <span class="na">ColumnDefinitions=</span><span class="s">"60,5,*,20"</span> <span class="na">ColumnSpacing=</span><span class="s">"15"</span><span class="nt">></span>
<span class="nt"><RoundRectangle</span>
<span class="na">Grid.Column=</span><span class="s">"0"</span>
<span class="na">CornerRadius=</span><span class="s">"10"</span>
<span class="na">Fill=</span><span class="s">"{Binding ., Converter={StaticResource IndexToBackgroundColorConverter},
ConverterParameter={x:Reference MentorsCollectionView}}"</span>
<span class="na">HeightRequest=</span><span class="s">"50"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">VerticalOptions=</span><span class="s">"End"</span>
<span class="na">WidthRequest=</span><span class="s">"50"</span> <span class="nt">/></span>
<span class="nt"><Image</span>
<span class="na">HeightRequest=</span><span class="s">"60"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Source=</span><span class="s">"{Binding Image}"</span>
<span class="na">VerticalOptions=</span><span class="s">"End"</span>
<span class="na">WidthRequest=</span><span class="s">"60"</span><span class="nt">></span>
<span class="nt"><Image.Clip></span>
<span class="nt"><RoundRectangleGeometry</span> <span class="na">CornerRadius=</span><span class="s">"10"</span> <span class="na">Rect=</span><span class="s">"5,0,50,60"</span> <span class="nt">/></span>
<span class="nt"></Image.Clip></span>
<span class="nt"></Image></span>
<span class="c"><!-- vertical divider line --></span>
<span class="nt"><Rectangle</span>
<span class="na">Grid.Column=</span><span class="s">"1"</span>
<span class="na">Fill=</span><span class="s">"{StaticResource Gray100}"</span>
<span class="na">HeightRequest=</span><span class="s">"30"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span>
<span class="na">WidthRequest=</span><span class="s">"2"</span> <span class="nt">/></span>
<span class="nt"><VerticalStackLayout</span> <span class="na">Grid.Column=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource CourseHeaderText}"</span> <span class="na">Text=</span><span class="s">"{Binding Name}"</span> <span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource CourseSmallText}"</span>
<span class="na">Text=</span><span class="s">"{Binding FollowerCount, StringFormat='{0} Followers'}"</span> <span class="nt">/></span>
<span class="nt"></VerticalStackLayout></span>
<span class="nt"><Image</span>
<span class="na">Grid.Column=</span><span class="s">"3"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Source=</span><span class="s">"right.png"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"></Grid></span>
<span class="nt"></DataTemplate></span>
</code></pre></div></div>
<h2 id="summary">Summary</h2>
<p>Okay, so thats about it for this blog post… I’m already behind schedule for MAUI UI July. The follow up to this is an article by Lachlan on how to implement the same thing in MAUI Blazor (I’ll post a link here when that’s available). Then stay tuned for a shared article where we discuss the advantages and disadvantages of using Blazor layout vs XAML layouts.</p>
<p>Till then, please check out the other <a href="https://goforgoldman.com/2022/05/19/maui-ui-july.html">.NET MAUI UI July</a> articles and thanks again to <a href="https://twitter.com/mattgoldman">Matt Goldman</a> for organizing.</p>
<h2 id="source-code">Source Code</h2>
<p>If you want to check out the source code, it’s available over at https://github.com/kphillpotts/MAUI-UI-July</p>Kym Phillpotts.NET MAUI gives you a lot of flexibility in how you create your applications. Not only can you target multiple different Operating System platforms (like iOS, Android, Windows, Mac, etc.), you can also target multiple different Form Factors (like phones, tablets, desktop, tv, watch, etc) and you can write your applications using C# Markup, XAML and even Blazor..NET Conf recap2021-12-05T11:00:00+11:002021-12-05T11:00:00+11:00https://kymphillpotts.com/dotnetconf-recap<p>There have been a tonne of announcements at .NET Conf 2021 including .NET 6, C# 10, Visual Studio 2022, and MAUI Preview 10. This post is a recap of the most important parts for Xamarin, Blazor and Maui developers. For those of you who don’t want to sit through hours of videos, I have watched the sessions and linked the resources so you don’t have to.</p>
<p>The purpose of this post is <strong>not</strong> to summarize the content from the various announcements, but rather point you (and me) to the best resources to get up to speed with all the new goodness. People like to consume different types of information, so I’ve broadly classified them into: Websites (🌐), Blogs and Articles (📄) and Videos (📺).</p>
<h1 id="net-conf">.NET Conf</h1>
<ul>
<li>🌐 <a href="https://www.dotnetconf.net/"><strong>.NET Conf Website</strong></a></li>
<li>📄 <a href="https://github.com/dotnet-presentations/dotNETConf/tree/master/2021/MainEvent/Technical"><strong>Session Slides and Videos</strong></a></li>
<li>📺 <a href="https://www.youtube.com/playlist?list=PLdo4fOcmZ0oVFtp9MDEBNbA2sSqYvXSXO"><strong>Session Replays</strong></a></li>
</ul>
<p><img src="assets/images/posts/dotnetConf2021/Slide3.PNG" /></p>
<h1 id="net-6">.NET 6</h1>
<ul>
<li>📄 <a href="https://devblogs.microsoft.com/dotnet/announcing-net-6/"><strong>Announcing .NET 6 — The Fastest .NET Yet</strong></a> - Richard Lander.
<blockquote>
<p>This is an absolute monster of a blog post that covers a huge number of the .NET 6 release features. If you only have time to read one article, then make it this one.</p>
</blockquote>
</li>
<li>📄 <a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/"><strong>Performance Improvements in .NET 6</strong></a> - Stephen Toub
<blockquote>
<p>This is a <strong>very</strong> deep dive into the performance work in .NET 6. Not for the faint of heart.</p>
</blockquote>
</li>
<li>📄 <a href="https://dotnet.microsoft.com/platform/support/policy/dotnet-core"><strong>.NET and .NET Core Support Policy</strong></a>
<blockquote>
<p>What you need to know about the support lifecycle of .NET versions. The take away, is that .NET 6 is here for a long time, and you are going to end up migrating here at some point ;-)</p>
</blockquote>
</li>
<li>🌐 <a href="http://aka.ms/dotnet-upgrade-assistant"><strong>.NET Upgrade Assistant</strong></a>
<blockquote>
<p>Speaking of migrating to .NET 6, this tool is here to help you. For many projects it might be a matter of flipping a setting in a project file, but for others (particularly older .NET Framework projects) this command line tool is for you.</p>
</blockquote>
</li>
<li>📺 <a href="https://channel9.msdn.com/Events/dotnetConf/2021/Keynote"><strong>.NET Conf Keynote</strong></a> - Scott Hunter + Friends - ⏰ <strong>70 mins</strong>
<blockquote>
<p>This keynote by Scott Hunter and Friends covers all the key announcements around .NET 6. If you are going to watch only one video, make it this one.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/speed-up-your-net-development-with-hot-reload"><strong>Speed up your .NET development with Hot Reload</strong></a> -
Dmitry Lyalin - ⏰ <strong>28 mins</strong>
<blockquote>
<p>This looks at the incredibly useful Hot Reload and how it works in Visual Studio 2022 with demos across .NET MAUI, Blazor, ASP.NET Core and desktop apps.</p>
</blockquote>
</li>
</ul>
<p><img src="assets/images/posts/dotnetConf2021/Slide12.PNG" /></p>
<h1 id="c-10">C# 10</h1>
<ul>
<li>📄 <a href="https://devblogs.microsoft.com/dotnet/welcome-to-csharp-10/"><strong>Welcome to C# 10</strong></a> - Kathleen Dollard
<blockquote>
<p>C# 10 has some lovely features to make your code cleaner, like <code class="highlighter-rouge">Gobal Usings</code> and <code class="highlighter-rouge">File-scoped namespaces</code>. This post, covers the most important C# 10 improvements.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/whats-new-in-c-10"><strong>What’s new in C# 10</strong></a> - Mads Torgersen & Dustin Campbell - ⏰ <strong>30 mins</strong>
<blockquote>
<p>Mads and Dustin for a discussion and demonstration of the features of C #10 with a all-code, no-slide approach we all love ;-)</p>
</blockquote>
</li>
</ul>
<p><img src="assets/images/posts/dotnetConf2021/Slide15.PNG" /></p>
<h1 id="visual-studio-2022">Visual Studio 2022</h1>
<ul>
<li>📄 <a href="https://devblogs.microsoft.com/visualstudio/visual-studio-2022-now-available/"><strong>Visual Studio 2022 now available</strong></a> - Amanda Silver
<blockquote>
<p>The announcement blog of VS 2022.</p>
</blockquote>
</li>
<li>📄 <a href="https://docs.microsoft.com/en-us/visualstudio/releases/2022/mac-release-notes-preview#whats-new-in-visual-studio-2022-for-mac"><strong>What’s New in Visual Studio 2022 for Mac</strong></a> - Docs Release list
<blockquote>
<p>Visual Studio 2022 for Mac is still in Preview, but that’s not going to stop you Mac people from using it. This page is a good place to look at for the latest release notes.</p>
</blockquote>
</li>
<li>📺 <a href="https://www.youtube.com/watch?v=f8jXO946eDw"><strong>Welcome to Visual Studio 2022</strong></a> – Scott Hanselman and friends - ⏰ <strong>33 mins</strong>
<blockquote>
<p>This is the keynote for the launch of VS2022. It’s a nice relaxed overview of the key features. This is what I imagine having a party over to Scott’s house would be like.</p>
</blockquote>
</li>
<li>📺 <a href="https://www.youtube.com/watch?v=8KsVw52hDVU"><strong>Visual Studio 2022 for .NET XAML developers</strong></a> - Dmitry Lyalin - ⏰ <strong>19 mins</strong>
<blockquote>
<p>I admit it, I still like XAML and VS 2022 has some nice features for XAML geeks that apply to WPF <strong>and</strong> Xamarin / MAUI projects.</p>
</blockquote>
</li>
<li>📺 <a href="https://www.youtube.com/watch?v=LUlbO5MjPjU"><strong>Building cross platform apps with .NET MAUI in Visual Studio 2022</strong></a> - Maddy Leger Montaquila - ⏰ <strong>17 mins</strong>
<blockquote>
<p>Maddy looks at the Preview version of VS 2022 which has all the goodness for us MAUI developers.</p>
</blockquote>
</li>
<li>📺 <a href="https://www.youtube.com/watch?v=B6K_UTYKHEA"><strong>What’s new for Visual Studio 2022 for Mac</strong></a> - Jordan Matthiesen - ⏰ <strong>19 mins</strong>
<blockquote>
<p>Learn about all the new capabilities and improvements in Visual Studio for Mac.</p>
</blockquote>
</li>
<li>🌐 <a href="https://www.youtube.com/visualstudio/"><strong>Visual Studio Youtube Channel</strong></a>
<blockquote>
<p>The official youtube channel for VS. Most interesting here is the <a href="https://www.youtube.com/playlist?list=PLReL099Y5nRdcMhrBc9y1dMhUYBk7hknY">Tips and Tricks playlists</a> but there is also some other gems on the site as well.</p>
</blockquote>
</li>
</ul>
<p><img src="assets/images/posts/dotnetConf2021/Slide18.PNG" /></p>
<h1 id="maui-preview-10">MAUI Preview 10</h1>
<ul>
<li>📄 <a href="https://devblogs.microsoft.com/dotnet/announcing-net-maui-preview-10/"><strong>Announcing .NET MAUI Preview 10</strong></a> - David Ortinau
<blockquote>
<p>.NET Multi-platform App UI (MAUI) Preview 10 is now available atop the stable release of .NET 6, and you can get it in the preview channel of Visual Studio 2022. This new release includes the merging of the remaining Windows App SDK dependencies, and ongoing progress to complete the remaining controls and control features. Might be time to start getting on the MAUI train!</p>
</blockquote>
</li>
<li>📄 <a href="https://github.com/dotnet/maui/wiki/Roadmap"><strong>.NET MAUI Roadmap</strong></a>
<blockquote>
<p>Useful resource to keep on track of the upcoming releases of MAUI.</p>
</blockquote>
</li>
<li>📄 <a href="https://github.com/dotnet/maui/wiki"><strong>.NET MAUI Wiki</strong></a>
<blockquote>
<p>Useful resource for quick docs and migration information.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/introduction-to-net-maui"><strong>Introduction to .NET MAUI</strong></a> - Maddy Leger Montaquila - ⏰ <strong>29 mins</strong>
<blockquote>
<p>Join Maddy, .NET MAUI Program Manager, to get a first look of .NET MAUI in .NET 6 and learn how you can start using it today.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/drawn-controls-in-net-maui"><strong>Drawn controls in .NET MAUI</strong></a> - Javier Suárez Ruiz - ⏰ <strong>30 mins</strong>
<blockquote>
<p>Learn all the basics of .NET MAUI Graphics, a new cross-platform graphics library included with .NET MAUI as well as use this library to create fully drawn controls. We will see what advantages brings this new way of creating controls and we will analyze concepts such as performance and other related aspects.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/welcome-to-maui-community-toolkit"><strong>Welcome to Maui Community Toolkit</strong></a> - Pedro Jesus, Gerald Versluis - ⏰ <strong>29 mins</strong>
<blockquote>
<p>Everything that you need to know about the Maui Community Toolkit and the migration between the old (but gold) Xamarin Community Toolkit.</p>
</blockquote>
</li>
</ul>
<p><img src="assets/images/posts/dotnetConf2021/Slide22.PNG" /></p>
<h1 id="blazor">Blazor</h1>
<ul>
<li>📄 <a href="https://devblogs.microsoft.com/dotnet/announcing-asp-net-core-in-net-6/"><strong>Announcing ASP.NET Core in .NET 6</strong></a> - Daniel Roth
<blockquote>
<p>This blog post covers all the new stuff in ASP.NET Core, but also a section focused on Blazor Improvements.</p>
</blockquote>
</li>
<li>🌐 <a href="aka.ms/blazor-fluent-ui"><strong>Blazor Fluent UI Components</strong></a>
<blockquote>
<p>Want your Blazor app to have the Windows 11 Fluent experience? Yeah, me neither, but you can!</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/enterprise-grade-blazor-apps-with-net-6"><strong>Enterprise-grade Blazor apps with .NET 6</strong></a> - Daniel Roth - ⏰ <strong>30 mins</strong>
<blockquote>
<p>A look at the new support for Hot Reload, error boundaries, state preservation during prerendering, faster file uploads, query string handling, adding page metadata, and integrating Blazor components into existing JavaScript based apps.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/responsive-and-adaptive-tactics-for-blazor-applications"><strong>Response and Adaptive Tactics for Blazor Applications</strong></a> - Ed Charbeneau - ⏰ <strong>27 mins</strong>
<blockquote>
<p>Learn about CSS techniques like CSS Grid, Flexbox and media queries. We will also explore techniques for adaptive the user interface at runtime for maximum control. These tactics apply to Blazor WASM, Server, and Blazor Hybrid.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/next-generation-blazor-components-with-net-6"><strong>Next-generation Blazor components with .NET 6</strong></a> - Daniel Roth, Javier Calvarro Nelson - ⏰ <strong>31 mins</strong>
<blockquote>
<p>A look at the new improvements to the Blazor component model, including required component parameters, generic type inference from ancestor components, JavaScript initializers, and handling of static web assets.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/new-blazor-webassembly-capabilities-in-net-6"><strong>New Blazor WebAssembly capabilities in .NET 6</strong></a> - Steve Sanderson - ⏰ <strong>31 mins</strong>
<blockquote>
<p>Want your mind blown 🤯 - then check out this session which deals with embedding native dependencies into Blazor.</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/building-a-production-ready-blazor-wasm-app"><strong>Building A Production Ready Blazor WASM App</strong></a> - Steve Peirce - ⏰ <strong>29 mins</strong>
<blockquote>
<p>Steve talks about writing production ready code that includes: How to test your Blazor application, How to improve performance of your application, and Common tips and tricks.</p>
</blockquote>
</li>
<li>📺 <a href="https://www.youtube.com/watch?v=swwWgFZJvEs"><strong>.NET MAUI Blazor - Build Hybrid Mobile, Desktop, and Web apps</strong></a> - Daniel Roth - ⏰ <strong>5 mins</strong>
<blockquote>
<p>Quick overview of the what/why of the MAUI / Blazor love child Blaui!</p>
</blockquote>
</li>
<li>📺 <a href="https://docs.microsoft.com/en-us/events/dotnetconf-2021/build-cross-platform-native-apps-with-net-maui-and-blazor"><strong>Build cross-platform native apps with .NET MAUI and Blazor</strong></a> - Eilon Lipton - ⏰ <strong>29 mins</strong>
<blockquote>
<p>The power of Blazor is coming to native apps on desktop and mobile devices! Use existing Blazor web components and get the power of native apps, or supercharge your existing native apps with the power and reach of Blazor with the new BlazorWebView control for .NET MAUI, WPF, and WinForms.</p>
</blockquote>
</li>
</ul>
<hr />
<p>Well, that’s a lot of links, but hopefully you find it useful to get up to speed with all the new goodness.</p>Kym PhillpottsThere have been a tonne of announcements at .NET Conf 2021 including .NET 6, C# 10, Visual Studio 2022, and MAUI Preview 10. This post is a recap of the most important parts for Xamarin, Blazor and Maui developers. For those of you who don’t want to sit through hours of videos, I have watched the sessions and linked the resources so you don’t have to.Avatar Groups2021-09-18T10:00:00+10:002021-09-18T10:00:00+10:00https://kymphillpotts.com/avatar-groups<p>Overlapping avatar badges are an effective way to show a collection of people, especially where space is at a premium. In this article we look at how to create this UI in Xamarin.Forms and MAUI.</p>
<p><img src="assets/images/posts/avatargroups/avatars.png" /></p>
<p>Along the way, we will touch on a few other interesting concepts like:</p>
<ul>
<li>Bindable Layouts</li>
<li>DataTemplate Selectors</li>
<li>MultiValue Converters</li>
</ul>
<blockquote>
<p><a href="https://github.com/kphillpotts/AvatarGroup">The code for this article is available over on my Github.</a></p>
</blockquote>
<h2 id="lets-start-at-the-beginning">Let’s start at the beginning</h2>
<p>The first thing we want is to have some data which is going be exposed via a simple Model and ViewModel.</p>
<h3 id="person-model">Person Model</h3>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
<span class="c1">// whatever properties you have for a person</span>
<span class="c1">// but the most important one we will use is</span>
<span class="c1">// the image for the person</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Image</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="peopleviewmodel">PeopleViewModel</h3>
<p>We are going to support adding and removing people at runtime, which means we have an <code class="highlighter-rouge">AddUserCommand</code>, a <code class="highlighter-rouge">RemoveUserCommand</code>, and we expose the data through an <code class="highlighter-rouge">ObservableCollection</code>.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PeopleViewModel</span> <span class="p">:</span> <span class="n">BaseViewModel</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">ICommand</span> <span class="n">AddUserCommand</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">ICommand</span> <span class="n">RemoveUserCommand</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Person</span><span class="p">></span> <span class="n">People</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Person</span><span class="p">>();</span>
<span class="k">public</span> <span class="nf">PeopleViewModel</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// setup the commands</span>
<span class="n">AddUserCommand</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Command</span><span class="p">(</span><span class="n">AddPerson</span><span class="p">);</span>
<span class="n">RemoveUserCommand</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Command</span><span class="p">(</span><span class="n">RemovePerson</span><span class="p">);</span>
<span class="nf">CreateSampleData</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">CreateSampleData</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// create 5 peeps</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="m">5</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="nf">AddPerson</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">AddPerson</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// add a person using an Image from pravatar.cc</span>
<span class="n">People</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="n">Person</span> <span class="p">{</span> <span class="n">Image</span> <span class="p">=</span> <span class="s">$"https://i.pravatar.cc/64?img=</span><span class="p">{</span><span class="n">People</span><span class="p">.</span><span class="n">Count</span><span class="p">+</span><span class="m">1</span><span class="p">}</span><span class="s">"</span> <span class="p">});</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">RemovePerson</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">People</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="n">People</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">People</span><span class="p">.</span><span class="nf">Last</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p>TIP: There are some nice services you can use for mock random avatars. Here are a few you I’ve use for mockups and design work.</p>
<ul>
<li><a href="https://randomuser.me">randomuser.me</a></li>
<li><a href="https://pravatar.cc">pravatar.cc</a></li>
<li><a href="https://uifaces.co">UI Faces</a></li>
</ul>
</blockquote>
<h2 id="show-me-the-avatars">Show me the avatars</h2>
<p>We can start out the UI simply by using a horizontal <code class="highlighter-rouge">StackLayout</code> and the <code class="highlighter-rouge">BindableLayout</code> attached extension.</p>
<blockquote>
<p><a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/bindable-layouts"><code class="highlighter-rouge">BindableLayouts</code></a> give you the ability to have a Layout generate its content by binding to a collection of items.</p>
</blockquote>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><StackLayout</span>
<span class="na">BindableLayout.ItemsSource=</span><span class="s">"{Binding People}"</span>
<span class="na">HorizontalOptions=</span><span class="s">"CenterAndExpand"</span>
<span class="na">Orientation=</span><span class="s">"Horizontal"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span><span class="nt">></span>
<span class="nt"><BindableLayout.ItemTemplate></span>
<span class="nt"><DataTemplate></span>
<span class="nt"><Frame</span>
<span class="na">Margin=</span><span class="s">"-20,0,0,0"</span>
<span class="na">Padding=</span><span class="s">"0"</span>
<span class="na">CornerRadius=</span><span class="s">"24"</span>
<span class="na">HeightRequest=</span><span class="s">"48"</span>
<span class="na">IsClippedToBounds=</span><span class="s">"True"</span>
<span class="na">WidthRequest=</span><span class="s">"48"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Source=</span><span class="s">"{Binding Image}"</span> <span class="nt">/></span>
<span class="nt"></Frame></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></BindableLayout.ItemTemplate></span>
<span class="nt"></StackLayout></span>
</code></pre></div></div>
<p>There are a couple of interesting things in that markup.</p>
<ul>
<li><strong>Negative Margins</strong> - This allows us to overlap the elements, notice the <code class="highlighter-rouge">Margin="-20,0,0,0"</code>.</li>
<li><strong>Circular images</strong> - There are a number of different ways of doing this, but a simple <code class="highlighter-rouge">Frame</code> with a <code class="highlighter-rouge">CornerRadius</code> does the job.</li>
</ul>
<p>And that’s going to give us a pretty decent looking group of overlapping avatars.</p>
<p><img src="assets/images/posts/avatargroups/simple_avatars.png" alt="Simple Avatars" /></p>
<blockquote>
<p>Another good option here is to use the <a href="https://docs.microsoft.com/en-us/xamarin/community-toolkit/views/avatarview">AvatarView</a> in the <a href="https://github.com/xamarin/XamarinCommunityToolkit">Xamarin Community Toolkit</a>. The cool thing about the AvatarView is that it handles showing initials for avatars if it can’t load an image.</p>
</blockquote>
<h3 id="improving-the-look">Improving the look</h3>
<p>For our simple avatars, we can make them look a little more dynamic by giving the illusion of the avatars cutting into each other. Like this:</p>
<p><img src="assets/images/posts/avatargroups/simple_avatars_styled.png" alt="Cutout Avatars" /></p>
<p>We can achieve this effect in a number of ways, but an easy way would be to have a thick border (frame) around each avatar using the background color (in our case white). Also, it’s probably time to introduce a few styles for consistency and reuse as well.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
<span class="nt"><ResourceDictionary></span>
<span class="nt"><Style</span> <span class="na">x:Key=</span><span class="s">"avatarFrame"</span> <span class="na">TargetType=</span><span class="s">"Frame"</span><span class="nt">></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"Margin"</span> <span class="na">Value=</span><span class="s">"-20,0,0,0"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"Padding"</span> <span class="na">Value=</span><span class="s">"0"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"BackgroundColor"</span> <span class="na">Value=</span><span class="s">"White"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"CornerRadius"</span> <span class="na">Value=</span><span class="s">"24"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"HasShadow"</span> <span class="na">Value=</span><span class="s">"False"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"HeightRequest"</span> <span class="na">Value=</span><span class="s">"48"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"VerticalOptions"</span> <span class="na">Value=</span><span class="s">"Start"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"WidthRequest"</span> <span class="na">Value=</span><span class="s">"48"</span> <span class="nt">/></span>
<span class="nt"></Style></span>
<span class="nt"><Style</span> <span class="na">x:Key=</span><span class="s">"contentFrame"</span> <span class="na">TargetType=</span><span class="s">"Frame"</span><span class="nt">></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"Padding"</span> <span class="na">Value=</span><span class="s">"0"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"BackgroundColor"</span> <span class="na">Value=</span><span class="s">"White"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"CornerRadius"</span> <span class="na">Value=</span><span class="s">"21"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"HasShadow"</span> <span class="na">Value=</span><span class="s">"False"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"HeightRequest"</span> <span class="na">Value=</span><span class="s">"42"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"VerticalOptions"</span> <span class="na">Value=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"HorizontalOptions"</span> <span class="na">Value=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"IsClippedToBounds"</span> <span class="na">Value=</span><span class="s">"True"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"WidthRequest"</span> <span class="na">Value=</span><span class="s">"42"</span> <span class="nt">/></span>
<span class="nt"></Style></span>
<span class="nt"></ResourceDictionary></span>
...
<span class="nt"><StackLayout</span>
<span class="na">BindableLayout.ItemsSource=</span><span class="s">"{Binding People}"</span>
<span class="na">HorizontalOptions=</span><span class="s">"CenterAndExpand"</span>
<span class="na">Orientation=</span><span class="s">"Horizontal"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span><span class="nt">></span>
<span class="nt"><BindableLayout.ItemTemplate></span>
<span class="nt"><DataTemplate></span>
<span class="nt"><Frame</span> <span class="na">Style=</span><span class="s">"{StaticResource avatarFrame}"</span><span class="nt">></span>
<span class="nt"><Frame</span> <span class="na">Style=</span><span class="s">"{StaticResource contentFrame}"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Source=</span><span class="s">"{Binding Image}"</span> <span class="nt">/></span>
<span class="nt"></Frame></span>
<span class="nt"></Frame></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></BindableLayout.ItemTemplate></span>
<span class="nt"></StackLayout></span>
</code></pre></div></div>
<h2 id="adding-a-counter">Adding a counter</h2>
<p>If we have learnt anything during the last year or two of global events, it’s that too many people in one place can be a problem. The same goes for our avatar collection. At the moment, we are showing all the avatars, which is a bit of a problem if you have lots of people in your list.</p>
<p><img src="assets/images/posts/avatargroups/too_many_avatars.png" alt="Too Many Avatars" /></p>
<p>Let’s say we want to just show the first X number of people and then have a counter indicating how many more.</p>
<p><img src="assets/images/posts/avatargroups/avatars.png" alt="Avatars with Counter" /></p>
<p>A simple approach (although not everyone’s favorite) is to create another property on the ViewModel that takes the first X people and include a counter as the last element.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">List</span><span class="p"><</span><span class="kt">object</span><span class="p">></span> <span class="n">PeopleCount</span>
<span class="p">{</span>
<span class="k">get</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">numberToShow</span> <span class="p">=</span> <span class="m">5</span><span class="p">;</span>
<span class="n">List</span><span class="p"><</span><span class="kt">object</span><span class="p">></span> <span class="n">returnList</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="kt">object</span><span class="p">>();</span>
<span class="c1">// add the number of people we are after</span>
<span class="n">returnList</span><span class="p">.</span><span class="nf">AddRange</span><span class="p">(</span><span class="n">People</span><span class="p">.</span><span class="nf">Take</span><span class="p">(</span><span class="n">numberToShow</span><span class="p">));</span>
<span class="c1">// if we have more people than we want to show, add a count</span>
<span class="k">if</span> <span class="p">(</span><span class="n">People</span><span class="p">.</span><span class="n">Count</span> <span class="p">></span> <span class="n">numberToShow</span><span class="p">)</span>
<span class="n">returnList</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">People</span><span class="p">.</span><span class="n">Count</span> <span class="p">-</span> <span class="n">numberToShow</span><span class="p">);</span>
<span class="k">return</span> <span class="n">returnList</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p>Note:</p>
<ul>
<li>Notice the new <code class="highlighter-rouge">PeopleCount</code> Property is now a collection of <code class="highlighter-rouge">object</code> - that’s because the collection can now contain <code class="highlighter-rouge">People</code> objects and a counter of type <code class="highlighter-rouge">int</code>.</li>
</ul>
</blockquote>
<p>If the number of people can change at runtime, it is also necessary to manually raise property changes in your ViewModel to tell the UI to update when you add or remove items from the People Collection.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">void</span> <span class="nf">AddPerson</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// add a person using an Image from pravatar.cc</span>
<span class="n">People</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="n">Person</span> <span class="p">{</span> <span class="n">Image</span> <span class="p">=</span> <span class="s">$"https://i.pravatar.cc/64?img=</span><span class="p">{</span><span class="n">People</span><span class="p">.</span><span class="n">Count</span> <span class="p">+</span> <span class="m">1</span><span class="p">}</span><span class="s">"</span> <span class="p">});</span>
<span class="nf">OnPropertyChanged</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">PeopleCount</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">RemovePerson</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">People</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="n">People</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">People</span><span class="p">.</span><span class="nf">Last</span><span class="p">());</span>
<span class="nf">OnPropertyChanged</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">PeopleCount</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="visualising-different-data-types">Visualising different data types</h3>
<p>A <code class="highlighter-rouge">DataTemplateSelector</code> is a nifty way of being able to select a visualisation for an item based on the data being displayed. In our case, we will create a <code class="highlighter-rouge">DataTemplateSelector</code> to show either a person or a counter template based on the type of the item.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PersonListDataTemplateSelector</span> <span class="p">:</span> <span class="n">DataTemplateSelector</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">DataTemplate</span> <span class="n">PersonTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">DataTemplate</span> <span class="n">CounterTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">protected</span> <span class="k">override</span> <span class="n">DataTemplate</span> <span class="nf">OnSelectTemplate</span><span class="p">(</span><span class="kt">object</span> <span class="n">item</span><span class="p">,</span> <span class="n">BindableObject</span> <span class="n">container</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">item</span> <span class="k">is</span> <span class="n">Models</span><span class="p">.</span><span class="n">Person</span><span class="p">)</span>
<span class="k">return</span> <span class="n">PersonTemplate</span><span class="p">;</span>
<span class="k">else</span>
<span class="k">return</span> <span class="n">CounterTemplate</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now we add the DataTemplateSelector to your page along with DataTemplates for each type</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><DataTemplate</span> <span class="na">x:Key=</span><span class="s">"personTemplate"</span><span class="nt">></span>
<span class="nt"><Frame</span> <span class="na">Style=</span><span class="s">"{StaticResource avatarFrame}"</span><span class="nt">></span>
<span class="nt"><Frame</span> <span class="na">Style=</span><span class="s">"{StaticResource contentFrame}"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Source=</span><span class="s">"{Binding Image}"</span> <span class="nt">/></span>
<span class="nt"></Frame></span>
<span class="nt"></Frame></span>
<span class="nt"></DataTemplate></span>
<span class="nt"><DataTemplate</span> <span class="na">x:Key=</span><span class="s">"counterTemplate"</span><span class="nt">></span>
<span class="nt"><Frame</span> <span class="na">Style=</span><span class="s">"{StaticResource avatarFrame}"</span><span class="nt">></span>
<span class="nt"><Frame</span> <span class="na">BackgroundColor=</span><span class="s">"LightGray"</span> <span class="na">Style=</span><span class="s">"{StaticResource contentFrame}"</span><span class="nt">></span>
<span class="nt"><Label</span>
<span class="na">FontSize=</span><span class="s">"16"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Text=</span><span class="s">"{Binding ., StringFormat='+{0}'}"</span>
<span class="na">TextColor=</span><span class="s">"Black"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"></Frame></span>
<span class="nt"></Frame></span>
<span class="nt"></DataTemplate></span>
<span class="nt"><templates:PersonListDataTemplateSelector</span>
<span class="na">x:Key=</span><span class="s">"personDataTemplateSelector"</span>
<span class="na">CounterTemplate=</span><span class="s">"{StaticResource counterTemplate}"</span>
<span class="na">PersonTemplate=</span><span class="s">"{StaticResource personTemplate}"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>Lastly we can update the our <code class="highlighter-rouge">StackLayout</code> to be bound to the new Property on the ViewModel and the <code class="highlighter-rouge">DataTemplateSelector</code>.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><StackLayout</span>
<span class="na">BindableLayout.ItemTemplateSelector=</span><span class="s">"{StaticResource personDataTemplateSelector}"</span>
<span class="na">BindableLayout.ItemsSource=</span><span class="s">"{Binding PeopleCount}"</span>
<span class="na">HorizontalOptions=</span><span class="s">"CenterAndExpand"</span>
<span class="na">Orientation=</span><span class="s">"Horizontal"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
</code></pre></div></div>
<h2 id="using-multivalue-converters">Using MultiValue Converters</h2>
<p>What we have right now works fine, but we have to keep our <code class="highlighter-rouge">PeopleCount</code> collection in sync by raising <code class="highlighter-rouge">OnPropertyChanged</code> whenever the main <code class="highlighter-rouge">Person</code> collection changes. A lot of people aren’t going to like that, so we can try an alternative approach of using a converter to do the work for us.</p>
<p>Unfortunately a single Converter isn’t going to do the job because we need our UI to update if either the collection changes entirely, or if the count in the collection changes. This is where a <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/multibinding">MultiValueConverter</a> can help us.</p>
<blockquote>
<p>MultiBinding provides the ability to attach a collection of Binding objects to a single binding target property. They are created with the MultiBinding class, which evaluates all of its Binding objects, and returns a single value through a IMultiValueConverter instance provided by your application. In addition, MultiBinding reevaluates all of its Binding objects when any of the bound data changes.</p>
</blockquote>
<p>First thing, let’s create a <code class="highlighter-rouge">PeopleMultiBind</code> converter:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PeopleMultiBind</span> <span class="p">:</span> <span class="n">IMultiValueConverter</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">NumberToShow</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="m">5</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">object</span> <span class="nf">Convert</span><span class="p">(</span><span class="kt">object</span><span class="p">[]</span> <span class="n">values</span><span class="p">,</span> <span class="n">Type</span> <span class="n">targetType</span><span class="p">,</span> <span class="kt">object</span> <span class="n">parameter</span><span class="p">,</span> <span class="n">CultureInfo</span> <span class="n">culture</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="k">value</span> <span class="k">in</span> <span class="n">values</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">value</span> <span class="k">is</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">Person</span><span class="p">></span> <span class="n">enumerable</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// get the first X number of people</span>
<span class="n">List</span><span class="p"><</span><span class="kt">object</span><span class="p">></span> <span class="n">returnList</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="kt">object</span><span class="p">>();</span>
<span class="n">returnList</span><span class="p">.</span><span class="nf">AddRange</span><span class="p">(</span><span class="n">enumerable</span><span class="p">.</span><span class="nf">Take</span><span class="p">(</span><span class="n">NumberToShow</span><span class="p">));</span>
<span class="c1">// if there are even more people - add a counter element</span>
<span class="k">if</span> <span class="p">(</span><span class="n">enumerable</span><span class="p">.</span><span class="nf">Count</span><span class="p">()</span> <span class="p">></span> <span class="n">NumberToShow</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">returnList</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">enumerable</span><span class="p">.</span><span class="nf">Count</span><span class="p">()</span> <span class="p">-</span> <span class="n">NumberToShow</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">returnList</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">object</span><span class="p">[]</span> <span class="nf">ConvertBack</span><span class="p">(</span><span class="kt">object</span> <span class="k">value</span><span class="p">,</span> <span class="n">Type</span><span class="p">[]</span> <span class="n">targetTypes</span><span class="p">,</span> <span class="kt">object</span> <span class="n">parameter</span><span class="p">,</span> <span class="n">CultureInfo</span> <span class="n">culture</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s basically doing the same thing as our property earlier, where we take the first X objects (in our case we have a property <code class="highlighter-rouge">NumberToShow</code>) and then if we have more, we and an int to the end of the collection.</p>
<blockquote>
<p>We can now delete that <code class="highlighter-rouge">PeopleCount</code> property on our ViewModel and the <code class="highlighter-rouge">OnPorpertyChanged</code> lines of our <code class="highlighter-rouge">AddPerson</code> and <code class="highlighter-rouge">RemovePerson</code> commands, because our MultiValueConverter is going to take care of it all</p>
</blockquote>
<p>Now we tie it all together by referencing the converter in our <code class="highlighter-rouge">ResourceDictionary</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><converters:PeopleMultiBind</span> <span class="na">x:Key=</span><span class="s">"peopleMulti"</span> <span class="na">NumberToShow=</span><span class="s">"5"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>And then update our Binding to use the multi-bind, as such:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><StackLayout</span>
<span class="na">BindableLayout.ItemTemplateSelector=</span><span class="s">"{StaticResource personDataTemplateSelector}"</span>
<span class="na">HorizontalOptions=</span><span class="s">"CenterAndExpand"</span>
<span class="na">Orientation=</span><span class="s">"Horizontal"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span><span class="nt">></span>
<span class="nt"><BindableLayout.ItemsSource></span>
<span class="nt"><MultiBinding</span> <span class="na">Converter=</span><span class="s">"{StaticResource peopleMulti}"</span><span class="nt">></span>
<span class="nt"><Binding</span> <span class="na">Path=</span><span class="s">"People"</span> <span class="nt">/></span>
<span class="nt"><Binding</span> <span class="na">Path=</span><span class="s">"People.Count"</span> <span class="nt">/></span>
<span class="nt"></MultiBinding></span>
<span class="nt"></BindableLayout.ItemsSource></span>
<span class="nt"><BindableLayout.EmptyViewTemplate></span>
<span class="nt"><DataTemplate></span>
<span class="nt"><Label</span> <span class="na">Text=</span><span class="s">"Nobody"</span> <span class="nt">/></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></BindableLayout.EmptyViewTemplate></span>
<span class="nt"></StackLayout></span>
</code></pre></div></div>
<blockquote>
<p>Notice how the <code class="highlighter-rouge">ItemsSource</code> indicates is now <code class="highlighter-rouge">MultiBinding</code> to the <code class="highlighter-rouge">People</code> collection and <em>also</em> the <code class="highlighter-rouge">Count</code> property in the collection. Nice!</p>
</blockquote>
<p>For added awesomeness we also add an <code class="highlighter-rouge">EmptyViewTemplate</code> to show something different when there are no people.</p>
<p><img src="\assets\images\posts\avatargroups\avatars_changing.gif" alt="Avatars Changing" /></p>
<h2 id="wrap-up">Wrap-up</h2>
<p>Well that’s it, we have looked at creating an overlapping avatar group using a variety of techniques. Mind you, if you are not expecting the collection of avatars to change at runtime you could probably do away with the second half of this blog post - but it’s a fun experiment anyway.</p>
<blockquote>
<p><a href="https://github.com/kphillpotts/AvatarGroup">The code for this article is available over on my Github.</a></p>
</blockquote>
<p>Thanks for reading, hope you found this useful, and happy coding!</p>Kym PhillpottsOverlapping avatar badges are an effective way to show a collection of people, especially where space is at a premium. In this article we look at how to create this UI in Xamarin.Forms and MAUI.Xamarin Tools 20212021-04-23T23:00:24+10:002021-04-23T23:00:24+10:00https://kymphillpotts.com/xamarin-tools-2021<p>Choosing the right tools and resources can make all the difference in being a productive and effective developer. So, what are all the tools and libraries you should be using to build your Xamarin apps in 2021?</p>
<p>I did a <a href="https://kymphillpotts.com/xamarin-tools-and-resources/">similar post a few years ago</a> but so much has changed in the Xamarin ecosystem I figured it was time for an update. I was curious what members of the Xamarin Community were using, so I sent the following tweet for their input on what they use.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Hey <a href="https://twitter.com/hashtag/Xamarin?src=hash&ref_src=twsrc%5Etfw">#Xamarin</a> twitter friends. What are great tools/resources are you using to develop your Xamarin apps?<br /><br />Let me start:<br />* XamlStyler<br />* MFractor<br />* Vysor<br />* SkiaSharp<br />* Xamarin Community Toolkit<br />* Lottie<br />* AdobeXD<br />* Hot Reload & MVVM Refactorings in VS<br />* SVGRepo<br />* PowerTools</p>— Kym Phillpotts (@kphillpotts) <a href="https://twitter.com/kphillpotts/status/1383995524953477125?ref_src=twsrc%5Etfw">April 19, 2021</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>It got fairly good traction, and lots of insightful replies from the community. This blog lists out those resources and (hopefully) makes a pretty good starting point for finding “best of breed” components for your next Xamarin app.</p>
<p>First a big thanks to the following people for their kind contributions:</p>
<blockquote>
<p><a href="https://twitter.com/@adpead">@adpead</a>
<a href="https://twitter.com/@allanritchie911">@allanritchie911</a>
<a href="https://twitter.com/@AltevirNeto">@AltevirNeto</a>
<a href="https://twitter.com/@AndrewSheley">@AndrewSheley</a>
<a href="https://twitter.com/@Andrik_Just4Fun">@Andrik_Just4Fun</a>
<a href="https://twitter.com/@beeradmoore">@beeradmoore</a>
<a href="https://twitter.com/@Boehm412">@Boehm412</a>
<a href="https://twitter.com/@bratschecody">@bratschecody</a>
<a href="https://twitter.com/@brungarc">@brungarc</a>
<a href="https://twitter.com/@DanJSiegel">@DanJSiegel</a>
<a href="https://twitter.com/@David_PETRIC_">@David_PETRIC_</a>
<a href="https://twitter.com/@devnl">@devnl</a>
<a href="https://twitter.com/@depechie">@depechie</a>
<a href="https://twitter.com/@dylbot9000">@dylbot9000</a>
<a href="https://twitter.com/@faraimuteroza">@faraimuteroza</a>
<a href="https://twitter.com/@GenieSoftStudio">@GenieSoftStudio</a>
<a href="https://twitter.com/@ibm515511">@ibm515511</a>
<a href="https://twitter.com/@IKrugers">@IKrugers</a>
<a href="https://twitter.com/@InquisitorJax">@InquisitorJax</a>
<a href="https://twitter.com/@ivrmirx">@ivrmirx</a>
<a href="https://twitter.com/@jfversluis">@jfversluis</a>
<a href="https://twitter.com/@jmparent_dev">@jmparent_dev</a>
<a href="https://twitter.com/@JosueYeray">@JosueYeray</a>
<a href="https://twitter.com/@kwlothrop">@kwlothrop</a>
<a href="https://twitter.com/@LarsDuewel">@LarsDuewel</a>
<a href="https://twitter.com/@leolimanfl">@leolimanfl</a>
<a href="https://twitter.com/@luismatosluna">@luismatosluna</a>
<a href="https://twitter.com/@mallibone">@mallibone</a>
<a href="https://twitter.com/@matthewrdev">@matthewrdev</a>
<a href="https://twitter.com/@mayaraga2690">@mayaraga2690</a>
<a href="https://twitter.com/@michaldobro">@michaldobro</a>
<a href="https://twitter.com/@mr_javicho">@mr_javicho</a>
<a href="https://twitter.com/@mrwcjoughin">@mrwcjoughin</a>
<a href="https://twitter.com/@normanmackay3">@normanmackay3</a>
<a href="https://twitter.com/@Osoy13">@Osoy13</a>
<a href="https://twitter.com/@PatGet">@PatGet</a>
<a href="https://twitter.com/@pauloquicoli">@pauloquicoli</a>
<a href="https://twitter.com/@RaamAkkireddy">@RaamAkkireddy</a>
<a href="https://twitter.com/@rdavis_au">@rdavis_au</a>
<a href="https://twitter.com/@sphauck">@sphauck</a>
<a href="https://twitter.com/@steven1909">@steven1909</a>
<a href="https://twitter.com/@stntz">@stntz</a>
<a href="https://twitter.com/@strifex24">@strifex24</a>
<a href="https://twitter.com/@unofficial_mozi">@unofficial_mozi</a>
<a href="https://twitter.com/@usdivers">@usdivers</a>
<a href="https://twitter.com/@WorldWeatherApp">@WorldWeatherApp</a></p>
</blockquote>
<p>This post got a little bit out of hand, so I’ve tried to categorize all the suggestions (and a few bonus ones) to help you quickly find all the goodness.</p>
<h2 id="table-of-contents">Table of Contents</h2>
<blockquote>
<ul>
<li><strong>Design</strong>
<ul>
<li><a href="#design-guidelines">Design Guidelines</a></li>
<li><a href="#design-tools">Design Tools</a></li>
<li><a href="#Image-and-font-resources">Image and Font Resources</a></li>
<li><a href="#design-inspiration">Design Inspiration</a></li>
</ul>
</li>
<li><strong>Develop</strong>
<ul>
<li><a href="#extensions--add-ins">Extensions & Add-Ins</a></li>
<li><a href="#device-mirroring-and-emulation">Device Mirroring and Emulation</a></li>
<li><a href="#working-with-data">Working with Data</a>
<ul>
<li><a href="#web-things">Web Things</a></li>
<li><a href="#local-data">Local Data</a></li>
</ul>
</li>
<li><a href="#mvvm-life">MVVM Life</a></li>
<li><a href="#debugging-and-logging">Debugging and Logging</a></li>
<li><a href="#xamarinforms-libraries">Xamarin.Forms Libraries</a></li>
</ul>
</li>
<li><a href="#build-and-deploy"><strong>Build and Deploy</strong></a></li>
<li><a href="#other-resources"><strong>Other Resources</strong></a></li>
<li><a href="#wrap-up"><strong>Wrap-up</strong></a></li>
</ul>
</blockquote>
<hr />
<h2 id="design">Design</h2>
<p>We constantly hear about how designers should learn to code, but I think that in order to create amazing products, developers should learn a bit about design and how to use design tools. Here are the most popular highlighted by the community.</p>
<h3 id="design-guidelines">Design Guidelines</h3>
<ul>
<li>
<p><a href="https://developer.apple.com/design/"><strong>Apple Developer Design Website</strong></a> - This is Apples landing page for all things design. Specifically you should check out the <a href="https://developer.apple.com/design/human-interface-guidelines/">Human Interface Guidelines</a>.</p>
</li>
<li>
<p><a href="https://ivomynttinen.com/blog/ios-design-guidelines"><strong>The iOS Design Guidelines Cheatsheet</strong></a> - This is a much quicker, easier place to check out information about icons, fonts, iconography and design resources.</p>
</li>
<li>
<p><a href="https://developer.android.com/design"><strong>Android Developers Design for Android</strong></a> - Android Design Guidelines. All the juicy guidance from google.</p>
</li>
<li>
<p><a href="https://material.io/"><strong>Material Design Homepage</strong></a> - Material design is the cornerstone of Android design. This page links out to some useful information and tools for Material design.</p>
</li>
</ul>
<h3 id="design-tools">Design Tools</h3>
<ul>
<li>
<p><a href="https://www.adobe.com/products/xd/pricing/individual.html"><strong>Adobe XD</strong></a> - Adobe XD is a vector based design tool specifically designed for creating UI’s and enhancing collaboration between designers and developers. It’s actually my preferred design tool at the moment. If you wanting to get started <a href="https://helpx.adobe.com/xd/tutorials.html">Adobe has some pretty good tutorials</a> on their website. Available for PC and Mac with a Free tier (which is all you need).</p>
</li>
<li>
<p><a href="https://www.figma.com/"><strong>Figma</strong></a> - Another very popular design tool recommended by the community. <em>“Figma brings your teams together to design better products from start to finish.”</em>. Figma has a <a href="https://www.youtube.com/channel/UCQsVmhSa4X-G3lHlUtejzLA">YouTube channel</a> with good learning content. [PC and Mac - Free version available].</p>
</li>
<li>
<p><a href="https://www.sketch.com/"><strong>Sketch App</strong></a> - Sketch is the original UI specific design tool. At one time, it was my preferred design tool but more recently Adobe XD and Figma have entered the scene. Also it’s Mac only and there is no free version. But having said all that, it’s still very popular and an excellent tool.</p>
</li>
<li>
<p><a href="https://zeplin.io/"><strong>Zeplin</strong></a> - Zeplin is a plugin and standalone desktop application for Mac and Windows specifically designed to bridge the gap between designers and engineers. It takes designs from Sketch, Adobe XD CC, Figma, or Adobe Photoshop CC and exports them into a format which generates code snippets, design specs, and assets. There is a free version which allows you to work on 1 project, but beyond that it’ll cost you.</p>
</li>
<li>
<p><a href="https://paintcodeapp.com"><strong>PaintCode</strong></a> - PaintCode is a unique vector drawing app that generates Objective-C or Swift code in real time, acting as a bridge between developers and graphic designers.</p>
</li>
<li>
<p><a href="https://github.com/xamarin/KimonoDesigner"><strong>Kimono Designer</strong></a> - Kimono Designer allows a developer to graphically edit SkiaSharp based graphic objects that can be converted to code and included in any cross-platform language and OS that SkiaSharp supports.</p>
</li>
<li>
<p>A few people mentioned other Adobe products such as <a href="https://www.adobe.com/products/illustrator.html"><strong>Illustrator</strong></a> and <a href="https://www.adobe.com/products/photoshop.html"><strong>Photoshop</strong></a> which are great products for Vector and Bitmap images, but primarily aimed at designers. They are not free. I have a subscription, but rarely use them for mobile development. If you are after free options, you could try <a href="https://www.gimp.org/"><strong>Gimp</strong></a>, <a href="https://www.getpaint.net/"><strong>paint.net</strong></a> and <a href="https://inkscape.org/"><strong>InkScape</strong></a>.</p>
</li>
</ul>
<h3 id="image-and-font-resources">Image and Font Resources</h3>
<ul>
<li>
<p><a href="https://materialdesignicons.com"><strong>Material Design Icons</strong></a> - Material Design Icons is a large collection of free icons. The website allows you to download them as SVGs, PNGs, and Android resources.</p>
</li>
<li>
<p><a href="https://www.svgrepo.com"><strong>SVG Repo</strong></a> - A fantastic resource of SVGs in Monocolor, Multicolor, Outlined and Filled styles.</p>
</li>
<li>
<p><a href="undraw.co"><strong>unDraw</strong></a> - If you are looking for great looking illustrations in SVG or PNG formats, look no further.</p>
</li>
<li>
<p><a href="https://www.syncfusion.com/downloads/metrostudio"><strong>Metro Studio</strong></a> - Syncfusion Metro Studio is a collection of over 7,000 flat and wireframe icon templates that can be easily customized to create thousands of unique icons. It also supports creating custom icon font packages from selected sets of icons.</p>
</li>
<li>
<p><a href="https://icons8.com/"><strong>Icons8</strong></a> - Thousands of free icons in different styles and sizes. You can also recolour them as well.</p>
</li>
<li>
<p><a href="https://fonts.google.com/"><strong>Google Fonts</strong></a> - Google has loads of fonts you can download and use in your apps.</p>
</li>
<li>
<p><a href="https://www.fontsquirrel.com/"><strong>Font Squirrel</strong></a> - If you are after something a little more wacky, Font Squirrel probably has you covered.</p>
</li>
<li>
<p><a href="https://github.com/sw-yx/spark-joy/blob/master/README.md#logos-incl-company-logos"><strong>Spark Joy - Design Tools and Tips for Developers in a Hurry</strong></a> - This is a bonus one. It’s a github repo which lists out a kazillion design resources (mostly aimed at web devs, but still useful for Xamarin / Mobile devs). I’ve linked to the logo section, but there is also sooo much other goodness in here.</p>
</li>
</ul>
<h3 id="design-inspiration">Design Inspiration</h3>
<ul>
<li>
<p><a href="https://dribbble.com/"><strong>Dribbble</strong></a> - This is probably my default go-to place for looking at beautiful UI and interaction designs. So many talented people contribute designs.</p>
</li>
<li>
<p><a href="https://www.behance.net/""><strong>Behance</strong></a> - Another location for our hipster designer friends to submit their goods.</p>
</li>
<li>
<p><a href="https://snppts.dev/"><strong>Snppts</strong></a> - Xamarin Forms UI Snippets. This is a great site to look at if you are interested to see how other devs layout their Xamarin Forms pages, it has links to source code and XAML.</p>
</li>
<li>
<p><a href="https://github.com/jsuarezruiz/xamarin-forms-goodlooking-UI"><strong>Xamarin.Forms goodlooking UI samples</strong></a> - A resource of some of the best looking Xamarin.Forms interfaces out there.</p>
</li>
</ul>
<hr />
<h2 id="develop">Develop</h2>
<p>Developers love tools; they make our life better. Most of us (except perhaps the geekiest) will use an IDE to develop applications. From the community responses it looks like people are using <a href="https://visualstudio.microsoft.com/downloads/"><strong>Visual Studio, Visual Studio for Mac</strong></a> and <a href="https://www.jetbrains.com/rider/download/"><strong>Rider</strong></a> for development. (oh yeah and let’s not forget the greatest developer tool of all time <a href="https://stackoverlow.com"><strong>StackOverflow</strong></a>). You might even find yourself using <a href="https://developer.apple.com/xcode/"><strong>XCode</strong></a> for some iOS things like editing storyboards. Apart form that, below are some resources and tools that might help you develop your apps.</p>
<h3 id="extensions--add-ins">Extensions & Add-Ins</h3>
<ul>
<li>
<p><a href="https://www.mfractor.com/"><strong>MFractor</strong></a> - MFractor is a powerful productivity tool for Xamarin developers. This tool is specifically for Xamarin developers and has loads of great features, especially for XAML developers. There is a free “Lite edition”, or if you are serious get the “Full” edition - you won’t regret it. Available for Visual Studio Windows and Mac.</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=TeamXavalon.XAMLStyler"><strong>XamlStyler</strong></a> - XAML Styler is a visual studio extension that formats XAML source code based on a set of styling rules. This tool can help you/your team maintain a better XAML coding style as well as a much better XAML readability. I love this tool! As a bonus <a href="https://www.sharpnado.com/xamarin-forms-xamlstyler-config/">Sharpnado wrote a cool blog post</a> on how you can setup a config file to keep your project consistent amongst your team.</p>
</li>
<li>
<p><a href="https://github.com/ademanuele/VSMac-CodeCoverage"><strong>VSMac-CodeCoverage</strong></a> - A code coverage extension for Visual Studio for Mac that provides a new pad for displaying coverage statistics and visualizing line coverage in the editor margin.</p>
</li>
<li>
<p><a href="https://www.jetbrains.com/resharper/"><strong>Resharper</strong></a> - Visual Studio Extension which provides an absolute truck load of refactoring and code quality tools for all types of .NET projects. Personally, I don’t use this any more because there have been so many great refactoring additions to core Visual Studio, but it is still super popular amongst .NET devs.</p>
</li>
</ul>
<h3 id="device-mirroring-and-emulation">Device Mirroring and Emulation</h3>
<ul>
<li>
<p><a href="https://github.com/Genymobile/scrcpy"><strong>scrcpy</strong></a> - This application provides display and control of Android devices connected on USB (or over TCP/IP). It works on GNU/Linux, Windows and macOS. I don’t use this at the moment, but that may change because this gets really great reviews.</p>
</li>
<li>
<p><a href="https://www.airdroid.com/en/personal/"><strong>AirDroid</strong></a> - AirDroid enables you to transfer files across different platforms, mirror and remote control mobile devices, receive and reply to messages on the computer.</p>
</li>
<li>
<p><a href="https://www.genymotion.com/desktop/"><strong>Genymotion Desktop</strong></a> - Let’s face it, Android emulators can be in pain in the butt. Genymotion aims to remove that pain but giving you a better Android emulator than the one provided by Google.</p>
</li>
<li>
<p><a href="https://www.airsquirrels.com/reflector"><strong>Reflector</strong></a> - Allows you to mirror your iOS and Android device to a bunch of different devices.</p>
</li>
<li>
<p><a href="https://www.vysor.io/"><strong>Vysor</strong></a> - Vysor allows you to mirror your Android device as an emulator on your desktop. I normally use this when I am mirroring my Android device during development, because I can control my physical device with a mouse.</p>
</li>
</ul>
<h3 id="working-with-data">Working with Data</h3>
<p>Like it or not, a great deal of our time is working with Data. In a mobile context that mostly means hitting Web Services and local databases. Here are some great tools to help you out.</p>
<h3 id="web-things">Web Things</h3>
<ul>
<li>
<p><a href="https://www.telerik.com/fiddler/"><strong>Fiddler</strong></a> - Fiddler is a debugging proxy server tool used to log, inspect, and alter HTTP and HTTPS traffic between a computer and a web server or servers. There are a few different editions now, but there is still the <a href="https://www.telerik.com/fiddler/fiddler-classic">Classic</a> version which is free.</p>
</li>
<li>
<p><a href="https://www.postman.com/"><strong>Postman</strong></a> - Postman is a popular API client that makes it easy for developers to create, share, test and document APIs. This is done by allowing users to create and save simple and complex HTTP/s requests, as well as read their responses. The result - more efficient and less tedious work. Another option suggested is <a href="https://install.advancedrestclient.com/install"><strong>Advanced REST Client</strong></a>.</p>
</li>
<li>
<p><a href="https://quicktype.io/"><strong>Quicktype</strong></a> - This tool is fantastic. If you’ve got some JSON you can paste it in and it will generate you models and serialization code.</p>
</li>
<li>
<p><a href="https://github.com/reactiveui/refit"><strong>Refit</strong></a> - Simplify calling Rest services. If you want to simplify your code for calling web services give Refit a shot, you can basically just create an interface which describes your web service, then let it do all the hard work of accessing the service - no need to worry about doing the serializing and deserializing yourself or creating GET and PUT methods. It’ll do it for you.</p>
</li>
<li>
<p><a href="https://github.com/BSiLabs/HttpTracer"><strong>HttpTracer</strong></a> - A simple http tracing library to write request and response information to your output window. Making your life easier when debugging http calls!</p>
</li>
<li>
<p><a href="https://github.com/App-vNext/Polly"><strong>Polly</strong></a>- Automatic retry policies. The reality is that on mobile devices your connection sucks. It’s going to disappear for no apparent reason and sometimes the best thing is to just reissue a request. Polly is going to help you out here by allowing you to define automatic retry policies.</p>
</li>
<li>
<p><a href="http://www.newtonsoft.com/json"><strong>Newtonsoft Json.NET</strong></a> still my goto library for anything to do with serializing, deserializing and parsing JSON results.</p>
</li>
<li>
<p><a href="https://github.com/protobuf-net/protobuf-net"><strong>protobuf-net</strong></a> - protobuf-net is a contract based serializer for .NET code, that happens to write data in the “protocol buffers” serialization format engineered by Google.</p>
</li>
<li>
<p><a href="https://restsharp.dev/"><strong>RestSharp</strong></a> - Featuring automatic serialization and deserialization, request and response type detection, variety of authentications and other useful features.</p>
</li>
<li>
<p><a href="https://github.com/alexrainman/ModernHttpClient"><strong>modernhttpclient-updated</strong></a> - This library brings the latest platform-specific networking libraries to Xamarin applications via a custom HttpClient handler.</p>
</li>
</ul>
<h3 id="local-data">Local Data</h3>
<ul>
<li>
<p><a href="https://github.com/praeclarum/sqlite-net"><strong>Sqlite-Net</strong></a> Dead simple SQLite integration – This package from Frank Krueger is the best way (I know of) to integrate SQLite into your Xamarin Apps. It also provides a light weight ORM interface to make it really simple to use. There is also an option to provide encryption support with <a href="https://www.nuget.org/packages/sqlite-net-sqlcipher">sqlite-net-sqlcipher</a>.</p>
</li>
<li>
<p><a href="https://www.litedb.org/"><strong>LiteDB</strong></a> - Embedded NoSQL database for .NET. An open source MongoDB-like database with zero configuration - mobile ready</p>
</li>
<li>
<p><a href="https://sqlitebrowser.org/"><strong>DB Browser for SQLite</strong></a> - A free cross platform tool which will allow you to create databases, define the schema, and add records to the database.</p>
</li>
<li>
<p><a href="https://github.com/reactiveui/Akavache"><strong>Akavache</strong></a> - An asynchronous, persistent key-value store created for writing desktop and mobile applications, based on SQLite3. Akavache is great for both storing important data as well as cached local data that expires.</p>
</li>
<li>
<p><a href="https://github.com/jamesmontemagno/monkey-cache"><strong>Monkey Cache</strong></a> - A great dead-simple caching library for any .NET application. It can work with File, SQLite and LiteDB persistence.</p>
</li>
</ul>
<h3 id="mvvm-life">MVVM Life</h3>
<p>I often joke that <em>to be good at MVVM you really just need to have a really strong opinions and tell everyone else they are doing it wrong</em>. But seriously MVVM is a great architecture for building your applications. I feel that in the future we are also going to see more and more MVU. In the meantime, check out these great MVVM frameworks and libraries.</p>
<ul>
<li>
<p><a href="https://github.com/jamesmontemagno/mvvm-helpers"><strong>MVVM Helpers</strong></a> - James Montemagno’s Collection of MVVM helper classes for any application. I <em>think</em> this is getting merged into the Xamarin Community Toolkit, but I still like to use this directly sometimes.</p>
</li>
<li>
<p><a href="https://github.com/Fody/PropertyChanged"><strong>Fody Property Changed</strong></a> - This is an code weaver that can help you out writing your INotifyPropertyChange notifications and keep your code nice and clean. Not everyone loves this because it does magic behind the scenes, but still pretty popular.</p>
</li>
<li>
<p><a href="https://github.com/PrismLibrary/Prism"><strong>Prism</strong></a> - Prism is a framework for building loosely coupled, maintainable, and testable XAML applications in WPF, Xamarin Forms, and Uno / Win UI Applications. A very popular choice for people looking for an MVVM framework.</p>
</li>
<li>
<p><a href="https://github.com/rid00z/FreshMvvm"><strong>FreshMVVM</strong></a> - FreshMvvm is a super light Mvvm Framework designed specifically for Xamarin.Forms. It’s designed to be Easy, Simple and Flexible.</p>
</li>
<li>
<p><a href="https://www.mvvmcross.com/"><strong>MvvmCross</strong></a> - Share behavior and business logic in a single codebase across supported platforms, using the Model-View-ViewModel (MVVM) design pattern. MvvmCross is a framework specifically developed for Xamarin and the mobile ecosystem.</p>
</li>
<li>
<p><a href="https://reactiveui.net"><strong>ReactiveUI</strong></a> - An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms! I don’t do a lot of Reactive programming is definitely something I should look into more.</p>
</li>
<li>
<p><a href="https://github.com/runceel/ReactiveProperty"><strong>ReactiveProperty</strong></a> - ReactiveProperty provides MVVM and asynchronous support features under <a href="http://reactivex.io/">Reactive Extensions</a>.</p>
</li>
<li>
<p><a href="http://www.mvvmlight.net/"><strong>MvvmLight</strong></a> - Yet another popular framework for cross platform MVVM.</p>
</li>
</ul>
<h3 id="debugging-and-logging">Debugging and Logging</h3>
<ul>
<li>
<p><a href="https://www.linqpad.net/"><strong>LinqPad</strong></a> - LinqPad is a great tool to just smash out some code and test it out. No more creating a new VS Solution called Console765.exe.</p>
</li>
<li>
<p><a href="https://github.com/moq/moq4"><strong>moq</strong></a> - The most popular and friendly mocking library for .NET</p>
</li>
<li>
<p><a href="https://github.com/serilog/serilog"><strong>SeriLog</strong></a> - Serilog is a diagnostic logging library for .NET applications. It is easy to set up, has a clean API, and runs on all recent .NET platforms.</p>
</li>
<li>
<p><a href=""><strong>xunit</strong></a> - xUnit.net is a free, open source, community-focused unit testing tool for the .NET Framework. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit testing C#, F#, VB.NET and other .NET languages. xUnit.net works with ReSharper, CodeRush, TestDriven.NET and Xamarin.</p>
</li>
<li>
<p><a href="https://github.com/AutoFixture/AutoFixture"><strong>AutoFixture</strong></a> - Because sometimes you can’t be bothered creating your own data for unit tests. This library will reflect over your models, so you can just ask it to create a bunch of objects for you.</p>
</li>
</ul>
<h3 id="xamarinforms-libraries">Xamarin.Forms Libraries</h3>
<p>Xamarin.Forms is the way <em>most</em> people develop Xamarin Applications and it has some amazing libraries you can use to help you out.</p>
<ul>
<li>
<p><a href="https://github.com/aritchie/userdialogs"><strong>ACR User Dialogs</strong></a> - A cross platform library that allows you to call for standard user dialogs from a shared/portable library. Supports Android, iOS, and Unified Windows Platform (UWP, UAP). This is a popular library to show dialogs, but looking at the github repo it looks like it may not be getting any more updates.</p>
</li>
<li>
<p><a href="https://github.com/AndreiMisiukevich/CardView"><strong>CardsView</strong></a> - In my opinion, this is the best Carousel style control available for Xamarin.Forms. It is very flexible and performs excellently.</p>
</li>
<li>
<p><a href="https://github.com/sthewissen/Xamarin.Forms.DebugRainbows"><strong>Debug Rainbows</strong></a> - This is a niftly little addition to your app used for debugging layouts. Flip a switch in your XAML and you can see the bounds of your controls. As Steven himself says, “The package you didn’t even know you needed”!</p>
</li>
<li>
<p><a href="https://github.com/luberda-molinet/FFImageLoading"><strong>FFImageLoading</strong></a> - This is an amazing library for better downloading and caching of images across all the platforms.</p>
</li>
<li>
<p><a href="https://github.com/beraybentesen/glide-xamarin-android"><strong>Glide</strong></a> - Glide is a fast and efficient image loading library for Android focused on smooth scrolling. There is also a Xamarin.Forms focused version called <a href="https://github.com/jonathanpeppers/glidex">GlideX</a>.</p>
</li>
<li>
<p><a href="https://grialkit.com/"><strong>GrailKit</strong></a> - Designed for .NET Developers. Grial UI Kit provides a complete ready made collection of fully customizable beautiful XAML templates, UI screens and resources to help you build cross-platform apps with Xamarin.Forms.</p>
</li>
<li>
<p><a href="https://github.com/Baseflow/LottieXamarin"><strong>Lottie</strong></a> - If you are working with designers who are throwing After Effects animations at you, or if you just want to spice up your UI with animations have a look at this bad boy. To go along with this you’ll also want to have a look at <a href="https://lottiefiles.com/"><strong>Lottie Files</strong></a> which has free animation files and some very cool tools for creating and editing Lottie files.</p>
</li>
<li>
<p><a href="https://github.com/mgierlasinski/MagicGradients"><strong>MagicGradients</strong></a> - If you love gradients (and who doesn’t) this library is for you.</p>
</li>
<li>
<p><a href="https://www.mapbox.com/"><strong>Mapbox</strong></a> - If Apple or Google Maps aren’t doing it for you, or if you want to have offline maps this could be a good solution. There is <a href="https://github.com/NAXAM/mapbox-xamarin-forms"><strong>MapBox Xamarin Forms</strong></a> to use as well.</p>
</li>
<li>
<p><a href="https://github.com/sthewissen/Xamarin.Forms.PancakeView"><strong>PancakeView</strong></a> - A delicous library that provides an extended ContentView for Xamarin.Forms with rounded corners, borders, shadows. As I understand it, most of the functionality is being moved into Xamarin Community Toolkit. But I still love you. Regardless, you’ll still see PancakeView referenced in a bunch of solutions and tutorials.</p>
</li>
<li>
<p><a href="https://github.com/rotorgames/Rg.Plugins.Popup"><strong>Rg.Plugins.Popup</strong></a> - Rg.Plugins.Popup - is a cross platform plugin for Xamarin.Forms which allows you to open Xamarin.Forms pages as a popup that can be shared across iOS, Android, UWP, and macOS.</p>
</li>
<li>
<p><a href="https://github.com/GiampaoloGabba/Xamarin.Plugin.SharedTransitions"><strong>Shared Transitions</strong></a> - If you want to provide dynamic transitions between your pages you can do it in platform specific code… or just use Shared Transitions!</p>
</li>
<li>
<p><a href="https://github.com/roubachof"><strong>Sharpnado</strong></a> - Sharpnado is a great collection of UI based improvements, including <a href="https://github.com/roubachof/Sharpnado.Tabs">Tabs</a>, <a href="https://github.com/roubachof/Sharpnado.HorizontalListView">HorizontalListView</a>, <a href="https://github.com/roubachof/Sharpnado.MaterialFrame">MaterialFrame</a>, <a href="https://github.com/roubachof/Sharpnado.Shadows">Shadows</a> and a pretty cool <a href="https://github.com/roubachof/Sharpnado.TaskLoaderView">TaskLoaderView</a></p>
</li>
<li>
<p><a href="https://github.com/shinyorg"><strong>Shiny</strong></a> - Xamarin.Essentials has some fantastic platform abstractions for devices, but Shiny really… err… shines when it comes to deep platform abstractions for things like Background Jobs and location.</p>
</li>
<li>
<p><a href="https://github.com/mono/SkiaSharp"><strong>SkiaSharp</strong></a> - I can’t even tell you how much I love SkiaSharp for doing cross platform 2D Graphics. It’s super fast and super powerful.</p>
</li>
<li>
<p><a href="https://github.com/xamarin/Essentials"><strong>Xamarin.Essentials</strong></a> - This library provides so many amazing abstractions over the platform APIs. It is now part of the standard template when creating a Xamarin.Forms application. Between Essentials and Shiny I feel like I may never need to write platform specific code ever again.</p>
</li>
<li>
<p><a href="https://github.com/xamarin/XamarinCommunityToolkit"><strong>Xamarin Community Toolkit</strong></a> - A collection of Animations, Behaviors, Converters, Controls and Effects for mobile development with Xamarin.Forms. This library is almost certainly going to be a “must have” for new Xamarin.Forms projects. Very importantly, it is providing a bridge of new features as we head towards the MAUI future.</p>
</li>
</ul>
<hr />
<h2 id="build-and-deploy">Build and Deploy</h2>
<p>Continuous Integration and Continuous Deployment practices these days should be considered a <em>must have</em>. And with the tools available it’s really very simple to get started.</p>
<ul>
<li>
<p><a href="https://github.com/dansiegel/Mobile.BuildTools"><strong>Mobile.BuildTools</strong></a> - The Mobile.BuildTools are a collection of MSBuild Tasks that help make MSBuild smarter in handling the build process for CI/CD with Mobile Applications.</p>
</li>
<li>
<p><a href="https://appcenter.ms/"><strong>Visual Studio App Center</strong></a> - Connect to GitHub, Bitbucket, GitLab, or Azure DevOps and build your app in the cloud on every commit. Automatically run unit tests, release to testers and stores, or test your UI on real devices.</p>
</li>
<li>
<p><a href="https://azure.microsoft.com/services/devops/pipelines/"><strong>Azure DevOps - Azure Pipelines</strong></a> - Get cloud-hosted pipelines for Linux, macOS and Windows. Build web, desktop and mobile applications. Deploy to any cloud or on‑premises. Very full featured and customizable.</p>
</li>
<li>
<p><a href="https://github.com/features/actions"><strong>Github Actions</strong></a> - If you are on GitHub then GitHub Actions seems to me to be something which is growing in momentum.</p>
</li>
</ul>
<h2 id="other-resources">Other Resources</h2>
<p>This blog post was meant to be much shorter, but there is just so much awesome stuff available for the Xamarin Ecosystem that I couldn’t help myself. Other resources you might want to check out are:</p>
<ul>
<li><a href="https://github.com/XamSome/awesome-xamarin"><strong>Awesome Xamarin</strong></a> - it lists outs <em>loads</em> of other libraries and tools for just about everything.</li>
<li><a href="https://github.com/jsuarezruiz/awesome-xamarin-forms"><strong>Awesome Xamarin.Forms</strong></a> - A curated list of awesome Xamarin.Forms libraries and resources.</li>
<li><a href="https://luismts.com/design-tools-xamarin-forms/"><strong>Design Tools for Xamarin Forms</strong></a> - Design tools and resources for Xamarin.Forms</li>
</ul>
<h2 id="wrap-up">Wrap-up</h2>
<p>So there you have it people, a grab bag of tools, libraries and resources I (and the community) recommend for Xamarin Development. I hope you find it useful. Maybe you have some tools or resources which you consider essential… please let me know or post some comments below. Remember, sharing is caring.</p>
<p>Take care, and happy coding!</p>Kym PhillpottsChoosing the right tools and resources can make all the difference in being a productive and effective developer. So, what are all the tools and libraries you should be using to build your Xamarin apps in 2021?Xamarin.Forms UI Challenges - Pizza Shop2020-07-03T09:00:24+10:002020-07-03T09:00:24+10:00https://kymphillpotts.com/pizza-shop<p>Pizza is like the entire food pyramid. And This Xamarin UI Challenge, like our friend the pizza, has lots of delicious ingredients including animations, transitions, custom buttons, fonts and a sprinkle of SkiaSharp. Nom Nom.</p>
<h2 id="the-design">The Design</h2>
<p><img src="assets/images/posts/pizza/pizza-design.gif" /></p>
<p>This is a great looking design called <a href="https://dribbble.com/shots/10921038-Pizza-Shop-app-Interaction">Pizza Shop app Interaction</a> created by <a href="https://dribbble.com/bidyutbera">Bidyut Kumar Bera</a>. It’s a lovely playful design that uses thoughtful animations and transitions to enhance the user experience of the app.</p>
<blockquote>
<p>Animations should always serve a purpose</p>
</blockquote>
<p>The key elements we will focus on in this challenge are:</p>
<ul>
<li>Flying pizzas around the screen using an animation state helper</li>
<li>Custom stepper button with flip up labels</li>
<li>“Chomp Button” when a pizza is added to the cart</li>
</ul>
<h2 id="basic-page-layout">Basic Page Layout</h2>
<p>The main page of the application uses a <code class="highlighter-rouge">Grid</code> as its main container which is fine for most of the elements. However, in order to control over the position of the flying pizza another key element is using an <code class="highlighter-rouge">AbsoluteLayout</code> which allows us to have precise control of the pizza.</p>
<h2 id="flying-pizzas">Flying Pizzas</h2>
<p>In this design there are ingredients flying all over the screen. To simplify this we use an AnimationStateEngine class to control the positions of the pizza for various states.</p>
<p><img src="assets/images/posts/pizza/pizza-states.gif" /></p>
<p>There are five states which are represented in an enumeration:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">enum</span> <span class="n">State</span>
<span class="p">{</span>
<span class="n">Start</span><span class="p">,</span>
<span class="n">Entrance</span><span class="p">,</span>
<span class="n">Small</span><span class="p">,</span>
<span class="n">Medium</span><span class="p">,</span>
<span class="n">Large</span>
<span class="p">}</span></code></pre></figure>
<p>We use a custom AnimationStateEngine to control elements for each of the states. There is quite a bit of code to setup the various elements, but as an example for the pizza we setup positions and rotation for the pizza for each state.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="n">animState</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AnimationStateMachine</span><span class="p">();</span>
<span class="n">animState</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">State</span><span class="p">.</span><span class="n">Start</span><span class="p">,</span> <span class="k">new</span> <span class="n">ViewTransition</span><span class="p">[]</span>
<span class="p">{</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Layout</span><span class="p">,</span> <span class="n">startRect</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Rotation</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="n">animState</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">State</span><span class="p">.</span><span class="n">Entrance</span><span class="p">,</span> <span class="k">new</span> <span class="n">ViewTransition</span><span class="p">[]</span>
<span class="p">{</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Layout</span><span class="p">,</span> <span class="n">entranceRect</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Rotation</span><span class="p">,</span> <span class="m">20</span><span class="p">),</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="n">animState</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">State</span><span class="p">.</span><span class="n">Small</span><span class="p">,</span> <span class="k">new</span> <span class="n">ViewTransition</span><span class="p">[]</span>
<span class="p">{</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Layout</span><span class="p">,</span> <span class="n">smallRect</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Rotation</span><span class="p">,</span> <span class="m">45</span><span class="p">),</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="n">animState</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">State</span><span class="p">.</span><span class="n">Medium</span><span class="p">,</span> <span class="k">new</span> <span class="n">ViewTransition</span><span class="p">[]</span>
<span class="p">{</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Layout</span><span class="p">,</span> <span class="n">mediumRect</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Rotation</span><span class="p">,</span> <span class="m">90</span><span class="p">),</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="n">animState</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">State</span><span class="p">.</span><span class="n">Large</span><span class="p">,</span> <span class="k">new</span> <span class="n">ViewTransition</span><span class="p">[]</span>
<span class="p">{</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Layout</span><span class="p">,</span> <span class="n">largeRect</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">ViewTransition</span><span class="p">(</span><span class="n">Pizza</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">.</span><span class="n">Rotation</span><span class="p">,</span> <span class="m">135</span><span class="p">),</span>
<span class="p">...</span>
<span class="p">}</span></code></pre></figure>
<p>Then to do the animation it’s just a matter of activating a particular state and the AnimationStateEngine will handle the translations for us.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">void</span> <span class="nf">PizzaRulerThumb_Tapped</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// navigate to the next state</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">animState</span><span class="p">.</span><span class="n">CurrentState</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">State</span><span class="p">.</span><span class="n">Small</span><span class="p">:</span>
<span class="n">animState</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="n">State</span><span class="p">.</span><span class="n">Medium</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="n">State</span><span class="p">.</span><span class="n">Medium</span><span class="p">:</span>
<span class="n">animState</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="n">State</span><span class="p">.</span><span class="n">Large</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="n">State</span><span class="p">.</span><span class="n">Large</span><span class="p">:</span>
<span class="n">animState</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="n">State</span><span class="p">.</span><span class="n">Small</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>As I said, there is quite a bit more in the code and you should definitely checkout the code if you are interested in the inner workings of it. But hopefully that gives you enough of a clue to see the benefits of this sort of approach.</p>
<h2 id="custom-stepper">Custom stepper</h2>
<p>The design calls for a fancy stepper control that rotates the quantity up and down.</p>
<p><img src="assets/images/posts/pizza/stepper.gif" /></p>
<p>The main layout of this is a <code class="highlighter-rouge">Grid</code>. The first column just has the the Label. So not particularly interesting.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Grid</span>
<span class="na">x:Name=</span><span class="s">"QuantitySelect"</span>
<span class="na">Margin=</span><span class="s">"20,30"</span>
<span class="na">ColumnSpacing=</span><span class="s">"40"</span>
<span class="na">VerticalOptions=</span><span class="s">"End"</span><span class="nt">></span>
<span class="nt"><Grid.ColumnDefinitions></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"Auto"</span> <span class="nt">/></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"></Grid.ColumnDefinitions></span>
<span class="nt"><Label</span>
<span class="na">FontSize=</span><span class="s">"18"</span>
<span class="na">Style=</span><span class="s">"{StaticResource DescriptionText}"</span>
<span class="na">Text=</span><span class="s">"Quantity"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
...
<span class="nt"></Grid></span></code></pre></figure>
<p>The second column is where the magic happens. To create the buttons and outline for the stepper button, we just use a frame and a couple of buttons with appropriate <code class="highlighter-rouge">Border</code> and <code class="highlighter-rouge">CornerRadius</code> settings. But interestingly here we don’t enclose the buttons inside the frame, rather we use the magic of <code class="highlighter-rouge">Grid</code> to have the elements overlapping each other.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Frame</span>
<span class="na">Grid.Column=</span><span class="s">"1"</span>
<span class="na">Padding=</span><span class="s">"0"</span>
<span class="na">BackgroundColor=</span><span class="s">"Transparent"</span>
<span class="na">BorderColor=</span><span class="s">"White"</span>
<span class="na">CornerRadius=</span><span class="s">"25"</span>
<span class="na">HasShadow=</span><span class="s">"False"</span>
<span class="na">HeightRequest=</span><span class="s">"50"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Fill"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"><Button</span>
<span class="na">x:Name=</span><span class="s">"DecreaseButton"</span>
<span class="na">Grid.Column=</span><span class="s">"1"</span>
<span class="na">Margin=</span><span class="s">"10,0,0,0"</span>
<span class="na">Clicked=</span><span class="s">"DecreaseButton_Clicked"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Start"</span>
<span class="na">Style=</span><span class="s">"{StaticResource QuantityButton}"</span>
<span class="na">Text=</span><span class="s">"-"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"><Button</span>
<span class="na">x:Name=</span><span class="s">"IncreaseButton"</span>
<span class="na">Grid.Column=</span><span class="s">"1"</span>
<span class="na">Margin=</span><span class="s">"0,0,10,0"</span>
<span class="na">Clicked=</span><span class="s">"IncreaseButton_Clicked"</span>
<span class="na">HorizontalOptions=</span><span class="s">"End"</span>
<span class="na">Style=</span><span class="s">"{StaticResource QuantityButton}"</span>
<span class="na">Text=</span><span class="s">"+"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"><controls:FerrisLabel</span>
<span class="na">x:Name=</span><span class="s">"QuantityLabel"</span>
<span class="na">Grid.Column=</span><span class="s">"1"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Text=</span><span class="s">"1"</span>
<span class="na">TextStyle=</span><span class="s">"{StaticResource QuantityStyle}"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span></code></pre></figure>
<h2 id="the-ferris-label-control">The Ferris Label Control</h2>
<p>Now for the rotating label. We put this into a custom control because it’s used in multiple places in the design so we can reuse it. It’s called a <code class="highlighter-rouge">FerrisLabel</code> which is kind of a play on the words that it’s like a carousel but vertical.</p>
<p>It’s actually quite simple. It uses two labels and a couple bindable properties that respond to when the text changes to kick off an animation.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Grid</span>
<span class="na">x:Class=</span><span class="s">"Pizza.Controls.FerrisLabel"</span>
<span class="na">xmlns=</span><span class="s">"http://xamarin.com/schemas/2014/forms"</span>
<span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
<span class="na">xmlns:controls=</span><span class="s">"clr-namespace:Pizza.Controls"</span>
<span class="na">xmlns:mc=</span><span class="s">"http://schemas.openxmlformats.org/markup-compatibility/2006"</span><span class="nt">></span>
<span class="nt"><Label</span>
<span class="na">x:Name=</span><span class="s">"CurrentLabel"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"><Label</span>
<span class="na">x:Name=</span><span class="s">"NextLabel"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Opacity=</span><span class="s">"0"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="nt"></Grid></span></code></pre></figure>
<p>Behind the scenes, we have an <code class="highlighter-rouge">AnimationOffset</code> property which controls which way the animations should go (up or down, or even diagonal if you want). Also a <code class="highlighter-rouge">BindableProperty</code> for the Text, which kicks off the animations when it detects a change.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"> <span class="k">public</span> <span class="n">Point</span> <span class="n">AnimationOffset</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">BindableProperty</span> <span class="n">TextProperty</span> <span class="p">=</span> <span class="n">BindableProperty</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">Text</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="kt">string</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">FerrisLabel</span><span class="p">),</span> <span class="k">default</span><span class="p">(</span><span class="kt">string</span><span class="p">),</span> <span class="n">propertyChanged</span><span class="p">:</span> <span class="n">OnTextChanged</span><span class="p">);</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Text</span>
<span class="p">{</span>
<span class="k">get</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">TextProperty</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">set</span>
<span class="p">{</span>
<span class="nf">SetValue</span><span class="p">(</span><span class="n">TextProperty</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">static</span> <span class="k">void</span> <span class="nf">OnTextChanged</span><span class="p">(</span><span class="n">BindableObject</span> <span class="n">bindable</span><span class="p">,</span> <span class="kt">object</span> <span class="n">oldValue</span><span class="p">,</span> <span class="kt">object</span> <span class="n">newValue</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">control</span> <span class="p">=</span> <span class="p">(</span><span class="n">FerrisLabel</span><span class="p">)</span><span class="n">bindable</span><span class="p">;</span>
<span class="kt">var</span> <span class="k">value</span> <span class="p">=</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="n">newValue</span><span class="p">;</span>
<span class="n">control</span><span class="p">.</span><span class="nf">ApplyText</span><span class="p">((</span><span class="kt">string</span><span class="p">)</span><span class="n">oldValue</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// TODO: Handle exception.</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>So from there it’s just a matter of doing the animations:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">async</span> <span class="k">void</span> <span class="nf">ApplyText</span><span class="p">(</span><span class="kt">string</span> <span class="n">oldValue</span><span class="p">,</span> <span class="kt">string</span> <span class="n">newValue</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// update the labels</span>
<span class="n">Current</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="n">oldValue</span><span class="p">;</span>
<span class="n">Current</span><span class="p">.</span><span class="n">TranslationY</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">Current</span><span class="p">.</span><span class="n">TranslationX</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">Current</span><span class="p">.</span><span class="n">Opacity</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>
<span class="c1">// set the starting positions</span>
<span class="n">Current</span><span class="p">.</span><span class="n">TranslationY</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">Current</span><span class="p">.</span><span class="nf">TranslateTo</span><span class="p">(-</span><span class="n">AnimationOffset</span><span class="p">.</span><span class="n">X</span><span class="p">,</span> <span class="p">-</span><span class="n">AnimationOffset</span><span class="p">.</span><span class="n">Y</span><span class="p">);</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">Current</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
<span class="c1">// animate in the next label</span>
<span class="n">Next</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="n">newValue</span><span class="p">;</span>
<span class="n">Next</span><span class="p">.</span><span class="n">TranslationY</span> <span class="p">=</span> <span class="n">AnimationOffset</span><span class="p">.</span><span class="n">Y</span><span class="p">;</span>
<span class="n">Next</span><span class="p">.</span><span class="n">TranslationX</span> <span class="p">=</span> <span class="n">AnimationOffset</span><span class="p">.</span><span class="n">X</span><span class="p">;</span>
<span class="n">Next</span><span class="p">.</span><span class="n">Opacity</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">Next</span><span class="p">.</span><span class="nf">TranslateTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">);</span>
<span class="k">await</span> <span class="n">Next</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
<span class="c1">// recycle the views</span>
<span class="n">Current</span> <span class="p">=</span> <span class="n">NextLabel</span><span class="p">;</span>
<span class="n">Next</span> <span class="p">=</span> <span class="n">CurrentLabel</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>So back in our calling code all we need to do is update the property and the animations get triggered from the bindable property.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">DecreaseButton_Clicked</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">currentQuantity</span> <span class="p">==</span> <span class="m">1</span><span class="p">)</span>
<span class="k">return</span><span class="p">;</span>
<span class="k">else</span>
<span class="n">currentQuantity</span><span class="p">--;</span>
<span class="n">QuantityLabel</span><span class="p">.</span><span class="n">AnimationOffset</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Point</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="p">-</span><span class="m">20</span><span class="p">);</span>
<span class="n">QuantityLabel</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="n">currentQuantity</span><span class="p">.</span><span class="nf">ToString</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">IncreaseButton_Clicked</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">currentQuantity</span><span class="p">++;</span>
<span class="n">QuantityLabel</span><span class="p">.</span><span class="n">AnimationOffset</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Point</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">20</span><span class="p">);</span>
<span class="n">QuantityLabel</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="n">currentQuantity</span><span class="p">.</span><span class="nf">ToString</span><span class="p">();</span>
<span class="p">}</span></code></pre></figure>
<h2 id="chomping-pizza-button">Chomping Pizza Button</h2>
<p>This is my favorite piece of animation. It’s a very playful way of adding the pizza to the shopping cart.</p>
<p><img src="assets/images/posts/pizza/chomping-pizza.gif" /></p>
<p>The key to this animation is having a hidden <code class="highlighter-rouge">FlyingPizza</code> image, which is exactly over top of the normal pizza image. Once the Pizza needs to fly, we make it it visible, animate its <code class="highlighter-rouge">LayoutBounds</code> so that it flies down to the bottom right of the <code class="highlighter-rouge">PlaceOrderButton</code>. Then at the same time, adjust the bounds of the Button to allow a space for the pizza. There is also a little bit of animation to rotate the pizza as it’s flying and as it lands on the button.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">PizzaFly</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// check if the pizza is already flying</span>
<span class="k">if</span> <span class="p">(</span><span class="n">FlyingPizza</span><span class="p">.</span><span class="n">IsVisible</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="c1">// eat the pizza</span>
<span class="n">FlyingPizza</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="c1">// position pizza</span>
<span class="n">AbsoluteLayout</span><span class="p">.</span><span class="nf">SetLayoutBounds</span><span class="p">(</span><span class="n">FlyingPizza</span><span class="p">,</span> <span class="n">Pizza</span><span class="p">.</span><span class="n">Bounds</span><span class="p">);</span>
<span class="c1">// get the bounds of the button</span>
<span class="kt">var</span> <span class="n">buttonBounds</span> <span class="p">=</span> <span class="n">PlaceOrderButton</span><span class="p">.</span><span class="n">Bounds</span><span class="p">;</span>
<span class="c1">// work out where it needs to fly to?</span>
<span class="kt">var</span> <span class="n">size</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Size</span><span class="p">(</span><span class="n">buttonBounds</span><span class="p">.</span><span class="n">Height</span><span class="p">,</span> <span class="n">buttonBounds</span><span class="p">.</span><span class="n">Height</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">location</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Point</span><span class="p">(</span><span class="n">buttonBounds</span><span class="p">.</span><span class="n">Right</span> <span class="p">-</span> <span class="n">size</span><span class="p">.</span><span class="n">Width</span><span class="p">,</span> <span class="n">buttonBounds</span><span class="p">.</span><span class="n">Top</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">chompBounds</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Rectangle</span><span class="p">(</span><span class="n">location</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
<span class="c1">// animate the pizza down</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">FlyingPizza</span><span class="p">.</span><span class="nf">LayoutTo</span><span class="p">(</span><span class="n">chompBounds</span><span class="p">,</span> <span class="m">500</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">FlyingPizza</span><span class="p">.</span><span class="nf">RelRotateTo</span><span class="p">(</span><span class="m">90</span><span class="p">,</span> <span class="m">500</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="c1">// do the button chomp</span>
<span class="kt">var</span> <span class="n">buttonChompBounds</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Rectangle</span><span class="p">(</span><span class="n">PlaceOrderButton</span><span class="p">.</span><span class="n">Bounds</span><span class="p">.</span><span class="n">Location</span><span class="p">,</span>
<span class="k">new</span> <span class="nf">Size</span><span class="p">(</span><span class="n">PlaceOrderButton</span><span class="p">.</span><span class="n">Width</span> <span class="p">-</span> <span class="n">buttonBounds</span><span class="p">.</span><span class="n">Height</span><span class="p">,</span> <span class="n">buttonBounds</span><span class="p">.</span><span class="n">Height</span><span class="p">));</span>
<span class="k">await</span> <span class="n">PlaceOrderButton</span><span class="p">.</span><span class="nf">LayoutTo</span><span class="p">(</span><span class="n">buttonChompBounds</span><span class="p">,</span> <span class="m">500</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">FlyingPizza</span><span class="p">.</span><span class="nf">RelRotateTo</span><span class="p">(-</span><span class="m">90</span><span class="p">,</span> <span class="m">500</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="c1">// close the button chomp</span>
<span class="k">await</span> <span class="n">PlaceOrderButton</span><span class="p">.</span><span class="nf">LayoutTo</span><span class="p">(</span><span class="n">buttonBounds</span><span class="p">,</span> <span class="m">500</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="n">FlyingPizza</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>Not terribly complicated code, but it’s a really nice effect. To move it back out of the button when the quantity decreases there is another method called <code class="highlighter-rouge">RegurgitatePizza</code> which pretty much does the same thing but in reverse.</p>
<h2 id="the-ruler">The Ruler</h2>
<p>As with a lot of complex designs there are often things which require some custom graphics. In this case the ruler for the pizza size is rendered using <code class="highlighter-rouge">SkiaSharp</code>.</p>
<p><img src="assets/images/posts/pizza/ruler.gif" /></p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="n">SKPaint</span> <span class="n">rulerPaint</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SKPaint</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Color</span> <span class="p">=</span> <span class="n">SKColors</span><span class="p">.</span><span class="n">White</span><span class="p">,</span>
<span class="n">StrokeWidth</span> <span class="p">=</span> <span class="m">2</span><span class="p">,</span>
<span class="n">Style</span> <span class="p">=</span> <span class="n">SKPaintStyle</span><span class="p">.</span><span class="n">Stroke</span>
<span class="p">};</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">PizzaRuler_PaintSurface</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">SkiaSharp</span><span class="p">.</span><span class="n">Views</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">SKPaintSurfaceEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">canvas</span> <span class="p">=</span> <span class="n">e</span><span class="p">.</span><span class="n">Surface</span><span class="p">.</span><span class="n">Canvas</span><span class="p">;</span>
<span class="c1">// draw the main ruler line</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">DrawLine</span><span class="p">(</span><span class="k">new</span> <span class="nf">SKPoint</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="k">new</span> <span class="nf">SKPoint</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">Info</span><span class="p">.</span><span class="n">Width</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="n">rulerPaint</span><span class="p">);</span>
<span class="c1">// draw the ticks</span>
<span class="kt">var</span> <span class="n">numberOfTicks</span> <span class="p">=</span> <span class="m">30</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">distanceBetweenTicks</span> <span class="p">=</span> <span class="n">e</span><span class="p">.</span><span class="n">Info</span><span class="p">.</span><span class="n">Width</span> <span class="p">/</span> <span class="n">numberOfTicks</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><=</span> <span class="n">numberOfTicks</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="c1">// every 5th tick is full height</span>
<span class="kt">float</span> <span class="n">tickHeight</span> <span class="p">=</span> <span class="p">(</span><span class="n">i</span> <span class="p">%</span> <span class="m">5</span><span class="p">)</span> <span class="p">==</span> <span class="m">0</span> <span class="p">?</span> <span class="n">e</span><span class="p">.</span><span class="n">Info</span><span class="p">.</span><span class="n">Height</span> <span class="p">:</span> <span class="p">(</span><span class="kt">float</span><span class="p">)(</span><span class="n">e</span><span class="p">.</span><span class="n">Info</span><span class="p">.</span><span class="n">Height</span> <span class="p">/</span> <span class="m">2</span><span class="p">);</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">DrawLine</span><span class="p">(</span>
<span class="k">new</span> <span class="nf">SKPoint</span><span class="p">(</span><span class="n">i</span> <span class="p">*</span> <span class="n">distanceBetweenTicks</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">SKPoint</span><span class="p">(</span><span class="n">i</span> <span class="p">*</span> <span class="n">distanceBetweenTicks</span><span class="p">,</span> <span class="n">tickHeight</span><span class="p">),</span>
<span class="n">rulerPaint</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>And finally there is the button which appears on the ruler, which is only interesting in so much that it has a constant animation showing the direction of the arrow. You can’t achieve this with the normal Animation extension methods but with a custom animation you can do this very easily by just setting the repeat to true:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="c1">// create continuous animation for thumb</span>
<span class="n">pulse</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Animation</span><span class="p">();</span>
<span class="n">pulse</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="p">.</span><span class="m">5</span><span class="p">,</span> <span class="k">new</span> <span class="nf">Animation</span><span class="p">(</span><span class="n">a</span> <span class="p">=></span> <span class="n">PizzaThumbLabel</span><span class="p">.</span><span class="n">TranslationX</span> <span class="p">=</span> <span class="n">a</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">5</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">));</span>
<span class="n">pulse</span><span class="p">.</span><span class="nf">Add</span><span class="p">(.</span><span class="m">5</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="k">new</span> <span class="nf">Animation</span><span class="p">(</span><span class="n">a</span> <span class="p">=></span> <span class="n">PizzaThumbLabel</span><span class="p">.</span><span class="n">TranslationX</span> <span class="p">=</span> <span class="n">a</span><span class="p">,</span> <span class="m">5</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">));</span>
<span class="c1">// start the animation continuously</span>
<span class="n">pulse</span><span class="p">.</span><span class="nf">Commit</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"pulse"</span><span class="p">,</span> <span class="n">length</span><span class="p">:</span><span class="m">500</span><span class="p">,</span> <span class="n">repeat</span><span class="p">:</span> <span class="p">()</span> <span class="p">=></span> <span class="k">true</span><span class="p">);</span></code></pre></figure>
<h2 id="summary">Summary</h2>
<p>Okay, so that’s the key elements in this UI Challenge. It was a lot of fun to put together and the final result is pretty delicious.</p>
<p><img src="assets/images/posts/pizza/final.gif" /></p>
<h2 id="get-the-code">Get the code</h2>
<p>There are some complexities which I haven’t gone into in this blog post, but I’ve covered most of the interesting ingredients. All the code is available open source on <a href="https://github.com/kphillpotts/pizza">my github</a>.</p>
<h2 id="watch-me-code-it">Watch me code it</h2>
<p>I actually did this UI Challenge live over Twitch, so if you want to watch hours of me coding this up then check out these recordings.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ffgTho5YBCE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/3q2y15POx6g" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/IpuJtIPdaK0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/pKveUoplD7E" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<hr />
<p>If you want to catch me doing other live coding things follow me on Twitch. It’s a great platform where we can chat as we build software. Follow me at <a href="https://www.twitch.tv/kymphillpotts">https://www.twitch.tv/kymphillpotts</a> and come join in the fun!</p>
<p><a href="https://twitch.tv/kymphillpotts"><img src="https://kymphillpotts.com/assets/images/twitch_banner.png" alt="Kym's Twitch Channel" /></a></p>
<p>If you can’t make it to the Twitch streams, then I also upload the videos to my <a href="https://www.youtube.com/user/kphillpotts/">YouTube Channel</a></p>
<p>I hope these posts are useful for you, feel free to leave me a comment below or reach out to me via <a href="https://twitter.com/kphillpotts">Twitter</a>.</p>
<p>Until next time, Happy Coding</p>
<p>❤
Kym</p>Kym PhillpottsPizza is like the entire food pyramid. And This Xamarin UI Challenge, like our friend the pizza, has lots of delicious ingredients including animations, transitions, custom buttons, fonts and a sprinkle of SkiaSharp. Nom Nom.Xamarin.Forms UI Challenges - RottenUI2019-12-20T16:00:24+11:002019-12-20T16:00:24+11:00https://kymphillpotts.com/xamarin-forms-ui-challenge-rottenui<p>This UI Challenge is all about composing overlapping elements in Xamarin.Forms. Overlapping elements is one of the subtle elements that can enhance a design and make it pop.</p>
<h2 id="the-design">The Design</h2>
<p><img src="assets/images/posts/rottenui/rottenui.png" /></p>
<p>This is a great looking design called <a href="https://dribbble.com/shots/2784573-Rottentomatoes-App-Concept">Rottentomatoes App Concept</a> created by <a href="https://dribbble.com/ghanipradita">Ghani Pradita</a>. Even though the design has two screens in it, the focus of this chanllenge was the details page.</p>
<p><img src="assets/images/posts/rottenui/rottenuiresult.png" /></p>
<p>The key elements we will focus on in this challenge are:</p>
<ul>
<li>Overlapping elements</li>
<li>Custom rendering of shadows</li>
<li>Tabbed bottom content</li>
</ul>
<h2 id="basic-page-layout">Basic Page Layout</h2>
<p>If you have checked out any of my other Xamarin.Forms UI Challenges, you are probably aware that I really enjoy using a <code class="highlighter-rouge">Grid</code> as the main layout container for pages. At the surface the <code class="highlighter-rouge">Grid</code> seems to be quite limited in terms of functionality, but the <code class="highlighter-rouge">Grid</code> provides a couple of great benefits:</p>
<ul>
<li>Responsive design</li>
<li>Overlapping elements</li>
</ul>
<p>The <code class="highlighter-rouge">Grid</code> makes a perfect layout for this sort of design. The <code class="highlighter-rouge">Grid</code> occupies the page, and is divided into two rows. The first row is for the background header of 200 units, whilst the second row occupies the rest of the page and is where the main content goes.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Grid></span>
<span class="nt"><Grid.RowDefinitions></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"200"</span> <span class="nt">/></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"></Grid.RowDefinitions></span>
...
<span class="nt"></Grid></span></code></pre></figure>
<p>Okay, so let’s break this down into the various sections.</p>
<h2 id="the-header">The Header</h2>
<p>The header for the page has a few overlapped components. First we have the background image, over that we have a play button and a white overlay.</p>
<p><img src="assets/images/posts/rottenui/pageheader.png" /></p>
<p>This is nice and simple to implement with a <code class="highlighter-rouge">Grid</code> because we can just put the elements into the same cell of the <code class="highlighter-rouge">Grid</code> to have them overlay. In this situation, order is important because the order that you put the elements in your <code class="highlighter-rouge">xaml</code> file indicates the z-order. Sometimes this can be a little confusing because the elements you want to be in front will actually go down the bottom of your <code class="highlighter-rouge">xaml</code>.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Image</span>
<span class="na">Aspect=</span><span class="s">"AspectFill"</span>
<span class="na">HeightRequest=</span><span class="s">"200"</span>
<span class="na">Source=</span><span class="s">"{Binding BackdropUrl}"</span> <span class="nt">/></span>
<span class="nt"><Image</span>
<span class="na">Aspect=</span><span class="s">"Fill"</span>
<span class="na">HeightRequest=</span><span class="s">"100"</span>
<span class="na">Source=</span><span class="s">"white_gradient"</span>
<span class="na">VerticalOptions=</span><span class="s">"End"</span> <span class="nt">/></span>
<span class="nt"><Image</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Source=</span><span class="s">"play_button"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span></code></pre></figure>
<p>Probably the only interesting point here is the white gradient down the bottom of the header. There are a few different ways you could implement this. First, and probably the technically best solution is to use SkiaSharp to render a transparent to white gradient. In this case though, I have just implemented this with a very simple <code class="highlighter-rouge">.png</code> file with a gradient and overlayed it over the image. Sometimes simple just works.</p>
<h2 id="the-movie-details">The Movie Details</h2>
<p>Okay onto the more interesting part, which is the movie details.</p>
<p><img src="assets/images/posts/rottenui/moviedetails.png" /></p>
<p>This get’s a little bit funky because we have numerous elements overlapping within the details section as well as the whole thing overlapping the header. The trick here is that the details of the page actually sit in Row 0 of the table but span across 2 rows. (hence it sits above and overlapping the header).</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Grid</span>
<span class="na">Grid.Row=</span><span class="s">"0"</span>
<span class="na">Grid.RowSpan=</span><span class="s">"2"</span>
<span class="na">Margin=</span><span class="s">"14,172,14,0"</span>
<span class="na">ColumnSpacing=</span><span class="s">"0"</span>
<span class="na">IsClippedToBounds=</span><span class="s">"True"</span>
<span class="na">RowSpacing=</span><span class="s">"0"</span><span class="nt">></span>
<span class="nt"><Grid.RowDefinitions></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"138"</span> <span class="nt">/></span> <span class="c"><!-- movie detail box --></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"46"</span> <span class="nt">/></span> <span class="c"><!-- rating button row --></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"46"</span> <span class="nt">/></span> <span class="c"><!-- spacing between button and tab--></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"*"</span> <span class="nt">/></span> <span class="c"><!-- rest of the page --></span>
<span class="nt"></Grid.RowDefinitions></span>
<span class="nt"><Grid.ColumnDefinitions></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"150"</span> <span class="nt">/></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"></Grid.ColumnDefinitions></span>
...</code></pre></figure>
<p>There are a couple of things to note in here.</p>
<ul>
<li>The <code class="highlighter-rouge">Margin="14,172,14,0"</code> is what brings the content down so that it doesn’t sit entirely at the top of the page.</li>
<li>The <code class="highlighter-rouge">IsClippedToBounds="True"</code> is what allows the FRESH text to be clipped to the grid.</li>
</ul>
<p>Okay, let’s break it down even further into the elements:</p>
<h3 id="movie-information-container">Movie Information Container</h3>
<p><img src="assets/images/posts/rottenui/movieinfocontainer.png" /></p>
<p>The main container for the movie information is a white <code class="highlighter-rouge">BoxView</code> and a green <code class="highlighter-rouge">Boxview</code> for the button with a combination of <code class="highlighter-rouge">CornerRadius</code> to make it look pretty.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="c"><!-- white panel --></span>
<span class="nt"><BoxView</span>
<span class="na">Grid.Row=</span><span class="s">"0"</span>
<span class="na">Grid.ColumnSpan=</span><span class="s">"2"</span>
<span class="na">BackgroundColor=</span><span class="s">"White"</span>
<span class="na">CornerRadius=</span><span class="s">"6,6,0,0"</span> <span class="nt">/></span>
<span class="c"><!-- the add your rating button --></span>
<span class="nt"><BoxView</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span>
<span class="na">Grid.ColumnSpan=</span><span class="s">"2"</span>
<span class="na">BackgroundColor=</span><span class="s">"{StaticResource ButtonColor}"</span>
<span class="na">CornerRadius=</span><span class="s">"0,0,6,6"</span> <span class="nt">/></span>
<span class="nt"><Label</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span>
<span class="na">Grid.ColumnSpan=</span><span class="s">"2"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Style=</span><span class="s">"{StaticResource MovieName}"</span>
<span class="na">Text=</span><span class="s">"ADD YOUR RATING"</span>
<span class="na">TextColor=</span><span class="s">"White"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span> <span class="nt">/></span>
<span class="c"><!-- button shadow --></span>
<span class="nt"><skia:SKCanvasView</span>
<span class="na">Grid.Row=</span><span class="s">"2"</span>
<span class="na">Grid.RowSpan=</span><span class="s">"2"</span>
<span class="na">Grid.ColumnSpan=</span><span class="s">"2"</span>
<span class="na">PaintSurface=</span><span class="s">"SKCanvasView_PaintSurface"</span> <span class="nt">/></span></code></pre></figure>
<p>The hardest part was the shadow for the button. If you look at it carefully, you’ll notice that it’s actually smaller than the button and has a big blur. I had to do this with SkiaSharp - because that’s where I go when I need to render funky things ;-)</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">void</span> <span class="nf">SKCanvasView_PaintSurface</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">SkiaSharp</span><span class="p">.</span><span class="n">Views</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">SKPaintSurfaceEventArgs</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">SKImageInfo</span> <span class="n">info</span> <span class="p">=</span> <span class="n">args</span><span class="p">.</span><span class="n">Info</span><span class="p">;</span>
<span class="n">SKSurface</span> <span class="n">surface</span> <span class="p">=</span> <span class="n">args</span><span class="p">.</span><span class="n">Surface</span><span class="p">;</span>
<span class="n">SKCanvas</span> <span class="n">canvas</span> <span class="p">=</span> <span class="n">surface</span><span class="p">.</span><span class="n">Canvas</span><span class="p">;</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="k">using</span> <span class="p">(</span><span class="n">SKPaint</span> <span class="n">paint</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SKPaint</span><span class="p">())</span>
<span class="p">{</span>
<span class="c1">// define the color for the shadow</span>
<span class="n">SKColor</span> <span class="n">shadowColor</span> <span class="p">=</span> <span class="n">Color</span><span class="p">.</span><span class="nf">FromHex</span><span class="p">(</span><span class="s">"#5ACB6E"</span><span class="p">).</span><span class="nf">ToSKColor</span><span class="p">();</span>
<span class="n">paint</span><span class="p">.</span><span class="n">IsDither</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">paint</span><span class="p">.</span><span class="n">IsAntialias</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">paint</span><span class="p">.</span><span class="n">Color</span> <span class="p">=</span> <span class="n">shadowColor</span><span class="p">;</span>
<span class="c1">// create filter for drop shadow</span>
<span class="n">paint</span><span class="p">.</span><span class="n">ImageFilter</span> <span class="p">=</span> <span class="n">SKImageFilter</span><span class="p">.</span><span class="nf">CreateDropShadow</span><span class="p">(</span>
<span class="n">dx</span><span class="p">:</span> <span class="m">0</span><span class="p">,</span> <span class="n">dy</span><span class="p">:</span> <span class="m">0</span><span class="p">,</span>
<span class="n">sigmaX</span><span class="p">:</span> <span class="m">40</span><span class="p">,</span> <span class="n">sigmaY</span><span class="p">:</span> <span class="m">40</span><span class="p">,</span>
<span class="n">color</span><span class="p">:</span> <span class="n">shadowColor</span><span class="p">,</span>
<span class="n">shadowMode</span><span class="p">:</span> <span class="n">SKDropShadowImageFilterShadowMode</span><span class="p">.</span><span class="n">DrawShadowOnly</span><span class="p">);</span>
<span class="c1">// define where I want to draw the object</span>
<span class="kt">var</span> <span class="n">margin</span> <span class="p">=</span> <span class="n">info</span><span class="p">.</span><span class="n">Width</span> <span class="p">/</span> <span class="m">10</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">shadowBounds</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SKRect</span><span class="p">(</span><span class="n">margin</span><span class="p">,</span> <span class="p">-</span><span class="m">40</span><span class="p">,</span> <span class="n">info</span><span class="p">.</span><span class="n">Width</span> <span class="p">-</span> <span class="n">margin</span><span class="p">,</span> <span class="m">10</span><span class="p">);</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">DrawRoundRect</span><span class="p">(</span><span class="n">shadowBounds</span><span class="p">,</span> <span class="m">10</span><span class="p">,</span> <span class="m">10</span><span class="p">,</span> <span class="n">paint</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Fortunately, Skia is amazing and allows us to easily do things like create a shadow effect.</p>
<h3 id="movie-information">Movie Information</h3>
<p>The next bit is the movie information and ratings inside the container. This is really just combination of <code class="highlighter-rouge">Grid</code> and <code class="highlighter-rouge">StackLayout</code>.</p>
<p><img src="assets/images/posts/rottenui/movieinfodetails.png" /></p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="c"><!-- movie information --></span>
<span class="nt"><Grid</span> <span class="na">Grid.Row=</span><span class="s">"0"</span> <span class="na">Grid.Column=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><Grid.RowDefinitions></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"></Grid.RowDefinitions></span>
<span class="nt"><Image</span>
<span class="na">Margin=</span><span class="s">"0,0,-55,0"</span>
<span class="na">Source=</span><span class="s">"Fresh"</span>
<span class="na">TranslationX=</span><span class="s">"-28"</span> <span class="nt">/></span>
<span class="nt"><StackLayout</span> <span class="na">Margin=</span><span class="s">"14,0,0,0"</span> <span class="na">VerticalOptions=</span><span class="s">"Center"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource MovieName}"</span> <span class="na">Text=</span><span class="s">"{Binding Title}"</span> <span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource SubText}"</span> <span class="na">Text=</span><span class="s">"{Binding ReleaseDate}"</span> <span class="nt">/></span>
<span class="nt"></StackLayout></span>
<span class="nt"><BoxView</span>
<span class="na">Margin=</span><span class="s">"0,0,14,0"</span>
<span class="na">HeightRequest=</span><span class="s">".5"</span>
<span class="na">VerticalOptions=</span><span class="s">"End"</span>
<span class="na">Color=</span><span class="s">"{StaticResource SubTextColor}"</span> <span class="nt">/></span>
<span class="nt"><Grid</span> <span class="na">Grid.Row=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><Grid.ColumnDefinitions></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"></Grid.ColumnDefinitions></span>
<span class="nt"><StackLayout</span> <span class="na">Margin=</span><span class="s">"14,0,0,0"</span> <span class="na">VerticalOptions=</span><span class="s">"Center"</span><span class="nt">></span>
<span class="nt"><StackLayout</span> <span class="na">Orientation=</span><span class="s">"Horizontal"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Source=</span><span class="s">"rotten_popcorn"</span> <span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource MovieName}"</span> <span class="na">Text=</span><span class="s">"{Binding AudienceScore, StringFormat='{0:}%'}"</span> <span class="nt">/></span>
<span class="nt"></StackLayout></span>
<span class="nt"><Label</span>
<span class="na">Margin=</span><span class="s">"0,-5,0,0"</span>
<span class="na">Style=</span><span class="s">"{StaticResource SubText}"</span>
<span class="na">Text=</span><span class="s">"Audience"</span> <span class="nt">/></span>
<span class="nt"></StackLayout></span>
<span class="nt"><StackLayout</span>
<span class="na">Grid.Column=</span><span class="s">"1"</span>
<span class="na">Margin=</span><span class="s">"14,0,0,0"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span><span class="nt">></span>
<span class="nt"><StackLayout</span> <span class="na">Orientation=</span><span class="s">"Horizontal"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Source=</span><span class="s">"rotten_tomato"</span> <span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource MovieName}"</span> <span class="na">Text=</span><span class="s">"{Binding TomatometerScore, StringFormat='{0:}%'}"</span> <span class="nt">/></span>
<span class="nt"></StackLayout></span>
<span class="nt"><Label</span>
<span class="na">Margin=</span><span class="s">"0,-5,0,0"</span>
<span class="na">Style=</span><span class="s">"{StaticResource SubText}"</span>
<span class="na">Text=</span><span class="s">"Tomatometer"</span> <span class="nt">/></span>
<span class="nt"></StackLayout></span>
<span class="nt"></Grid></span>
<span class="nt"></Grid></span></code></pre></figure>
<p>For the FRESH text unfortunately I couldn’t see a way of getting that to work with a label because the font spacing and size is very specific, so instead I used an image like this:</p>
<p><img src="assets/images/posts/rottenui/fresh.png" /></p>
<p>and then translated it using a negative right margin (to make it wider than the cell) and then translated it to the left so that it would go under the poster.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Image</span>
<span class="na">Margin=</span><span class="s">"0,0,-55,0"</span>
<span class="na">Source=</span><span class="s">"Fresh"</span>
<span class="na">TranslationX=</span><span class="s">"-28"</span> <span class="nt">/></span></code></pre></figure>
<h3 id="the-poster">The Poster</h3>
<p>And that just leaves us with the poster. I used the most excellent <code class="highlighter-rouge">PancakeView</code> to get the rounded corners. But what is interesting about this is that because it’s got to go over all the other information it is the very last thing that appears in the <code class="highlighter-rouge">xaml</code> even though it’s at the top.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><pancake:PancakeView</span>
<span class="na">Grid.RowSpan=</span><span class="s">"2"</span>
<span class="na">Margin=</span><span class="s">"28,126,0,0"</span>
<span class="na">CornerRadius=</span><span class="s">"20"</span>
<span class="na">HeightRequest=</span><span class="s">"170"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Start"</span>
<span class="na">IsClippedToBounds=</span><span class="s">"True"</span>
<span class="na">VerticalOptions=</span><span class="s">"Start"</span>
<span class="na">WidthRequest=</span><span class="s">"125"</span><span class="nt">></span>
<span class="nt"><Image</span>
<span class="na">Aspect=</span><span class="s">"AspectFill"</span>
<span class="na">HeightRequest=</span><span class="s">"170"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Start"</span>
<span class="na">Source=</span><span class="s">"{Binding PosterUrl}"</span>
<span class="na">VerticalOptions=</span><span class="s">"Start"</span>
<span class="na">WidthRequest=</span><span class="s">"125"</span> <span class="nt">/></span>
<span class="nt"></pancake:PancakeView></span></code></pre></figure>
<h2 id="the-tabbed-bottom-content">The Tabbed Bottom Content</h2>
<p>Alright, so that just leaves us with the bottom tabs. In Xamarin.Forms there is a <code class="highlighter-rouge">TabbedPage</code>, but not a tabbed view. There are a number of ones available in the Open Source community, but none that would deo exactly what I was after. But, it is super easy to create your own.</p>
<p><img src="assets/videos/rottenuitab.gif" /></p>
<p>In our case we implemented this in it’s own <code class="highlighter-rouge">ContentView</code> so as not to bloat the size of the <code class="highlighter-rouge">MainPage</code>. It’s implemented in a file called <code class="highlighter-rouge">DetailsSection.xaml</code>. The layout for the tab headers is just a <code class="highlighter-rouge">Grid</code> with 5 columns. Each column has a <code class="highlighter-rouge">Label</code> which is shown on the Tab header.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Grid</span> <span class="na">Grid.Row=</span><span class="s">"0"</span><span class="nt">></span>
<span class="nt"><Grid.ColumnDefinitions></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><ColumnDefinition</span> <span class="na">Width=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"></Grid.ColumnDefinitions></span>
<span class="nt"><Label</span>
<span class="na">x:Name=</span><span class="s">"InfoTab"</span>
<span class="na">Grid.Column=</span><span class="s">"0"</span>
<span class="na">Style=</span><span class="s">"{StaticResource SelectedTabLabel}"</span>
<span class="na">Text=</span><span class="s">"Info"</span><span class="nt">></span>
<span class="nt"><Label.GestureRecognizers></span>
<span class="nt"><TapGestureRecognizer</span> <span class="na">Tapped=</span><span class="s">"TapGestureRecognizer_Tapped"</span> <span class="nt">/></span>
<span class="nt"></Label.GestureRecognizers></span>
<span class="nt"></Label></span>
<span class="nt"><Label</span>
<span class="na">x:Name=</span><span class="s">"CastTab"</span>
<span class="na">Grid.Column=</span><span class="s">"1"</span>
<span class="na">Style=</span><span class="s">"{StaticResource TabLabel}"</span>
<span class="na">Text=</span><span class="s">"Cast"</span><span class="nt">></span>
<span class="nt"><Label.GestureRecognizers></span>
<span class="nt"><TapGestureRecognizer</span> <span class="na">Tapped=</span><span class="s">"TapGestureRecognizer_Tapped"</span> <span class="nt">/></span>
<span class="nt"></Label.GestureRecognizers></span>
<span class="nt"></Label></span>
...
<span class="nt"></Grid></span></code></pre></figure>
<p>We also have the little orange element that moves with the selection. This is just a a couple of <code class="highlighter-rouge">BoxView</code> elements (which we will animate later)</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><BoxView</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span>
<span class="na">HeightRequest=</span><span class="s">"1"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span>
<span class="na">Color=</span><span class="s">"{StaticResource SubTextColor}"</span> <span class="nt">/></span>
<span class="nt"><BoxView</span>
<span class="na">x:Name=</span><span class="s">"SelectionUnderline"</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span>
<span class="na">CornerRadius=</span><span class="s">"2"</span>
<span class="na">HeightRequest=</span><span class="s">"5"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Start"</span>
<span class="na">WidthRequest=</span><span class="s">"40"</span>
<span class="na">Color=</span><span class="s">"Orange"</span> <span class="nt">/></span></code></pre></figure>
<p>To be fair, there is a tiny bit of structure for the control by having a couple of collections for the headers and contents. We load these up in the constructor.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="kt">int</span> <span class="n">selectionIndex</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">List</span><span class="p"><</span><span class="n">Label</span><span class="p">></span> <span class="n">tabHeaders</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">Label</span><span class="p">>();</span>
<span class="n">List</span><span class="p"><</span><span class="n">VisualElement</span><span class="p">></span> <span class="n">tabContents</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">VisualElement</span><span class="p">>();</span>
<span class="k">public</span> <span class="nf">DetailsSection</span><span class="p">()</span>
<span class="p">{</span>
<span class="nf">InitializeComponent</span><span class="p">();</span>
<span class="n">tabHeaders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">InfoTab</span><span class="p">);</span>
<span class="n">tabHeaders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">CastTab</span><span class="p">);</span>
<span class="n">tabHeaders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">NewsTab</span><span class="p">);</span>
<span class="n">tabHeaders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">CriticsTab</span><span class="p">);</span>
<span class="n">tabHeaders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">MediaTab</span><span class="p">);</span>
<span class="n">tabContents</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">InfoContent</span><span class="p">);</span>
<span class="n">tabContents</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">CastContent</span><span class="p">);</span>
<span class="n">tabContents</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">NewsContent</span><span class="p">);</span>
<span class="n">tabContents</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">CriticsContent</span><span class="p">);</span>
<span class="n">tabContents</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">MediaContent</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>For each of the headers labels we have <code class="highlighter-rouge">TapGestureRecognizer</code> that all call the same method. That method works out which index we have selected and then calls a method to do the tab switching.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">TapGestureRecognizer_Tapped</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">tabIndex</span> <span class="p">=</span> <span class="n">tabHeaders</span><span class="p">.</span><span class="nf">IndexOf</span><span class="p">((</span><span class="n">Label</span><span class="p">)</span><span class="n">sender</span><span class="p">);</span>
<span class="k">await</span> <span class="nf">ShowSelection</span><span class="p">(</span><span class="n">tabIndex</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">ShowSelection</span><span class="p">(</span><span class="kt">int</span> <span class="n">newTab</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">newTab</span> <span class="p">==</span> <span class="n">selectionIndex</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="c1">// navigate the selection pill</span>
<span class="kt">var</span> <span class="n">selectdTabLabel</span> <span class="p">=</span> <span class="n">tabHeaders</span><span class="p">[</span><span class="n">newTab</span><span class="p">];</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">SelectionUnderline</span><span class="p">.</span><span class="nf">TranslateTo</span><span class="p">(</span><span class="n">selectdTabLabel</span><span class="p">.</span><span class="n">Bounds</span><span class="p">.</span><span class="n">X</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">150</span><span class="p">,</span> <span class="n">easing</span><span class="p">:</span><span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="c1">// update the style of the header to show it's selcted</span>
<span class="kt">var</span> <span class="n">unselectedStyle</span> <span class="p">=</span> <span class="p">(</span><span class="n">Style</span><span class="p">)</span><span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"TabLabel"</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">selectedStyle</span> <span class="p">=</span> <span class="p">(</span><span class="n">Style</span><span class="p">)</span><span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"SelectedTabLabel"</span><span class="p">];</span>
<span class="n">tabHeaders</span><span class="p">[</span><span class="n">selectionIndex</span><span class="p">].</span><span class="n">Style</span> <span class="p">=</span> <span class="n">unselectedStyle</span><span class="p">;</span>
<span class="n">selectdTabLabel</span><span class="p">.</span><span class="n">Style</span> <span class="p">=</span> <span class="n">selectedStyle</span><span class="p">;</span>
<span class="c1">/// reveal the contents</span>
<span class="k">await</span> <span class="n">tabContents</span><span class="p">[</span><span class="n">selectionIndex</span><span class="p">].</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
<span class="n">tabContents</span><span class="p">[</span><span class="n">selectionIndex</span><span class="p">].</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="n">tabContents</span><span class="p">[</span><span class="n">newTab</span><span class="p">].</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">tabContents</span><span class="p">[</span><span class="n">newTab</span><span class="p">].</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">1</span><span class="p">);</span> <span class="c1">//ybadragon thanks!</span>
<span class="n">selectionIndex</span> <span class="p">=</span> <span class="n">newTab</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>Just to cover off the key things that happen inside <code class="highlighter-rouge">ShowSelection</code> method above:</p>
<ul>
<li>We use a <code class="highlighter-rouge">TranslateTo</code> animation to change the position of the orange indicator to the bounds of the selected header</li>
<li>We use the application resources to change the style of the TabHeader labels</li>
<li>We fade out the existing tab content</li>
<li>We fade in the new tab content</li>
</ul>
<h1 id="summary">Summary</h1>
<p>Okay, so that’s the key elements in this UI Challenge. It was definitely a fun UI to put together, and not too difficult when you break it apart into it’s elements. Just remember people, <code class="highlighter-rouge">Grid</code>s are your friends!</p>
<div class="myvideo">
<video style="display:block;" autoplay="" controls="" loop="loop">
<source src="assets/videos/rottenui.mp4" type="video/mp4" />
</video>
</div>
<h2 id="components-used">Components Used</h2>
<ul>
<li><a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/">SkiaSharp</a></li>
<li><a href="https://github.com/sthewissen/Xamarin.Forms.PancakeView">PancakeView</a></li>
</ul>
<h2 id="get-the-code">Get the code</h2>
<p>All the code is available open source on <a href="https://github.com/kphillpotts/rottenui">my github</a>.</p>
<h2 id="watch-me-code-it">Watch me code it</h2>
<p>I actually did this UI Challenge live over Twitch, so if you want to watch hours of me coding this up then check out these recordings. And as a special bit of fun James Montemagno jumped in on the stream towards the end and shows a trick or two. Thanks James!</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/7xNszLcE_A4" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<hr />
<p>If you want to catch me doing other live coding things follow me on Twitch. It’s a great platform where we can chat as we build software, ask questions, submit code). Follow me at <a href="https://www.twitch.tv/kymphillpotts">https://www.twitch.tv/kymphillpotts</a> and come join in the fun!</p>
<p><a href="https://twitch.tv/kymphillpotts"><img src="https://kymphillpotts.com/assets/images/twitch_banner.png" alt="Kym's Twitch Channel" /></a></p>
<p>If you can’t make it to the Twitch streams, then I also upload the videos to my <a href="https://www.youtube.com/user/kphillpotts/">YouTube Channel</a></p>
<p>I hope these posts are useful for you, feel free to leave me a comment below or reach out to me via <a href="https://twitter.com/kphillpotts">Twitter</a>.</p>
<p>Until next time, Happy Coding</p>
<p>❤
Kym</p>Kym PhillpottsThis UI Challenge is all about composing overlapping elements in Xamarin.Forms. Overlapping elements is one of the subtle elements that can enhance a design and make it pop.Xamarin.Forms UI Challenges - Unzone2019-09-27T12:40:00+10:002019-09-27T12:40:00+10:00https://kymphillpotts.com/xamarin-forms-ui-challenge-unzone<p>Well thought out animations can help your applications come to life through having better user experience and a sense of joy. This Xamarin.Forms UI Challenge illustrates how simple it can be to add compelling animations into your applications.</p>
<h2 id="the-design">The Design</h2>
<p><img src="assets/images/posts/unzone/unzone_animation.gif" /></p>
<p>This is a great looking design called <a href="https://dribbble.com/shots/1551934-Unzone-Details-popup-animation">Unzone</a> created by <a href="https://dribbble.com/rickwaalders">Rick Waalders</a>. The star of this design is the flip animations, which do a great job of adding personality and being functional without becoming annoying.</p>
<p>The key elements we will focus on in this challenge are:</p>
<ul>
<li>Creating a modal dialog style interaction by fading the background when a cell is clicked</li>
<li>Flying in the cancel button from the bottom of the page</li>
<li>Open and Close animations for the buttons on the modal dialog</li>
<li>Flipping the dialog when the Delete button is clicked</li>
<li>Remove a cell with a nice little bounce effect.</li>
</ul>
<h2 id="basic-page-layout">Basic Page Layout</h2>
<p>If you have checked out any of my other Xamarin.Forms UI Challenges, you are probably aware that I really enjoy using a <code class="highlighter-rouge">Grid</code> as the main layout container for pages. However, this challenge requires very exacting positioning of elements so this time we opt for an <code class="highlighter-rouge">AbsoluteLayout</code> to make sure we can get the positioning correct.</p>
<p><code class="highlighter-rouge">AbsoluteLayout</code> is great for very specific positioning and is pretty efficient because it doesn’t consume a bunch of layout cycles. However, it is pretty complex and you don’t get some of the dynamic goodness of managed layouts (like <code class="highlighter-rouge">Grid</code>).</p>
<blockquote>
<p>Do check out the <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/absolute-layout">documentation</a>, which is top notch.</p>
</blockquote>
<p>The list of cells is a <code class="highlighter-rouge">StackLayout</code> with a <code class="highlighter-rouge">BindableLayout.ItemsSource</code> to create a cell for each row of data.</p>
<h2 id="cells">Cells</h2>
<p>You may notice that there are two types of custom cells, PersonTimeCell and a YouTimeCell. To keep things simple, these two cells are defined in their own classes.</p>
<p><strong>PersonTimeCell</strong></p>
<p><img src="assets/images/posts/unzone/PersonTimeCell.png" /></p>
<p>This is the main cell that is used for the different time zones in the list. If you have a look at it, it’s a pretty simple layout which consists of a <code class="highlighter-rouge">Grid</code> of three columns and a <code class="highlighter-rouge">StackLayout</code> for the middle column.</p>
<p><strong>YouTimeCell</strong></p>
<p><img src="assets/images/posts/unzone/YouTimeCell.png" />
There is really just one of these cells and it is designed to show your current time zone.</p>
<h3 id="datatemplateselector">DataTemplateSelector</h3>
<p>What brings this all together is the <code class="highlighter-rouge">DataTemplateSelector</code> which allows us to specify which cell is used for a particular row of data. If we look at the data that is backing the cells there is a field called <code class="highlighter-rouge">TimeInfoType</code> which specifies if it’s a <em>Person</em> or a <em>You</em> type of cell.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">public</span> <span class="nf">MainViewModel</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Locations</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">TimeInfo</span><span class="p">>();</span>
<span class="n">Locations</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">TimeInfo</span><span class="p">()</span> <span class="p">{</span> <span class="n">UserName</span> <span class="p">=</span> <span class="s">"Kym"</span><span class="p">,</span> <span class="n">CurrentTime</span> <span class="p">=</span> <span class="s">"10:49"</span><span class="p">,</span> <span class="n">Location</span> <span class="p">=</span> <span class="s">"Melbourne"</span><span class="p">,</span> <span class="n">TimeZoneId</span> <span class="p">=</span> <span class="s">"AEST"</span><span class="p">,</span> <span class="n">TimeInfoType</span> <span class="p">=</span> <span class="n">TimeInfo</span><span class="p">.</span><span class="n">TimeInfoTypeEnum</span><span class="p">.</span><span class="n">Person</span> <span class="p">});</span>
<span class="n">Locations</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">TimeInfo</span><span class="p">()</span> <span class="p">{</span> <span class="n">UserName</span> <span class="p">=</span> <span class="s">"Frank"</span><span class="p">,</span> <span class="n">CurrentTime</span> <span class="p">=</span> <span class="s">"14:49"</span><span class="p">,</span> <span class="n">Location</span> <span class="p">=</span> <span class="s">"Berlin"</span><span class="p">,</span> <span class="n">TimeZoneId</span> <span class="p">=</span> <span class="s">"CST"</span><span class="p">,</span> <span class="n">TimeInfoType</span> <span class="p">=</span> <span class="n">TimeInfo</span><span class="p">.</span><span class="n">TimeInfoTypeEnum</span><span class="p">.</span><span class="n">Person</span> <span class="p">});</span>
<span class="n">Locations</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">TimeInfo</span><span class="p">()</span> <span class="p">{</span> <span class="n">UserName</span> <span class="p">=</span> <span class="s">"Helen"</span><span class="p">,</span> <span class="n">CurrentTime</span> <span class="p">=</span> <span class="s">"23:00"</span><span class="p">,</span> <span class="n">Location</span> <span class="p">=</span> <span class="s">"Copenhagen"</span><span class="p">,</span> <span class="n">TimeZoneId</span> <span class="p">=</span> <span class="s">"CST"</span><span class="p">,</span> <span class="n">TimeInfoType</span> <span class="p">=</span> <span class="n">TimeInfo</span><span class="p">.</span><span class="n">TimeInfoTypeEnum</span><span class="p">.</span><span class="n">Person</span> <span class="p">});</span>
<span class="n">Locations</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">TimeInfo</span><span class="p">()</span> <span class="p">{</span> <span class="n">UserName</span> <span class="p">=</span> <span class="s">"You"</span><span class="p">,</span> <span class="n">CurrentTime</span> <span class="p">=</span> <span class="s">"23:00"</span><span class="p">,</span> <span class="n">Location</span> <span class="p">=</span> <span class="s">"Melborune"</span><span class="p">,</span> <span class="n">TimeZoneId</span> <span class="p">=</span> <span class="s">"AEST"</span><span class="p">,</span> <span class="n">TimeInfoType</span> <span class="p">=</span> <span class="n">TimeInfo</span><span class="p">.</span><span class="n">TimeInfoTypeEnum</span><span class="p">.</span><span class="n">You</span> <span class="p">});</span>
<span class="n">Locations</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">TimeInfo</span><span class="p">()</span> <span class="p">{</span> <span class="n">UserName</span> <span class="p">=</span> <span class="s">"James"</span><span class="p">,</span> <span class="n">CurrentTime</span> <span class="p">=</span> <span class="s">"11:30"</span><span class="p">,</span> <span class="n">Location</span> <span class="p">=</span> <span class="s">"Seattle"</span><span class="p">,</span> <span class="n">TimeZoneId</span> <span class="p">=</span> <span class="s">"PST"</span><span class="p">,</span> <span class="n">TimeInfoType</span> <span class="p">=</span> <span class="n">TimeInfo</span><span class="p">.</span><span class="n">TimeInfoTypeEnum</span><span class="p">.</span><span class="n">Person</span> <span class="p">});</span>
<span class="p">}</span></code></pre></figure>
<p>It all kind of makes sense when you have a look at our <code class="highlighter-rouge">DataTemplateSelector</code> class.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">public</span> <span class="k">class</span> <span class="nc">TimeCellDataTemplateSelector</span> <span class="p">:</span> <span class="n">DataTemplateSelector</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">DataTemplate</span> <span class="n">PersonTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">DataTemplate</span> <span class="n">YouTemplate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">protected</span> <span class="k">override</span> <span class="n">DataTemplate</span> <span class="nf">OnSelectTemplate</span><span class="p">(</span><span class="kt">object</span> <span class="n">item</span><span class="p">,</span> <span class="n">BindableObject</span> <span class="n">container</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// have a look at the item and return the right type of cell</span>
<span class="k">if</span> <span class="p">(((</span><span class="n">TimeInfo</span><span class="p">)</span><span class="n">item</span><span class="p">).</span><span class="n">TimeInfoType</span> <span class="p">==</span> <span class="n">TimeInfoType</span><span class="p">.</span><span class="n">Person</span><span class="p">)</span>
<span class="k">return</span> <span class="n">PersonTemplate</span><span class="p">;</span>
<span class="k">else</span>
<span class="k">return</span> <span class="n">YouTemplate</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>And then we apply the DataTemplateSelector in our XAML via the <code class="highlighter-rouge">BindableLayout.ItemTemplateSelector</code>.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><ContentPage.Resources></span>
<span class="nt"><ResourceDictionary></span>
<span class="nt"><DataTemplate</span> <span class="na">x:Key=</span><span class="s">"PersonTimeCell"</span><span class="nt">></span>
<span class="nt"><controls:TimeCell</span> <span class="na">HeightRequest=</span><span class="s">"80"</span><span class="nt">></span>
<span class="nt"><controls:TimeCell.GestureRecognizers></span>
<span class="nt"><TapGestureRecognizer</span> <span class="na">Tapped=</span><span class="s">"TimeCellTapRecognizer_Tapped"</span> <span class="nt">/></span>
<span class="nt"></controls:TimeCell.GestureRecognizers></span>
<span class="nt"></controls:TimeCell></span>
<span class="nt"></DataTemplate></span>
<span class="nt"><DataTemplate</span> <span class="na">x:Key=</span><span class="s">"YouTimeCell"</span><span class="nt">></span>
<span class="nt"><controls:YouTimeCell</span> <span class="na">HeightRequest=</span><span class="s">"160"</span> <span class="nt">/></span>
<span class="nt"></DataTemplate></span>
<span class="nt"><controls:TimeCellDataTemplateSelector</span>
<span class="na">x:Key=</span><span class="s">"TimeCellDataTemplateSelector"</span>
<span class="na">PersonTemplate=</span><span class="s">"{StaticResource PersonTimeCell}"</span>
<span class="na">YouTemplate=</span><span class="s">"{StaticResource YouTimeCell}"</span> <span class="nt">/></span>
<span class="nt"></ResourceDictionary></span>
<span class="nt"></ContentPage.Resources></span>
...
<span class="nt"><ScrollView></span>
<span class="nt"><StackLayout</span>
<span class="na">x:Name=</span><span class="s">"ViewStack"</span>
<span class="na">BindableLayout.ItemTemplateSelector=</span><span class="s">"{StaticResource TimeCellDataTemplateSelector}"</span>
<span class="na">BindableLayout.ItemsSource=</span><span class="s">"{Binding Locations}"</span>
<span class="na">Spacing=</span><span class="s">"0"</span> <span class="nt">/></span>
<span class="nt"></ScrollView></span></code></pre></figure>
<p>Okay, so that’s probably enough about how the collection of timezones is constructed. Let’s get into the fun stuff.</p>
<h2 id="popup-with-faded-background">Popup with faded background</h2>
<p>The design has a nice effect when the user selects one of the person cells where it darkens everything except the selected cell.</p>
<div class="myvideo">
<video style="display:block;" autoplay="" controls="" loop="loop">
<source src="assets/videos/UnZone1.mp4" type="video/mp4" />
</video>
</div>
<p>In reality, it’s very simple to achieve. The basics of it are:</p>
<ul>
<li>
<p>Fade in a whole screen dark overlay over the content. This stops the user from selecting things in the background. You can set it’s <code class="highlighter-rouge">BackgroundColor</code> to have some transparency baked in too (<code class="highlighter-rouge">#CC000000</code>) which means the user can still see the content behind it.</p>
</li>
<li>
<p>Have an additional <code class="highlighter-rouge">PersonCell</code> that sits over the overlay which is positioned exactly where the selected cell is located. Now, set the <code class="highlighter-rouge">BindingContext</code> of the <em>fake cell</em> to the binding context of the selected cell so that it shows the same data.</p>
</li>
</ul>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">TimeCellTapRecognizer_Tapped</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// get the cell that was tapped</span>
<span class="n">selectedElement</span> <span class="p">=</span> <span class="p">(</span><span class="n">TimeCell</span><span class="p">)</span><span class="n">sender</span><span class="p">;</span>
<span class="n">selectedIndex</span> <span class="p">=</span> <span class="n">ViewStack</span><span class="p">.</span><span class="n">Children</span><span class="p">.</span><span class="nf">IndexOf</span><span class="p">(</span><span class="n">selectedElement</span><span class="p">);</span>
<span class="c1">// set the binding context to the panel</span>
<span class="c1">// so that it can be inherited by the FakeCell and</span>
<span class="c1">// any other controls required on the popup.</span>
<span class="n">FrontSide</span><span class="p">.</span><span class="n">BindingContext</span> <span class="p">=</span> <span class="n">selectedElement</span><span class="p">.</span><span class="n">BindingContext</span><span class="p">;</span>
<span class="c1">// position the popups</span>
<span class="nf">PositionDropDown</span><span class="p">(</span><span class="n">selectedElement</span><span class="p">,</span> <span class="n">FrontSide</span><span class="p">);</span>
<span class="nf">PositionDropDown</span><span class="p">(</span><span class="n">selectedElement</span><span class="p">,</span> <span class="n">BackSide</span><span class="p">);</span>
<span class="c1">// fade in the overlay</span>
<span class="n">FadeBackground</span><span class="p">.</span><span class="n">Opacity</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">FadeBackground</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">FadeBackground</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">);</span>
<span class="p">...</span>
<span class="p">}</span></code></pre></figure>
<h2 id="animating-the-close-button">Animating the Close Button</h2>
<p>Another really nice effect is the Close button that pops in from the bottom when a cell is selected.</p>
<div class="myvideo">
<video style="display:block;" autoplay="" controls="" loop="loop">
<source src="assets/videos/UnZone2.mp4" type="video/mp4" />
</video>
</div>
<p>This is really just a case of positioning the close button relative to the selected cell and then doing some animations to adjust it’s Position, Rotation and Opacity.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="p">...</span>
<span class="c1">// position the close button</span>
<span class="kt">var</span> <span class="n">padding</span> <span class="p">=</span> <span class="m">40</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">xPos</span> <span class="p">=</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">Width</span> <span class="p">/</span> <span class="m">2</span><span class="p">)</span> <span class="p">-</span> <span class="p">(</span><span class="n">CloseButton</span><span class="p">.</span><span class="n">Width</span> <span class="p">/</span> <span class="m">2</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">yPos</span> <span class="p">=</span> <span class="n">selectedElement</span><span class="p">.</span><span class="n">Bounds</span><span class="p">.</span><span class="n">Y</span> <span class="p">+</span> <span class="n">FrontSide</span><span class="p">.</span><span class="n">Bounds</span><span class="p">.</span><span class="n">Height</span> <span class="p">+</span> <span class="n">padding</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">closeButtonRect</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Rectangle</span><span class="p">(</span><span class="n">xPos</span><span class="p">,</span> <span class="n">yPos</span><span class="p">,</span> <span class="n">CloseButton</span><span class="p">.</span><span class="n">Width</span><span class="p">,</span> <span class="n">CloseButton</span><span class="p">.</span><span class="n">Height</span><span class="p">);</span>
<span class="n">AbsoluteLayout</span><span class="p">.</span><span class="nf">SetLayoutBounds</span><span class="p">(</span><span class="n">CloseButton</span><span class="p">,</span> <span class="n">closeButtonRect</span><span class="p">);</span>
<span class="p">...</span>
<span class="nf">AnimateCloseButton</span><span class="p">(</span><span class="n">CloseButton</span><span class="p">,</span> <span class="n">entering</span><span class="p">:</span><span class="k">true</span><span class="p">);</span>
<span class="p">...</span></code></pre></figure>
<p>The entering and exiting animations vary slightly which is why it passes in an parameter into the AnimateCloseButton method.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">void</span> <span class="nf">AnimateCloseButton</span><span class="p">(</span><span class="n">VisualElement</span> <span class="n">elementToTransform</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">entering</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">startingTranslation</span> <span class="p">=</span> <span class="n">entering</span> <span class="p">?</span> <span class="m">100</span> <span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">endingTranslation</span> <span class="p">=</span> <span class="n">entering</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="m">100</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">translationEasing</span> <span class="p">=</span> <span class="n">entering</span> <span class="p">?</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SpringOut</span> <span class="p">:</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinIn</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">startingOpacity</span> <span class="p">=</span> <span class="n">entering</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="m">1</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">endingOpacity</span> <span class="p">=</span> <span class="n">entering</span> <span class="p">?</span> <span class="m">1</span> <span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">startingRotation</span> <span class="p">=</span> <span class="n">entering</span> <span class="p">?</span> <span class="p">-</span><span class="m">90</span> <span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">endingRotation</span> <span class="p">=</span> <span class="n">entering</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="m">180</span><span class="p">;</span>
<span class="n">elementToTransform</span><span class="p">.</span><span class="n">TranslationY</span> <span class="p">=</span> <span class="n">startingTranslation</span><span class="p">;</span>
<span class="n">elementToTransform</span><span class="p">.</span><span class="n">Opacity</span> <span class="p">=</span> <span class="n">startingOpacity</span><span class="p">;</span>
<span class="n">elementToTransform</span><span class="p">.</span><span class="n">Rotation</span> <span class="p">=</span> <span class="n">startingRotation</span><span class="p">;</span>
<span class="n">elementToTransform</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="n">endingOpacity</span><span class="p">,</span> <span class="m">500</span><span class="p">);</span>
<span class="n">elementToTransform</span><span class="p">.</span><span class="nf">RotateTo</span><span class="p">(</span><span class="n">endingRotation</span><span class="p">,</span> <span class="m">700</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="n">elementToTransform</span><span class="p">.</span><span class="nf">TranslateTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">endingTranslation</span><span class="p">,</span> <span class="m">600</span><span class="p">,</span> <span class="n">translationEasing</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>Really the only interesting part of this is that it uses the animation extension methods which Xamarin.Forms provides, namely <code class="highlighter-rouge">FadeTo</code>, <code class="highlighter-rouge">RotateTo</code> and <code class="highlighter-rouge">TranslateTo</code>. These are awaitable methods, but if you don’t await them, you can kick of multiple animations that run at the same time.</p>
<blockquote>
<p>You might notice that we always position the close image beneath the <em>fake cell</em>, which is mostly fine for the purpose of this UI Challenge, but in reality you and your designer would probably have to think about this a little more. What happens when when the selected cell is at the bottom of the screen?</p>
</blockquote>
<h2 id="folding-down-the-buttons">Folding down the buttons</h2>
<p>Even though the folding down buttons are a little gratuitous they add a bit of personality to the app. The key behind this effect is to adjust the rotation of elements so that they appear to fold down.</p>
<div class="myvideo">
<video style="display:block;" autoplay="" controls="" loop="loop">
<source src="assets/videos/UnZone3.mp4" type="video/mp4" />
</video>
</div>
<blockquote>
<p>Rotation can be done on the X, Y and Z axis</p>
</blockquote>
<p>An important thing to know about rotations is that they happen around the <code class="highlighter-rouge">AnchorX</code> and <code class="highlighter-rouge">AnchorY</code> of the element. Think of the the <code class="highlighter-rouge">Anchor</code> as the rotation point.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Grid</span>
<span class="na">x:Name=</span><span class="s">"DeleteDropDown"</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span>
<span class="na">Grid.Column=</span><span class="s">"0"</span>
<span class="na">AnchorY=</span><span class="s">"0"</span>
<span class="na">BackgroundColor=</span><span class="s">"{StaticResource DropDownColor1}"</span>
<span class="na">HeightRequest=</span><span class="s">"80"</span><span class="nt">></span></code></pre></figure>
<p>The default value for <code class="highlighter-rouge">AnchorX</code> is .5 which means the middle (but not important for what we are doing). The important thing is to have the <code class="highlighter-rouge">AnchorY</code> to “0” which means that the rotation is going to happen around the TOP of the element.</p>
<p><strong>Step 1: Set the starting state</strong></p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="p">...</span>
<span class="c1">// hide the dropdowns</span>
<span class="n">DeleteDropDown</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="n">EditDropDown</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="n">InfoDropDown</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">...</span>
<span class="k">await</span> <span class="nf">OpenDropDown</span><span class="p">(</span><span class="n">DeleteDropDown</span><span class="p">);</span>
<span class="k">await</span> <span class="nf">OpenDropDown</span><span class="p">(</span><span class="n">EditDropDown</span><span class="p">);</span>
<span class="k">await</span> <span class="nf">OpenDropDown</span><span class="p">(</span><span class="n">InfoDropDown</span><span class="p">);</span>
<span class="p">...</span></code></pre></figure>
<p>You might notice that in the code above we are doing an <code class="highlighter-rouge">await</code> on the <code class="highlighter-rouge">OpenDropDown</code> method. This is basically saying wait (without blocking the main UI thread) until the previous dropdown has finished. If we didn’t have that, then they would all animate in at the same time and we would lose of staggered effect.</p>
<p><strong>Step 2: Animate that puppy</strong></p>
<p>When animating in with the <code class="highlighter-rouge">OpenDropDown</code> method we start by setting it’s initial Visibility, RotationX and Opacity.</p>
<p>The fold down animation is actually a Fade and a RotationX. In the below code you can see that the <code class="highlighter-rouge">FadeTo</code> is NOT awaited which means it will happen at the same time as the <code class="highlighter-rouge">RotateXTo</code> happens. It’s a small detail, but what I have learnt about design is that it’s all about the small details.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">OpenDropDown</span><span class="p">(</span><span class="n">View</span> <span class="n">view</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">view</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">view</span><span class="p">.</span><span class="n">RotationX</span> <span class="p">=</span> <span class="p">-</span><span class="m">90</span><span class="p">;</span>
<span class="n">view</span><span class="p">.</span><span class="n">Opacity</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">view</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">);</span>
<span class="k">await</span> <span class="n">view</span><span class="p">.</span><span class="nf">RotateXTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>Closing the drop down is effectively the reverse. Start it fading, wait for it to rotate, and then set it invisible.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">CloseDropDown</span><span class="p">(</span><span class="n">View</span> <span class="n">view</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_</span> <span class="p">=</span> <span class="n">view</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">);</span>
<span class="k">await</span> <span class="n">view</span><span class="p">.</span><span class="nf">RotateXTo</span><span class="p">(-</span><span class="m">90</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">);</span>
<span class="n">view</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h2 id="flipping-the-dialog">Flipping the dialog</h2>
<p>Creating a flip effect consists of having a <code class="highlighter-rouge">from</code> and <code class="highlighter-rouge">to</code> view.</p>
<ol>
<li>Rotating the <code class="highlighter-rouge">from</code> view around the middle of it’s Y axis until you are looking at it’s edge. (effectively invisible)</li>
<li>Swapping to the <code class="highlighter-rouge">to</code> view (also rotated)</li>
<li>Rotate the <code class="highlighter-rouge">to</code> view around it’s middle so that it comes into view.</li>
</ol>
<div class="myvideo">
<video style="display:block;" autoplay="" controls="" loop="loop">
<source src="assets/videos/UnZone4.mp4" type="video/mp4" />
</video>
</div>
<p>It’s simpler than it sounds:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">Flip</span> <span class="p">(</span><span class="n">VisualElement</span> <span class="k">from</span><span class="p">,</span> <span class="n">VisualElement</span> <span class="n">to</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">await</span> <span class="k">from</span><span class="p">.</span><span class="nf">RotateYTo</span><span class="p">(-</span><span class="m">90</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SpringIn</span><span class="p">);</span>
<span class="n">to</span><span class="p">.</span><span class="n">RotationY</span> <span class="p">=</span> <span class="m">90</span><span class="p">;</span>
<span class="n">to</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="k">from</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="k">await</span> <span class="n">to</span><span class="p">.</span><span class="nf">RotateYTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SpringOut</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>As a bonus you might notice there is a nice little <code class="highlighter-rouge">SpringIn</code> and <code class="highlighter-rouge">SpringOut</code> easing to give it a little bounce.</p>
<h2 id="bouncing-up-the-items">Bouncing up the items</h2>
<p>The final effect that is worth mentioning in this UI Challenge is the bounce animation for deleting an item from the list. I’ll admit this is a little hacky but seems to work quite nicely (just don’t try it with thousands of elements)</p>
<p>The basic idea is:</p>
<ul>
<li>Close down the overlays.</li>
<li>Iterate through all the items after the selected element</li>
<li>Create an animation to move it to location of the previous element with a bounce easing</li>
<li>Wait for all those animations to finish</li>
<li>Remove the selected element</li>
</ul>
<div class="myvideo">
<video style="display:block;" autoplay="" controls="" loop="loop">
<source src="assets/videos/UnZone5.mp4" type="video/mp4" />
</video>
</div>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">YesButtonTapGestureRecognizer_Tapped</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// close the overlays</span>
<span class="k">await</span> <span class="nf">Flip</span><span class="p">(</span><span class="n">BackSide</span><span class="p">,</span> <span class="n">FrontSide</span><span class="p">);</span>
<span class="nf">CloseDropDown</span><span class="p">(</span><span class="n">EditDropDown</span><span class="p">);</span>
<span class="k">await</span> <span class="nf">CloseDropDown</span><span class="p">(</span><span class="n">DeleteDropDown</span><span class="p">);</span>
<span class="c1">// fade out the overlay background</span>
<span class="k">await</span> <span class="n">FadeBackground</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">);</span>
<span class="n">FadeBackground</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="c1">// go through all the elements after the selected item</span>
<span class="c1">// and translate them up to overlap the element before</span>
<span class="n">List</span><span class="p"><</span><span class="n">Task</span><span class="p">></span> <span class="n">animations</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">Task</span><span class="p">>();</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="n">selectedIndex</span> <span class="p">+</span> <span class="m">1</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">ViewStack</span><span class="p">.</span><span class="n">Children</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="n">VisualElement</span> <span class="n">elementToMove</span><span class="p">;</span>
<span class="n">elementToMove</span> <span class="p">=</span> <span class="n">ViewStack</span><span class="p">.</span><span class="n">Children</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="c1">// work out the bounds we are going to move them to</span>
<span class="kt">var</span> <span class="n">boundsToMoveTo</span> <span class="p">=</span> <span class="n">elementToMove</span><span class="p">.</span><span class="n">Bounds</span><span class="p">;</span>
<span class="n">boundsToMoveTo</span><span class="p">.</span><span class="n">Top</span> <span class="p">-=</span> <span class="n">selectedElement</span><span class="p">.</span><span class="n">Height</span><span class="p">;</span>
<span class="n">animations</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">elementToMove</span><span class="p">.</span><span class="nf">LayoutTo</span><span class="p">(</span><span class="n">boundsToMoveTo</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">BounceOut</span><span class="p">));</span>
<span class="p">}</span>
<span class="c1">// wait for all those elements to move</span>
<span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">WhenAll</span><span class="p">(</span><span class="n">animations</span><span class="p">);</span>
<span class="c1">// fade out and then remove the selected</span>
<span class="k">await</span> <span class="n">selectedElement</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">animationSpeed</span><span class="p">);</span>
<span class="n">ViewStack</span><span class="p">.</span><span class="n">Children</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">selectedElement</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>BTW, the reason for the FadeOut before removing the element is really just in case you are deleting the last element in the list.</p>
<h1 id="summary">Summary</h1>
<p>Okay, so that’s the key elements in this UI Challenge. It was definitely a fun UI to put together, and not too difficult when you break it apart into it’s elements. Mostly it’s smoke and mirrors but it doesn’t provide a great looking UI.</p>
<h2 id="get-the-code">Get the code</h2>
<p>All the code is available open source on <a href="https://github.com/kphillpotts/UnZone">my github</a>.</p>
<h2 id="watch-me-code-it">Watch me code it</h2>
<p>I actually did this UI Challenge live over Twitch, so if you want to watch hours of me coding this up then check out these recordings.</p>
<h4 id="part-1">Part 1</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/J7UBWGPt3U0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h4 id="part-2">Part 2</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/n6oEFvoAZ3k" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<hr />
<p>If you want to catch me doing other live coding things follow me on Twitch. It’s a great platform where we can chat as we build software, ask questions, submit code). Follow me at <a href="https://www.twitch.tv/kymphillpotts">https://www.twitch.tv/kymphillpotts</a> and come join in the fun!</p>
<p><a href="https://twitch.tv/kymphillpotts"><img src="https://kymphillpotts.com/assets/images/twitch_banner.png" alt="Kym's Twitch Channel" /></a></p>
<p>If you can’t make it to the Twitch streams, then I also upload the videos to my <a href="https://www.youtube.com/user/kphillpotts/">YouTube Channel</a></p>
<p>I hope these posts are useful for you, feel free to leave me a comment below or reach out to me via <a href="https://twitter.com/kphillpotts">Twitter</a>.</p>
<p>Until next time, Happy Coding</p>
<p>❤
Kym</p>Kym PhillpottsWell thought out animations can help your applications come to life through having better user experience and a sense of joy. This Xamarin.Forms UI Challenge illustrates how simple it can be to add compelling animations into your applications.My Visual Studio 2019 Theme2019-09-01T10:00:00+10:002019-09-01T10:00:00+10:00https://kymphillpotts.com/my-visual-studio-2019-theme<p>I’ve had a couple of people ask me for my Visual Studio 2019 theme that I use when live streaming, so I thought I’d just blog it out as a reference for myself and for others who are interested.</p>
<h2 id="whats-it-look-like">What’s it look like</h2>
<p><img src="/assets/images/posts/theme/xaml.jpg" alt="Xaml" title="xaml view" /></p>
<p><img src="/assets/images/posts/theme/code.jpg" alt="Code" title="code view" /></p>
<h2 id="font">Font</h2>
<p>My font is <a href="https://github.com/tonsky/FiraCode">Fira Code Retina</a> which I zoom in at 200%. Mostly because I’m an old man with bad eye-sight, but also because I like my fonts to be big and colourful.</p>
<h2 id="colors">Colors</h2>
<p>To be honest, I can’t remember where I got the initial colors from but over time I have done some customization on them so they are a little unique.</p>
<p><a href="/assets/data/Exported-2019-09-01.vssettings">I’ve exported my settings and you can get them from here</a>. So hopefully if you are interested you can check them out and import them.</p>
<blockquote>
<p>Just don’t blame me if it breaks your Visual Studio ;-)</p>
</blockquote>
<h2 id="extensions-that-i-use">Extensions that I use</h2>
<p>I don’t use a lot of extensions for code formatting, but the ones I do are:</p>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=OmarRwemi.BetterComments">Better Comments</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=StanislavKuzmichArtStea1th.EnhancedSyntaxHighlighting">Enahnced Syntax Highlighting</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=NicoVermeir.XAMLStyler">XAML Styler</a></li>
</ul>
<h2 id="shameless-twitch-plug">Shameless Twitch plug</h2>
<p>If you want to catch me doing live coding follow me on Twitch. It’s a great platform where we can chat as we build software. Follow me at <a href="https://www.twitch.tv/kymphillpotts">https://www.twitch.tv/kymphillpotts</a> and come join in the fun!</p>
<p><a href="https://twitch.tv/kymphillpotts" target="_blank"><img src="assets/images/twitch_banner.png" /></a></p>
<p>I hope you find these posts useful, feel free to leave me a comment below or reach out to me via <a href="https://twitter.com/kphillpotts">Twitter</a> with some feedback.</p>
<p>Until next time, Happy Coding</p>
<p>❤
Kym</p>Kym PhillpottsI’ve had a couple of people ask me for my Visual Studio 2019 theme that I use when live streaming, so I thought I’d just blog it out as a reference for myself and for others who are interested.Xamarin.Forms UI Challenges - Day vs Night2019-07-13T10:00:00+10:002019-07-13T10:00:00+10:00https://kymphillpotts.com/xamarin-forms-ui-challenge-dayvsnight<p>Sometimes when developing Xamarin.Forms user interfaces you get to a point where you just need to handle the drawing yourself, and for that SkiaSharp is invaluable. And that’s one of the core pieces of this UI Challenge, using SkiaSharp to create controls that are unlike any other.</p>
<p>First, I should mention that this amazing design concept that we are reproducing was created by <a href="https://dribbble.com/ionuss">Ionut Zamfir</a> over at <a href="https://dribbble.com/shots/4840427-Dashboard-Day-vs-Night">Dribbble</a>. The key elements that drew my attention to this layout were:</p>
<ul>
<li>Beautiful use of colours</li>
<li>Light and Dark themes</li>
<li>Complex multi-colour gradients</li>
<li>Unique custom slider for the temperature</li>
</ul>
<p>Seriously, just check it out in all it’s beauty:</p>
<table>
<tbody>
<tr>
<td><img src="assets/images/posts/dayvsnight/day.png" /></td>
<td><img src="assets/images/posts/dayvsnight/night.png" /></td>
</tr>
</tbody>
</table>
<h2 id="xamarin-ui-july">Xamarin UI July</h2>
<p>This post is part of <a href="https://www.thewissen.io/introducing-xamarin-ui-july/">Xamarin UI July</a>, organised by <a href="https://twitter.com/devnl">Steve Thewissen</a>. Today is day 13 with another exciting post coming out every day for the rest of the month.</p>
<p><a href="https://www.thewissen.io/introducing-xamarin-ui-july/" target="_blank"><img src="assets/images/xamuijuly.png" /></a></p>
<h2 id="lets-break-it-down">Let’s break it down</h2>
<p>So let’s try and reproduce this layout in Xamarin.Forms. When you look at it, the overall page layout itself is fairly straight forward consisting of a main <code class="highlighter-rouge">Grid</code> with a <code class="highlighter-rouge">StackLayout</code> to lay the elements out down the page.</p>
<blockquote>
<p>Pro Tip: Grids are a great root layout element because they allow you to position elements proportionally on a page whilst still allowing overlapping elements</p>
</blockquote>
<h3 id="the-background">The Background</h3>
<p>If you focus just on the background of the design it has two elements:</p>
<ul>
<li>A multi-colour gradient across the entire page</li>
<li>The mountain image, at the bottom of the page</li>
</ul>
<p>For the implementation the main page consists of a <code class="highlighter-rouge">SKCanvasView</code> for the background gradient. There is also an Image with a <code class="highlighter-rouge">VerticalOptions</code> of <code class="highlighter-rouge">End</code> which positions it at the bottom of the screen but above the gradient.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Grid></span>
<span class="nt"><Grid.RowDefinitions></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"Auto"</span> <span class="nt">/></span>
<span class="nt"></Grid.RowDefinitions></span>
<span class="nt"><skia:SKCanvasView</span>
<span class="na">x:Name=</span><span class="s">"BackgroundGradient"</span>
<span class="na">Grid.RowSpan=</span><span class="s">"2"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Fill"</span>
<span class="na">PaintSurface=</span><span class="s">"BackgroundGradient_PaintSurface"</span>
<span class="na">VerticalOptions=</span><span class="s">"Fill"</span> <span class="nt">/></span>
<span class="nt"><Image</span>
<span class="na">Grid.RowSpan=</span><span class="s">"2"</span>
<span class="na">Aspect=</span><span class="s">"AspectFill"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Fill"</span>
<span class="na">Source=</span><span class="s">"{DynamicResource MountainImage}"</span>
<span class="na">VerticalOptions=</span><span class="s">"End"</span> <span class="nt">/></span>
...</code></pre></figure>
<blockquote>
<p>The order of elements in your Xaml is important because it implies the z-order. Which is to say, elements that appear later in your Xaml will appear in-front of earlier elements</p>
</blockquote>
<p>The background gradient is a little more complex than it may first appear because it isn’t just a linear gradient between two colours. It’s actually a radial gradient between three colours. This complexity means we can’t use something like <code class="highlighter-rouge">PancakeView</code> for the gradient, instead we can render this with SkiaSharp. Complex gradients are surprisingly easy in SkiaSharp, but does require you implement them in code.</p>
<p><em>Code to render gradient</em></p>
<p>The following code is called whenever the <code class="highlighter-rouge">BackgroundGradient</code> canvas needs to be drawn. First thing it does is load up the colours from the application resources (because different themes have different gradient colours - but more on that later). Then it uses those colours to create a <code class="highlighter-rouge">RadialGradient</code> shader which is drawn over the entire canvas area.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">void</span> <span class="nf">BackgroundGradient_PaintSurface</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">SkiaSharp</span><span class="p">.</span><span class="n">Views</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">SKPaintSurfaceEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">SKImageInfo</span> <span class="n">info</span> <span class="p">=</span> <span class="n">e</span><span class="p">.</span><span class="n">Info</span><span class="p">;</span>
<span class="n">SKSurface</span> <span class="n">surface</span> <span class="p">=</span> <span class="n">e</span><span class="p">.</span><span class="n">Surface</span><span class="p">;</span>
<span class="n">SKCanvas</span> <span class="n">canvas</span> <span class="p">=</span> <span class="n">surface</span><span class="p">.</span><span class="n">Canvas</span><span class="p">;</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="c1">// get the brush based on the theme</span>
<span class="n">SKColor</span> <span class="n">gradientStart</span> <span class="p">=</span> <span class="p">((</span><span class="n">Color</span><span class="p">)</span><span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"BackgroundGradientStartColor"</span><span class="p">]).</span><span class="nf">ToSKColor</span><span class="p">();</span>
<span class="n">SKColor</span> <span class="n">gradientMid</span> <span class="p">=</span> <span class="p">((</span><span class="n">Color</span><span class="p">)</span><span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"BackgroundGradientMidColor"</span><span class="p">]).</span><span class="nf">ToSKColor</span><span class="p">();</span>
<span class="n">SKColor</span> <span class="n">gradientEnd</span> <span class="p">=</span> <span class="p">((</span><span class="n">Color</span><span class="p">)</span><span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"BackgroundGradientEndColor"</span><span class="p">]).</span><span class="nf">ToSKColor</span><span class="p">();</span>
<span class="c1">// gradient background with 3 colors</span>
<span class="n">backgroundBrush</span><span class="p">.</span><span class="n">Shader</span> <span class="p">=</span> <span class="n">SKShader</span><span class="p">.</span><span class="nf">CreateRadialGradient</span> <span class="p">(</span>
<span class="k">new</span> <span class="nf">SKPoint</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">info</span><span class="p">.</span><span class="n">Height</span> <span class="p">*</span> <span class="p">.</span><span class="m">8f</span><span class="p">),</span>
<span class="n">info</span><span class="p">.</span><span class="n">Height</span><span class="p">*.</span><span class="m">8f</span><span class="p">,</span>
<span class="k">new</span> <span class="n">SKColor</span><span class="p">[]</span> <span class="p">{</span> <span class="n">gradientStart</span><span class="p">,</span> <span class="n">gradientMid</span><span class="p">,</span> <span class="n">gradientEnd</span> <span class="p">},</span>
<span class="k">new</span> <span class="kt">float</span><span class="p">[]</span> <span class="p">{</span> <span class="m">0</span><span class="p">,</span> <span class="p">.</span><span class="m">5f</span><span class="p">,</span> <span class="m">1</span> <span class="p">},</span>
<span class="n">SKShaderTileMode</span><span class="p">.</span><span class="n">Clamp</span><span class="p">);</span>
<span class="n">SKRect</span> <span class="n">backgroundBounds</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SKRect</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">info</span><span class="p">.</span><span class="n">Width</span><span class="p">,</span> <span class="n">info</span><span class="p">.</span><span class="n">Height</span><span class="p">);</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">DrawRect</span><span class="p">(</span><span class="n">backgroundBounds</span><span class="p">,</span> <span class="n">backgroundBrush</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<blockquote>
<p>The documentation and samples for SkiaSharp are excellent, don’t be scared to check them out. For example, here are the docs for <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/effects/shaders/circular-gradients">Creating Radial Gradients</a></p>
</blockquote>
<h3 id="temperature-slider">Temperature Slider</h3>
<p>Okay, this is the fun bit, that temperature slider. Notice how it has a slider thumb and above it, it dynamically eats into (or clips) the slider background. There isn’t a control like this in Xamarin.Forms (or anywhere for that matter), so it’s time to write a control in SkiaSharp, named appropriately <code class="highlighter-rouge">GaugeControl</code>.</p>
<div class="myvideo">
<video style="display:block;" autoplay="" controls="" loop="loop">
<source src="assets/videos/dayvsnight-slider.mp4" type="video/mp4" />
</video>
</div>
<p>The background of the slider is really just a <code class="highlighter-rouge">RoundedRect</code> with a gradient fill. No big deal. However, the trick is to have a bit “clipped” out where the slider thumb is. Well, it turns out that with SkiaSharp you can provide a path to clip out of the drawing, which is what we do above the slider thumb.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="n">SKPath</span> <span class="n">clipPath</span> <span class="p">=</span> <span class="n">SKPath</span><span class="p">.</span><span class="nf">ParseSvgPathData</span><span class="p">(</span><span class="s">"M.021 28.481a25.933 25.933 0 0 0 8.824-2.112 27.72 27.72 0 0 0 7.391-5.581l19.08-17.045S39.879.5 44.516.5s9.352 3.243 9.352 3.243l20.74 18.628a30.266 30.266 0 0 0 4.525 3.545c3.318 2.263 11.011 2.564 11.011 2.564z"</span><span class="p">);</span>
<span class="p">...</span>
<span class="c1">// get density</span>
<span class="kt">float</span> <span class="n">density</span> <span class="p">=</span> <span class="n">info</span><span class="p">.</span><span class="n">Size</span><span class="p">.</span><span class="n">Width</span> <span class="p">/</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="k">this</span><span class="p">.</span><span class="n">Width</span><span class="p">;</span>
<span class="c1">// get the path of the clip region</span>
<span class="kt">var</span> <span class="n">scaledClipPath</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SKPath</span><span class="p">(</span><span class="n">clipPath</span><span class="p">);</span>
<span class="n">scaledClipPath</span><span class="p">.</span><span class="nf">Transform</span><span class="p">(</span><span class="n">SKMatrix</span><span class="p">.</span><span class="nf">MakeScale</span><span class="p">(</span><span class="n">density</span><span class="p">,</span> <span class="n">density</span><span class="p">));</span>
<span class="n">scaledClipPath</span><span class="p">.</span><span class="nf">GetTightBounds</span><span class="p">(</span><span class="k">out</span> <span class="kt">var</span> <span class="n">tightBounds</span><span class="p">);</span>
<span class="c1">// apply translations to position the clip path</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">Translate</span><span class="p">(</span><span class="n">translateX</span><span class="p">,</span> <span class="n">translateY</span><span class="p">);</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">ClipPath</span><span class="p">(</span><span class="n">scaledClipPath</span><span class="p">,</span> <span class="n">SKClipOperation</span><span class="p">.</span><span class="n">Difference</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">Translate</span><span class="p">(-</span><span class="n">translateX</span><span class="p">,</span> <span class="p">-</span><span class="n">translateY</span><span class="p">);</span>
<span class="p">...</span></code></pre></figure>
<blockquote>
<p>One thing to note in the code above is that when you are dealing with SkiaSharp you are dealing with pixels, so if you are used to working with Xamarin.Forms units you need to actually work out the scaling of your device.</p>
</blockquote>
<p>The key line in there is the <code class="highlighter-rouge">ClipPath</code> method call which uses the path and a <code class="highlighter-rouge">SKClipOperation.Difference</code> which basically means, only render whatever you output where it falls outside of the clip path. One thing to consider is that every drawing operation you perform after you call <code class="highlighter-rouge">ClipPath</code> will have the clipping applied. But one little trick you can do is restore you canvas to a point before the clipping by using <code class="highlighter-rouge">SKAutoCanvasRestore</code>.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="c1">// do some drawing stuff here - it won't be clipped</span>
<span class="p">...</span>
<span class="k">using</span> <span class="p">(</span><span class="k">new</span> <span class="nf">SKAutoCanvasRestore</span><span class="p">(</span><span class="n">canvas</span><span class="p">))</span>
<span class="p">{</span>
<span class="p">...</span>
<span class="n">canvas</span><span class="p">.</span><span class="nf">ClipPath</span><span class="p">(</span><span class="n">scaledClipPath</span><span class="p">,</span> <span class="n">SKClipOperation</span><span class="p">.</span><span class="n">Difference</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="p">...</span>
<span class="c1">// everything here will have the clipping applied</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="c1">// after the USING the canvas is restored to it's previous state so no clipping will happen</span>
<span class="p">...</span></code></pre></figure>
<p><em>Handling User Input</em></p>
<p>It’s one thing to draw the control, it’s quite another to make it interact with the user. In our case, we want to detect when the user drags on the control so that we can update the position of the thumb slider and the clipping path. To do this we use a Xamarin.Forms effect which reports touch operations.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Grid></span>
<span class="nt"><skia:SKCanvasView</span> <span class="na">x:Name=</span><span class="s">"TempGaugeCanvas"</span> <span class="na">PaintSurface=</span><span class="s">"TempGaugeCanvas_PaintSurface"</span> <span class="nt">/></span>
<span class="nt"><Grid.Effects></span>
<span class="nt"><local:TouchEffect</span> <span class="na">Capture=</span><span class="s">"True"</span> <span class="na">TouchAction=</span><span class="s">"TouchEffect_TouchAction"</span> <span class="nt">/></span>
<span class="nt"></Grid.Effects></span>
<span class="nt"></Grid></span></code></pre></figure>
<p>So when we get a TouchAction we update the percent of the slider, which in turn calls <code class="highlighter-rouge">InvalidateSurface</code> causing a redraw of the SkiaSharp canvas.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">void</span> <span class="nf">TouchEffect_TouchAction</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">TouchEffect</span><span class="p">.</span><span class="n">TouchActionEventArgs</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Percent</span> <span class="p">=</span> <span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">Location</span><span class="p">.</span><span class="n">X</span> <span class="p">/</span> <span class="n">TempGaugeCanvas</span><span class="p">.</span><span class="n">Width</span><span class="p">)</span> <span class="p">*</span> <span class="m">100</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">double</span> <span class="n">Percent</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">percent</span><span class="p">;</span>
<span class="k">set</span>
<span class="p">{</span>
<span class="n">percent</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
<span class="n">TempGaugeCanvas</span><span class="p">.</span><span class="nf">InvalidateSurface</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<blockquote>
<p>The <code class="highlighter-rouge">TouchEffect</code> I lifted from the awesome SkiaSharp samples. For more about the TouchEffect check out <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/finger-paint">Finger Painting in SkiaSharp</a> in the Docs.</p>
</blockquote>
<h3 id="switching-themes">Switching Themes</h3>
<p>Another interesting bit about this design is the dark and light themes. For this UI Challenge we make it so when you click on the profile image it toggles between dark and light. In a real application you would probably tie this to user preferences, or even cooler, to the ambient light sensor of the device so it automatically switches based on lighting conditions. For us thought, it’s just tied to the profile image, like so:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">void</span> <span class="nf">ProfileImage_Tapped</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">themeName</span> <span class="p">==</span> <span class="s">"light"</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">themeName</span> <span class="p">=</span> <span class="s">"dark"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">themeName</span> <span class="p">=</span> <span class="s">"light"</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">ThemeHelper</span><span class="p">.</span><span class="nf">ChangeTheme</span><span class="p">(</span><span class="n">themeName</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>The result is quite nice though, as shown in this video:</p>
<div class="myvideo">
<video style="display:block; height:600px;" autoplay="" controls="" loop="loop">
<source src="assets/videos/dayvsnight-themechange.mp4" type="video/mp4" />
</video>
</div>
<p><em>Multiple Theme Files</em></p>
<p>I have multiple themes (technically <code class="highlighter-rouge">ResourceDictionary</code>) that have different values for resources. For example:</p>
<p><strong>LightTheme.xaml</strong></p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><ResourceDictionary</span> <span class="na">xmlns=</span><span class="s">"http://xamarin.com/schemas/2014/forms"</span>
<span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
<span class="na">x:Class=</span><span class="s">"DayVsNight.Themes.LightTheme"</span><span class="nt">></span>
<span class="c"><!-- Background Image --></span>
<span class="nt"><x:String</span> <span class="na">x:Key=</span><span class="s">"MountainImage"</span><span class="nt">></span>mountain_light<span class="nt"></x:String></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"HeaderColor"</span><span class="nt">></span>#213654<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"ArrowColor"</span><span class="nt">></span>#768ea0<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BorderColor"</span><span class="nt">></span>#d2d7dd<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"TagColor"</span><span class="nt">></span>#ffa318<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"TabSubTextColor"</span><span class="nt">></span>#96a7dd<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"SubTextColor"</span><span class="nt">></span>#95a8b6<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"TempLabelColor"</span><span class="nt">></span>#ffffff<span class="nt"></Color></span>
<span class="c"><!--BackgroundGradients--></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BackgroundGradientStartColor"</span><span class="nt">></span>#FFF1EA<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BackgroundGradientMidColor"</span><span class="nt">></span>#D6D7E3<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BackgroundGradientEndColor"</span><span class="nt">></span>#F0F6FF<span class="nt"></Color></span>
<span class="c"><!--Gauge Gradients--></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"GaugeGradientStartColor"</span><span class="nt">></span>#99C2FF<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"GaugeGradientEndColor"</span><span class="nt">></span>#FB7D80<span class="nt"></Color></span>
<span class="nt"></ResourceDictionary></span></code></pre></figure>
<p><strong>DarkTheme.xaml</strong></p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><ResourceDictionary</span> <span class="na">xmlns=</span><span class="s">"http://xamarin.com/schemas/2014/forms"</span>
<span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
<span class="na">x:Class=</span><span class="s">"DayVsNight.Themes.DarkTheme"</span><span class="nt">></span>
<span class="c"><!-- Background Image --></span>
<span class="nt"><x:String</span> <span class="na">x:Key=</span><span class="s">"MountainImage"</span><span class="nt">></span>mountain_dark<span class="nt"></x:String></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"HeaderColor"</span><span class="nt">></span>#FFFFFF<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"ArrowColor"</span><span class="nt">></span>#768ea0<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BorderColor"</span><span class="nt">></span>#d2d7dd<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"TagColor"</span><span class="nt">></span>#ffa318<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"TabSubTextColor"</span><span class="nt">></span>#FFFFFF<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"SubTextColor"</span><span class="nt">></span>#FFFFFF<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"TempLabelColor"</span><span class="nt">></span>#ffffff<span class="nt"></Color></span>
<span class="c"><!--BackgroundGradients--></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BackgroundGradientStartColor"</span><span class="nt">></span>#E8D6CB<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BackgroundGradientMidColor"</span><span class="nt">></span>#6683A9<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BackgroundGradientEndColor"</span><span class="nt">></span>#192E4A<span class="nt"></Color></span>
<span class="c"><!--Gauge Gradients--></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"GaugeGradientStartColor"</span><span class="nt">></span>#7A89B1<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"GaugeGradientEndColor"</span><span class="nt">></span>#FB7D80<span class="nt"></Color></span>
<span class="nt"></ResourceDictionary></span></code></pre></figure>
<blockquote>
<p>For this challenge we just have a dark and light theme, but of course you could have as many themes as you like. Or even allow the user to create their own.</p>
</blockquote>
<p><em>Theme Switcher</em></p>
<p>To go along with the themes we have a helper to switch between them. This essentially just loads resources from a resource dictionary into the application resources and then fires a message through the <code class="highlighter-rouge">MessagingCenter</code> so that other parts of the application can know when themes are switched.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">ThemeHelper</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="kt">string</span> <span class="n">CurrentTheme</span><span class="p">;</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">ChangeTheme</span><span class="p">(</span><span class="kt">string</span> <span class="n">theme</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// don't change to the same theme</span>
<span class="k">if</span> <span class="p">(</span><span class="n">theme</span> <span class="p">==</span> <span class="n">CurrentTheme</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="n">ResourceDictionary</span> <span class="n">applicationResourceDictionary</span> <span class="p">=</span> <span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">;</span>
<span class="n">ResourceDictionary</span> <span class="n">newTheme</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">theme</span><span class="p">.</span><span class="nf">ToLowerInvariant</span><span class="p">())</span>
<span class="p">{</span>
<span class="k">case</span> <span class="s">"light"</span><span class="p">:</span>
<span class="n">newTheme</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">LightTheme</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="s">"dark"</span><span class="p">:</span>
<span class="n">newTheme</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DarkTheme</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">merged</span> <span class="k">in</span> <span class="n">newTheme</span><span class="p">.</span><span class="n">MergedDictionaries</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">applicationResourceDictionary</span><span class="p">.</span><span class="n">MergedDictionaries</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">merged</span><span class="p">);</span>
<span class="p">}</span>
<span class="nf">ManuallyCopyThemes</span><span class="p">(</span><span class="n">newTheme</span><span class="p">,</span> <span class="n">applicationResourceDictionary</span><span class="p">);</span>
<span class="n">CurrentTheme</span> <span class="p">=</span> <span class="n">theme</span><span class="p">;</span>
<span class="n">MessagingCenter</span><span class="p">.</span><span class="n">Send</span><span class="p"><</span><span class="n">ThemeMessage</span><span class="p">>(</span><span class="k">new</span> <span class="nf">ThemeMessage</span><span class="p">(),</span> <span class="n">ThemeMessage</span><span class="p">.</span><span class="n">ThemeChanged</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">ManuallyCopyThemes</span><span class="p">(</span><span class="n">ResourceDictionary</span> <span class="n">fromResource</span><span class="p">,</span> <span class="n">ResourceDictionary</span> <span class="n">toResource</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">fromResource</span><span class="p">.</span><span class="n">Keys</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">toResource</span><span class="p">[</span><span class="n">item</span><span class="p">]</span> <span class="p">=</span> <span class="n">fromResource</span><span class="p">[</span><span class="n">item</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><em>Using Dynamic Resources</em></p>
<p>In order for Views to respond to changes in the Application Resources the key is to use <code class="highlighter-rouge">DynamicResource</code> instead of <code class="highlighter-rouge">StaticResource</code>.</p>
<blockquote>
<p><code class="highlighter-rouge">DynamicResource</code> will automatically respond if the resource value changes, <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/styles/xaml/dynamic">check the documentation here</a></p>
</blockquote>
<p>As an example, if you look at the styles setup in the <code class="highlighter-rouge">App.xaml</code>, you’ll see that I have used <code class="highlighter-rouge">DynamicResource</code> for the <code class="highlighter-rouge">TextColor</code> so that it’ll update when the <code class="highlighter-rouge">ThemeManager</code> changes the Color values, no additional code required.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="c"><!-- Styles --></span>
<span class="nt"><Style</span> <span class="na">x:Key=</span><span class="s">"Header"</span> <span class="na">TargetType=</span><span class="s">"Label"</span><span class="nt">></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"FontFamily"</span> <span class="na">Value=</span><span class="s">"{StaticResource TextBold}"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"FontSize"</span> <span class="na">Value=</span><span class="s">"30"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"TextColor"</span> <span class="na">Value=</span><span class="s">"{DynamicResource HeaderColor}"</span> <span class="nt">/></span>
<span class="nt"></Style></span></code></pre></figure>
<p>And to make the mountain image at the bottom change it is dynamically linked to an image name from the theme.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Image</span>
<span class="na">Grid.RowSpan=</span><span class="s">"2"</span>
<span class="na">Aspect=</span><span class="s">"AspectFill"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Fill"</span>
<span class="na">Source=</span><span class="s">"{DynamicResource MountainImage}"</span>
<span class="na">VerticalOptions=</span><span class="s">"End"</span> <span class="nt">/></span></code></pre></figure>
<p><em>Sending Theme Message</em></p>
<p>One aspect that will not automatically update when the theme changes is the SkiaSharp elements, so we need to trigger them to update somehow. As mentioned previously, we use the <code class="highlighter-rouge">MessagingCenter</code> (although any notification mechanism would work).</p>
<p>In the <code class="highlighter-rouge">MainPage</code> we register for notifications in the OnAppearing. When it fires (from the <code class="highlighter-rouge">ThemeHelper</code>), we call <code class="highlighter-rouge">UpdateTheme</code>, which effectively just calls <code class="highlighter-rouge">InvalidateSurface</code> which asks SkiaSharp to redraw the background.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnAppearing</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">base</span><span class="p">.</span><span class="nf">OnAppearing</span><span class="p">();</span>
<span class="n">MessagingCenter</span><span class="p">.</span><span class="n">Subscribe</span><span class="p"><</span><span class="n">ThemeMessage</span><span class="p">>(</span><span class="k">this</span><span class="p">,</span> <span class="n">ThemeMessage</span><span class="p">.</span><span class="n">ThemeChanged</span><span class="p">,</span> <span class="p">(</span><span class="n">tm</span><span class="p">)</span> <span class="p">=></span> <span class="nf">UpdateTheme</span><span class="p">(</span><span class="n">tm</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">UpdateTheme</span><span class="p">(</span><span class="n">ThemeMessage</span> <span class="n">tm</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">BackgroundGradient</span><span class="p">.</span><span class="nf">InvalidateSurface</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnDisappearing</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">base</span><span class="p">.</span><span class="nf">OnDisappearing</span><span class="p">();</span>
<span class="n">MessagingCenter</span><span class="p">.</span><span class="n">Unsubscribe</span><span class="p"><</span><span class="n">ThemeMessage</span><span class="p">>(</span><span class="k">this</span><span class="p">,</span> <span class="n">ThemeMessage</span><span class="p">.</span><span class="n">ThemeChanged</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<blockquote>
<p>Always remember to Unsubscribe from the <code class="highlighter-rouge">MessagingCenter</code></p>
</blockquote>
<p>Finally, in the Background SkiaSharp paint code we read the new colour values from the application dictionary that are used to draw the Gradient.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"> <span class="c1">// get the brush based on the theme</span>
<span class="n">SKColor</span> <span class="n">gradientStart</span> <span class="p">=</span> <span class="p">((</span><span class="n">Color</span><span class="p">)</span><span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"BackgroundGradientStartColor"</span><span class="p">]).</span><span class="nf">ToSKColor</span><span class="p">();</span>
<span class="n">SKColor</span> <span class="n">gradientMid</span> <span class="p">=</span> <span class="p">((</span><span class="n">Color</span><span class="p">)</span><span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"BackgroundGradientMidColor"</span><span class="p">]).</span><span class="nf">ToSKColor</span><span class="p">();</span>
<span class="n">SKColor</span> <span class="n">gradientEnd</span> <span class="p">=</span> <span class="p">((</span><span class="n">Color</span><span class="p">)</span><span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"BackgroundGradientEndColor"</span><span class="p">]).</span><span class="nf">ToSKColor</span><span class="p">();</span></code></pre></figure>
<p>The same sort of logic also applies to the background gradients in the <code class="highlighter-rouge">GaugeView</code>.</p>
<h3 id="security-zones">Security Zones</h3>
<p>The two sections right down the bottom of the page are simply horizontal scrolling <code class="highlighter-rouge">StackLayout</code> with <code class="highlighter-rouge">Grids</code> in them and using the <code class="highlighter-rouge">PancakeView</code> to give them rounded corners.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><ScrollView</span> <span class="na">HorizontalScrollBarVisibility=</span><span class="s">"Never"</span> <span class="na">Orientation=</span><span class="s">"Horizontal"</span><span class="nt">></span>
<span class="nt"><StackLayout</span> <span class="na">Orientation=</span><span class="s">"Horizontal"</span><span class="nt">></span>
<span class="c"><!-- room --></span>
<span class="nt"><pancake:PancakeView</span> <span class="na">CornerRadius=</span><span class="s">"10"</span><span class="nt">></span>
<span class="nt"><Grid</span>
<span class="na">BackgroundColor=</span><span class="s">"Red"</span>
<span class="na">HeightRequest=</span><span class="s">"100"</span>
<span class="na">WidthRequest=</span><span class="s">"150"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Aspect=</span><span class="s">"AspectFill"</span> <span class="na">Source=</span><span class="s">"Room1"</span> <span class="nt">/></span>
<span class="nt"><Frame</span>
<span class="na">Margin=</span><span class="s">"5"</span>
<span class="na">Padding=</span><span class="s">"10,5"</span>
<span class="na">BackgroundColor=</span><span class="s">"{StaticResource TagColor}"</span>
<span class="na">CornerRadius=</span><span class="s">"15"</span>
<span class="na">HasShadow=</span><span class="s">"False"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Start"</span>
<span class="na">VerticalOptions=</span><span class="s">"Start"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource TagLabel}"</span> <span class="na">Text=</span><span class="s">"Zone 1"</span> <span class="nt">/></span>
<span class="nt"></Frame></span>
<span class="nt"></Grid></span>
<span class="nt"></pancake:PancakeView></span>
<span class="nt"><pancake:PancakeView</span> <span class="na">CornerRadius=</span><span class="s">"10"</span><span class="nt">></span>
<span class="nt"><Grid</span>
<span class="na">BackgroundColor=</span><span class="s">"Red"</span>
<span class="na">HeightRequest=</span><span class="s">"100"</span>
<span class="na">WidthRequest=</span><span class="s">"150"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Aspect=</span><span class="s">"AspectFill"</span> <span class="na">Source=</span><span class="s">"Room2"</span> <span class="nt">/></span>
<span class="nt"><Frame</span>
<span class="na">Margin=</span><span class="s">"5"</span>
<span class="na">Padding=</span><span class="s">"10,5"</span>
<span class="na">BackgroundColor=</span><span class="s">"{StaticResource TagColor}"</span>
<span class="na">CornerRadius=</span><span class="s">"15"</span>
<span class="na">HasShadow=</span><span class="s">"False"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Start"</span>
<span class="na">VerticalOptions=</span><span class="s">"Start"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource TagLabel}"</span> <span class="na">Text=</span><span class="s">"Zone 2"</span> <span class="nt">/></span>
<span class="nt"></Frame></span>
<span class="nt"></Grid></span>
<span class="nt"></pancake:PancakeView></span>
<span class="nt"><pancake:PancakeView</span> <span class="na">CornerRadius=</span><span class="s">"10"</span><span class="nt">></span>
<span class="nt"><Grid</span>
<span class="na">BackgroundColor=</span><span class="s">"Red"</span>
<span class="na">HeightRequest=</span><span class="s">"100"</span>
<span class="na">WidthRequest=</span><span class="s">"150"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Aspect=</span><span class="s">"AspectFill"</span> <span class="na">Source=</span><span class="s">"Room3"</span> <span class="nt">/></span>
<span class="nt"><Frame</span>
<span class="na">Margin=</span><span class="s">"5"</span>
<span class="na">Padding=</span><span class="s">"10,5"</span>
<span class="na">BackgroundColor=</span><span class="s">"{StaticResource TagColor}"</span>
<span class="na">CornerRadius=</span><span class="s">"15"</span>
<span class="na">HasShadow=</span><span class="s">"False"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Start"</span>
<span class="na">VerticalOptions=</span><span class="s">"Start"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource TagLabel}"</span> <span class="na">Text=</span><span class="s">"Zone 3"</span> <span class="nt">/></span>
<span class="nt"></Frame></span>
<span class="nt"></Grid></span>
<span class="nt"></pancake:PancakeView></span>
<span class="nt"></StackLayout></span>
<span class="nt"></ScrollView></span></code></pre></figure>
<p>In a real solution, you might want to try using the new <code class="highlighter-rouge">CollectionView</code>, but for the sake of our exercise, the <code class="highlighter-rouge">ScrollView</code> will do.</p>
<h2 id="the-final-result">The Final Result</h2>
<p>I think we could call this Xamarin.Forms UI Challenge a success. It’s great to see how well SkiaSharp integrates with Xamarin.Forms to allow us to create crazy-cool custom controls. Also multiple themes/styles is not too hard to implement as well.</p>
<h3 id="ios-version">iOS Version</h3>
<div class="myvideo">
<video style="display:block; height:600px;" autoplay="" controls="" loop="loop">
<source src="assets/videos/dayvsnight-ios.mp4" type="video/mp4" />
</video>
</div>
<h3 id="android-version">Android Version</h3>
<div class="myvideo">
<video style="display:block; height:600px;" autoplay="" controls="" loop="loop">
<source src="assets/videos/dayvsnight-android.mp4" type="video/mp4" />
</video>
</div>
<h2 id="get-the-code">Get the code</h2>
<p>All the code is available open source on GitHub at <a href="https://github.com/kphillpotts/DayVsNight">https://github.com/kphillpotts/DayVsNight</a>.</p>
<h3 id="techniques-used">Techniques Used</h3>
<ul>
<li>ImageCirclePlugin</li>
<li>PancakeView</li>
<li>Custom Fonts</li>
<li>XAML Styles and Resources</li>
<li>Theme Switching</li>
<li>SkiaSharp controls</li>
<li>MessagingCenter notifications</li>
</ul>
<h2 id="watch-me-code-it">Watch me code it</h2>
<p>I actually did this UI Challenge live over Twitch, so if you want to watch hours of me coding this up then check out these recordings over at YouTube.</p>
<h4 id="part-1">Part 1</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/p6VTXVfJMoQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h4 id="part-2">Part 2</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/wl85fSpH4vE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h4 id="part-3">Part 3</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/dB8eXit39UA" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>If you want to catch me doing other live coding things follow me on Twitch. It’s a great platform where we can chat as we build software, ask questions, submit code). Follow me at <a href="https://www.twitch.tv/kymphillpotts">https://www.twitch.tv/kymphillpotts</a> and come join in the fun!</p>
<p><a href="https://twitch.tv/kymphillpotts" target="_blank"><img src="assets/images/twitch_banner.png" /></a></p>
<p>I hope you find these posts useful, feel free to leave me a comment below or reach out to me via <a href="https://twitter.com/kphillpotts">Twitter</a> with some feedback.</p>
<p>Until next time, Happy Coding</p>
<p>❤
Kym</p>Kym PhillpottsSometimes when developing Xamarin.Forms user interfaces you get to a point where you just need to handle the drawing yourself, and for that SkiaSharp is invaluable. And that’s one of the core pieces of this UI Challenge, using SkiaSharp to create controls that are unlike any other.Xamarin.Forms UI Challenges - Art Auction2019-05-30T09:50:31+10:002019-05-30T09:50:31+10:00https://kymphillpotts.com/xamarin-forms-ui-challenge-artauction<p>It’s time for another Xamarin.Forms UI Challenge! This time we are going to see how we can reproduce a design I found over at <a href="https://dribbble.com/shots/6177235-Valuable-Auction-Product-Page">Dribbble</a> by <a href="https://dribbble.com/alex_pesenka">Alex Pesenka</a>.</p>
<h2 id="lets-break-it-down">Let’s break it down</h2>
<p>The design has a reasonably complex layout involving animated popups and overlays, which makes it a good candidate for a Xamarin.Forms UI challenge.</p>
<p>Here is a little video of the animations in the design in motion:</p>
<div class="myvideo">
<video style="display:block; width:600px; height:auto;" autoplay="" controls="" loop="loop">
<source src="assets/videos/artauction.mp4" type="video/mp4" />
</video>
</div>
<p>It’s a little bit tricky to see what’s going on with all that zooming going on, but let’s break down the screens and see how we can lay it out in Xamarin.Forms.</p>
<h3 id="art-details-screen">Art Details Screen</h3>
<p>This is the main screen of the applciation.
<img src="assets/images/posts/artauction-screen1.png" />
This screen is actually pretty basic, the key elements are:</p>
<ul>
<li>Root layout is a <code class="highlighter-rouge">Grid</code> with 2 rows. So the footer section can always be at the bottom of the page</li>
<li>The main section is contained within a <code class="highlighter-rouge">ScrollView</code> so that if it gets longer than the page it will allow us to scroll to see the additional information.</li>
<li>Then it’s pretty much a <code class="highlighter-rouge">StackLayout</code> to lay the elements down the screen</li>
</ul>
<p>The intersting bits of this screen are:</p>
<ul>
<li>The top bit of text with the <code class="highlighter-rouge">Read More</code> section which was provided by a very kind community contribution by <a href="https://github.com/pictos">Pedro Jesus</a>. It’s a custom control with a couple of bindable properties which specify the <code class="highlighter-rouge">MaxLength</code> of the label and a <code class="highlighter-rouge">ReadMore</code> property which specifies the color of the readmore text. When the text is set it creates a <code class="highlighter-rouge">FormattedText</code> with a <code class="highlighter-rouge">Span</code> for both the text and the Read More section. You can see the <a href="https://github.com/kphillpotts/ArtAuction/blob/master/src/ArtAuction/ArtAuction/CustomControl/LabelLength.xaml.cs">code here</a>.</li>
<li>The Vincent Van Gogh image got the circular treatment thanks to James Montemagno’s <a href="https://github.com/jamesmontemagno/ImageCirclePlugin">ImageCirclePlugin</a></li>
<li>The Wikipedia icon is a SVG. In Android it’s an <code class="highlighter-rouge">xml</code> file that sits under the <code class="highlighter-rouge">drawable</code> file and in iOS it is provided by an AssetCatalog which creates a Vector from a PDF.</li>
</ul>
<h4 id="the-popup-bits">The Popup Bits</h4>
<p>The nicest part of this screen is the fading and popup that happens when you press the <code class="highlighter-rouge">MAKE A BID</code> button. The idea is to have the main part of the screen fade out, whilst a popup flies in from the bottom.</p>
<p>The fading is achieved by having a <code class="highlighter-rouge">BoxView</code> that covers the entire screen which we can adjust the opacity of.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><BoxView</span>
<span class="na">x:Name=</span><span class="s">"PageFader"</span>
<span class="na">Grid.Row=</span><span class="s">"0"</span>
<span class="na">Grid.RowSpan=</span><span class="s">"1"</span>
<span class="na">BackgroundColor=</span><span class="s">"{StaticResource PageFadeColor}"</span>
<span class="na">IsVisible=</span><span class="s">"false"</span>
<span class="na">Opacity=</span><span class="s">"0"</span><span class="nt">></span>
<span class="nt"><BoxView.GestureRecognizers></span>
<span class="nt"><TapGestureRecognizer</span> <span class="na">Tapped=</span><span class="s">"PageFader_Tapped"</span> <span class="nt">/></span>
<span class="nt"></BoxView.GestureRecognizers></span>
<span class="nt"></BoxView></span></code></pre></figure>
<p>When the user taps the button at the bottom of the screen the <code class="highlighter-rouge">PageFader</code> becomes visible via changing it’s opacity and visibility. At the same time it animates in the <code class="highlighter-rouge">BidPopup</code> from the bottom of the screen by animating it’s TranslationY property via the <code class="highlighter-rouge">TranslateTo</code> method.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">void</span> <span class="nf">BidPopupButton_Clicked</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">pageHeight</span> <span class="p">=</span> <span class="n">Height</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">firstSection</span> <span class="p">=</span> <span class="n">BidPopup</span><span class="p">.</span><span class="n">FirstSectionHeight</span><span class="p">;</span>
<span class="n">PageFader</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">PageFader</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="n">AnimationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="n">BidPopup</span><span class="p">.</span><span class="nf">TranslateTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">pageHeight</span><span class="p">-</span><span class="n">firstSection</span><span class="p">,</span> <span class="n">AnimationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>The <code class="highlighter-rouge">BidPopup</code> only animates in half way though, because it’s a two stage popup. There is a property called <code class="highlighter-rouge">FirstSectionHeight</code> on the <code class="highlighter-rouge">BidPopup</code> which tells us how high the first popup should be.</p>
<p>Next, if the the user taps on the <code class="highlighter-rouge">PageFader</code> (so the background outsie the popup) it will Translate the BidPopup down off the bottom of the screen and then fade the <code class="highlighter-rouge">PageFader</code> out.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">PageFader_Tapped</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">BidPopup</span><span class="p">.</span><span class="nf">TranslateTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">AnimationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="n">ArtistDetailsPopup</span><span class="p">.</span><span class="nf">TranslateTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">Height</span><span class="p">,</span> <span class="n">AnimationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="k">await</span> <span class="n">PageFader</span><span class="p">.</span><span class="nf">FadeTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">AnimationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="n">PageFader</span><span class="p">.</span><span class="n">IsVisible</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h3 id="the-bid-popup">The Bid Popup</h3>
<p>This is a <code class="highlighter-rouge">ContentView</code> that pops up from the bottom of the main screen. The tricky thing about this screen is that it happens in two “phases”. The first tap, brings in the gray section, then if the user taps again, it expands further to show the extended orange section.</p>
<p><img src="assets/images/posts/artauction-screen3.png" /></p>
<p>It looks kind of tricky, but the way the multi-stage popup works is to have a property on the <code class="highlighter-rouge">ContentView</code> which exposes the height of the <code class="highlighter-rouge">FirstSection</code>.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">public</span> <span class="kt">double</span> <span class="n">FirstSectionHeight</span>
<span class="p">{</span>
<span class="k">get</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">FirstSection</span><span class="p">.</span><span class="n">Height</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>And an event that is raised when the user taps on the first section, so that it knows to popup to it’s larger size.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">public</span> <span class="k">delegate</span> <span class="k">void</span> <span class="nf">ClickExpandDelegate</span><span class="p">();</span>
<span class="k">public</span> <span class="n">ClickExpandDelegate</span> <span class="n">OnExpandTapped</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">FirstSection_Tapped</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">OnExpandTapped</span><span class="p">?.</span><span class="nf">Invoke</span><span class="p">();</span>
<span class="p">}</span></code></pre></figure>
<p>On the MainPage it listens for this event to animate the <code class="highlighter-rouge">ContentView</code> in to it’s full height.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnAppearing</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">base</span><span class="p">.</span><span class="nf">OnAppearing</span><span class="p">();</span>
<span class="n">BidPopup</span><span class="p">.</span><span class="n">OnExpandTapped</span> <span class="p">+=</span> <span class="n">ExpandPopup</span><span class="p">;</span>
<span class="n">ArtistDetailsPopup</span><span class="p">.</span><span class="n">OnExpandTapped</span> <span class="p">+=</span> <span class="n">ExpandArtistDetails</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">ExpandPopup</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">height</span> <span class="p">=</span> <span class="n">BidPopup</span><span class="p">.</span><span class="n">Height</span><span class="p">;</span>
<span class="n">BidPopup</span><span class="p">.</span><span class="nf">TranslateTo</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">Height</span> <span class="p">-</span> <span class="n">height</span><span class="p">,</span> <span class="n">AnimationSpeed</span><span class="p">,</span> <span class="n">Easing</span><span class="p">.</span><span class="n">SinInOut</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>The rounded corners at the top of the page are achieved by the awesome <code class="highlighter-rouge">CornerRadius</code> properties of a <code class="highlighter-rouge">BoxView</code> which allows us to control the rounding of each corner.</p>
<p>The other part that is interesting is the gradient backgrounds on the buttons, which is achieved by using the magnificent <a href="https://github.com/sthewissen/Xamarin.Forms.PancakeView"><code class="highlighter-rouge">PancakeView</code></a> control by <a href="https://github.com/sthewissen">Steven Thewissen</a>.</p>
<h3 id="artist-details-popup">Artist Details Popup</h3>
<p>The layout of this screen is fairly simple as well. Technically, it’s a content view, which animates over the main page when the user taps on the artist name in the main page. Let’s have a look at it.
<img src="assets/images/posts/artauction-screen2.png" />
Most of the screen is just regular layouts using <code class="highlighter-rouge">Grid</code> and <code class="highlighter-rouge">StackLayout</code>. The cool bit is the staggered list which shows the artists works. This was a lovely implementation contributed by <a href="https://github.com/lachlanwgordon">Lachlan Gordon</a>. At it’s core it’s a <code class="highlighter-rouge">FlexLayout</code> with a BindableLayout of art works that sits within a <code class="highlighter-rouge">ScrollView</code>. Nice and simple, but super effective. (just the way code should be)</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><ScrollView</span>
<span class="na">Grid.Row=</span><span class="s">"9"</span>
<span class="na">Padding=</span><span class="s">"5"</span>
<span class="na">BackgroundColor=</span><span class="s">"{StaticResource LightBackgroundColor}"</span><span class="nt">></span>
<span class="nt"><StackLayout></span>
<span class="nt"><Label</span>
<span class="na">Margin=</span><span class="s">"5"</span>
<span class="na">FontAttributes=</span><span class="s">"Bold"</span>
<span class="na">Style=</span><span class="s">"{StaticResource BodyText}"</span>
<span class="na">Text=</span><span class="s">"Other Van Gogh Works"</span> <span class="nt">/></span>
<span class="nt"><FlexLayout</span>
<span class="na">BindableLayout.ItemsSource=</span><span class="s">"{Binding ArtWorks}"</span>
<span class="na">Direction=</span><span class="s">"Column"</span>
<span class="na">HeightRequest=</span><span class="s">"930"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span>
<span class="na">Wrap=</span><span class="s">"Wrap"</span><span class="nt">></span>
<span class="nt"><BindableLayout.ItemTemplate></span>
<span class="nt"><DataTemplate></span>
<span class="nt"><StackLayout</span> <span class="na">Padding=</span><span class="s">"5"</span><span class="nt">></span>
<span class="nt"><ImageButton</span>
<span class="na">Aspect=</span><span class="s">"AspectFill"</span>
<span class="na">CornerRadius=</span><span class="s">"10"</span>
<span class="na">HeightRequest=</span><span class="s">"{Binding Height}"</span>
<span class="na">Source=</span><span class="s">"{Binding ImagePath}"</span>
<span class="na">VerticalOptions=</span><span class="s">"Start"</span> <span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">Style=</span><span class="s">"{StaticResource BodyText}"</span> <span class="na">Text=</span><span class="s">"{Binding Name}"</span> <span class="nt">/></span>
<span class="nt"><Label</span>
<span class="na">Margin=</span><span class="s">"0,0,0,10"</span>
<span class="na">Style=</span><span class="s">"{StaticResource SubtitleText}"</span>
<span class="na">Text=</span><span class="s">"{Binding Path=Price, StringFormat='{0:C}'}"</span> <span class="nt">/></span>
<span class="nt"></StackLayout></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></BindableLayout.ItemTemplate></span>
<span class="nt"></FlexLayout></span>
<span class="nt"></StackLayout></span>
<span class="nt"></ScrollView></span></code></pre></figure>
<h3 id="styling">Styling</h3>
<p>Custom Fonts were used to give the all improtant visual style we wanted. Also using Application Styles were used to make it easier to get consistent colors. Here is the Styles used:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"> <span class="nt"><Application.Resources></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"BackgroundColor"</span><span class="nt">></span>#1F1E1E<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"LightBackgroundColor"</span><span class="nt">></span>#292828<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"ForegroundTextColor"</span><span class="nt">></span>White<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"SubtitleTextColor"</span><span class="nt">></span>#C5C4C4<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"AccentColor"</span><span class="nt">></span>#FF910A<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"LightAccentColor"</span><span class="nt">></span>#FFC57E<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"PageFadeColor"</span><span class="nt">></span>#AA000000<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"HighlightGradientStart"</span><span class="nt">></span>#ffca8a<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"HighlightGradientEnd"</span><span class="nt">></span>#ffba67<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"NonHighlightGradientStart"</span><span class="nt">></span>#ffad4a<span class="nt"></Color></span>
<span class="nt"><Color</span> <span class="na">x:Key=</span><span class="s">"NonHighlightGradientEnd"</span><span class="nt">></span>#ff9d24<span class="nt"></Color></span>
<span class="nt"><OnPlatform</span> <span class="na">x:Key=</span><span class="s">"TitleFontFamily"</span> <span class="na">x:TypeArguments=</span><span class="s">"x:String"</span><span class="nt">></span>
<span class="nt"><On</span> <span class="na">Platform=</span><span class="s">"iOS"</span> <span class="na">Value=</span><span class="s">"Old Standard TT"</span> <span class="nt">/></span>
<span class="nt"><On</span> <span class="na">Platform=</span><span class="s">"Android"</span> <span class="na">Value=</span><span class="s">"OldStandard-Bold.ttf#Old Standard TT"</span> <span class="nt">/></span>
<span class="nt"></OnPlatform></span>
<span class="nt"><OnPlatform</span> <span class="na">x:Key=</span><span class="s">"BodyFontFamily"</span> <span class="na">x:TypeArguments=</span><span class="s">"x:String"</span><span class="nt">></span>
<span class="nt"><On</span> <span class="na">Platform=</span><span class="s">"iOS"</span> <span class="na">Value=</span><span class="s">"Questrial"</span> <span class="nt">/></span>
<span class="nt"><On</span> <span class="na">Platform=</span><span class="s">"Android"</span> <span class="na">Value=</span><span class="s">"Questrial-Regular.ttf#Questrial"</span> <span class="nt">/></span>
<span class="nt"></OnPlatform></span>
<span class="nt"><Style</span> <span class="na">TargetType=</span><span class="s">"Grid"</span><span class="nt">></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"RowSpacing"</span> <span class="na">Value=</span><span class="s">"0"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"ColumnSpacing"</span> <span class="na">Value=</span><span class="s">"0"</span> <span class="nt">/></span>
<span class="nt"></Style></span>
<span class="nt"><Style</span> <span class="na">x:Key=</span><span class="s">"TitleText"</span> <span class="na">TargetType=</span><span class="s">"Label"</span><span class="nt">></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"TextColor"</span> <span class="na">Value=</span><span class="s">"{StaticResource ForegroundTextColor}"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"FontFamily"</span> <span class="na">Value=</span><span class="s">"{StaticResource TitleFontFamily}"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"FontSize"</span> <span class="na">Value=</span><span class="s">"30"</span> <span class="nt">/></span>
<span class="nt"></Style></span>
<span class="nt"><Style</span> <span class="na">x:Key=</span><span class="s">"SubtitleText"</span> <span class="na">TargetType=</span><span class="s">"Label"</span><span class="nt">></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"TextColor"</span> <span class="na">Value=</span><span class="s">"{StaticResource SubtitleTextColor}"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"FontFamily"</span> <span class="na">Value=</span><span class="s">"{StaticResource BodyFontFamily}"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"FontSize"</span> <span class="na">Value=</span><span class="s">"Small"</span> <span class="nt">/></span>
<span class="nt"></Style></span>
<span class="nt"><Style</span> <span class="na">x:Key=</span><span class="s">"BodyText"</span> <span class="na">TargetType=</span><span class="s">"Label"</span><span class="nt">></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"TextColor"</span> <span class="na">Value=</span><span class="s">"{StaticResource ForegroundTextColor}"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"FontFamily"</span> <span class="na">Value=</span><span class="s">"{StaticResource BodyFontFamily}"</span> <span class="nt">/></span>
<span class="nt"><Setter</span> <span class="na">Property=</span><span class="s">"FontSize"</span> <span class="na">Value=</span><span class="s">"Medium"</span> <span class="nt">/></span>
<span class="nt"></Style></span>
<span class="nt"><x:Double</span> <span class="na">x:Key=</span><span class="s">"LinkImageSize"</span><span class="nt">></span>50<span class="nt"></x:Double></span>
<span class="nt"><x:Double</span> <span class="na">x:Key=</span><span class="s">"LinkImageCornerSize"</span><span class="nt">></span>25<span class="nt"></x:Double></span>
<span class="nt"></Application.Resources></span></code></pre></figure>
<h2 id="the-final-result">The Final Result</h2>
<p>I think we could call this Xamarin.Forms UI Challenge a success. It does highlight that you can achieve some pretty amazing things without having to resort to custom renderers. Layouts, Styles and Animations for the win!</p>
<h3 id="ios-version">iOS Version</h3>
<div class="myvideo">
<video style="display:block; height:600px;" autoplay="" controls="" loop="loop">
<source src="assets/videos/artauction-ios.mp4" type="video/mp4" />
</video>
</div>
<h3 id="android-version">Android Version</h3>
<div class="myvideo">
<video style="display:block; height:600px;" autoplay="" controls="" loop="loop">
<source src="assets/videos/artauction-android.mp4" type="video/mp4" />
</video>
</div>
<h2 id="get-the-code">Get the code</h2>
<p>All the code is available open source on GitHub at <a href="https://github.com/kphillpotts/ArtAuction/">https://github.com/kphillpotts/ArtAuction/</a>.</p>
<h3 id="techniques-used">Techniques Used</h3>
<ul>
<li>ImageCirclePlugin</li>
<li>Xamarin.Forms Animations</li>
<li>BindableProperties</li>
<li>PancakeView</li>
<li>Custom Fonts</li>
<li>XAML Styles and Resources</li>
<li>FlexLayout</li>
</ul>
<h2 id="watch-me-code-it">Watch me code it</h2>
<p>I actually did this UI Challenge live over Twitch, so if you want to watch hours of me coding this up then check out these recordings.</p>
<h4 id="part-1---missing-in-action">Part 1 - Missing in action</h4>
<p>(NOTE: Because I’m an idiot and didn’t back up my files from Twitch, I lost the first session).</p>
<h4 id="part-2">Part 2</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/dmGxxz6kbNk" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h4 id="part-3">Part 3</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/S7eygoR7ESs" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>Also, if you want to catch me doing other live coding things, with Xamarin and Azure follow me on Twitch. It’s a great platform where we can chat as we build software, ask questions, submit code). Follow me at <a href="https://www.twitch.tv/kymphillpotts">https://www.twitch.tv/kymphillpotts</a> and come join in the fun!</p>
<h3 id="techniques-used-1">Techniques Used</h3>
<ul>
<li>ImageCirclePlugin</li>
<li>Xamarin.Forms Animations</li>
<li>BindableProperties</li>
<li>PancakeView</li>
<li>Custom Fonts</li>
<li>XAML Styles and Resources</li>
<li>FlexLayout</li>
</ul>
<p>I hope these posts are useful for you, feel free to leave me a comment below or reach out to me via <a href="https://twitter.com/kphillpotts">Twitter</a>.</p>
<p>Until next time, Happy Coding</p>
<p>❤
Kym</p>Kym PhillpottsIt’s time for another Xamarin.Forms UI Challenge! This time we are going to see how we can reproduce a design I found over at Dribbble by Alex Pesenka. Let’s break it down The design has a reasonably complex layout involving animated popups and overlays, which makes it a good candidate for a Xamarin.Forms UI challenge.