How to make a Floating Action Button in Xamarin Forms - xaml

I'm building a Xamarin CrossPlatform App!
I wanted to add a floating action button at the bottom right corner of the app page just like this
Here is my XAML code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Last_MSPL.Views.HomePage">
<ListView x:Name="Demolist" ItemSelected="OnItemSelected" BackgroundColor="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem x:Name="OnMore" Clicked="OnMore_Clicked" CommandParameter="{Binding .}"
Text="More" />
<MenuItem x:Name="OnDelete" Clicked="OnDelete_Clicked" CommandParameter="{Binding .}"
Text="Delete" IsDestructive="True" />
</ViewCell.ContextActions>
<StackLayout>
<StackLayout Padding="15,0">
<Label Text="{Binding employee_name}" FontAttributes="bold" x:Name="en"/>
<Label Text="{Binding employee_age}"/>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
How can I do this using XAML? Help me through this, Thanks!

You can use an ImageButton (Xamarin.Forms v3.4+)
Create your image with a transparent background in your favorite editor and then assign it a location on the Page.
Example using an AbsoluteLayout, just place your "FAB" as the last element so that its Z-order is highest and it will "float" above the rest of the content.
<AbsoluteLayout>
~~~~
<ImageButton Source="editFAB.png"
BackgroundColor="Transparent"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds=".95,.95,80,80"
Clicked="Handle_Clicked" />
</AbsoluteLayout>
Output:

**
If you want a solution thats simple and without downloading libraries or anything try this:
**
In your xaml you can use a normal button with rounded corners. Just make sure both width and height are the same. To have it float use a absolute layout and put the button on the bottom. I pasted mine and its style entry from my app.xml resource dictionary.
(Ive used both the james montenago packages and the suave controls. The first doesn't work on iOS and the latter doesn't show images on Android. This solution works on both iOS and Android.)
XAML:
<AbsoluteLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<!-- other content goes here -->
<Button x:Name="yourname" Image="baseline_menu_white_24"
Clicked="OnFabMenuClick" IsVisible="True"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize"
Style="{StaticResource FABPrimary}" />
</AbsoluteLayout>
Resource dictionary entry:
<Color x:Key="DarkButtonBackground">#921813</Color>
<Style x:Key="FABPrimary" TargetType="Button">
<Setter Property="CornerRadius" Value="100"/>
<Setter Property="BackgroundColor" Value="{StaticResource DarkButtonBackground}"/>
<Setter Property="HeightRequest" Value="55"/>
<Setter Property="WidthRequest" Value="55"/>
<Setter Property="HorizontalOptions" Value="CenterAndExpand"/>
<Setter Property="VerticalOptions" Value="CenterAndExpand"/>
<Setter Property="Padding" Value="15"/>
<Setter Property="Margin" Value="0,0,0,15"/>
</Style>
You can ignore the resource dictionary entry if you want and instead put the properties directly in the button declaration in your xaml file.
I have found that on some iOS devices, the buttons do not display correctly if the radius is set to 100. This can be fixed to setting the CornerRadius to half of the width and height, or you can use OnPlatform like this:
<Setter Property="CornerRadius">
<OnPlatform iOS="25" Android="100"/>
</Setter>
(When height and width are both 50.)

It's pretty easy to add a floating button in xamarin forms. Just add a Grid with one row having Height="*" and inside that, add a ScrollView and a Button, both in Grid.Row="0". Inside your ScrollView, put your design form. For button to be round in shape, put some BorderRadius and HeightRequest as well as WidthRequest should be double of that BorderRadius. Also, to show it at bottom right corner, put Margin="0,0,20,22". To show an Icon inside that button, put FontAwesome Icon as ImageSource of the button. FontAwesome Icons can be defined in a separate class (Please let me know if you need details on how to use FontAwesome Icons as well).
That's it, you are done.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollView Grid.Row="0">
</ScrollView>
<Button Grid.Row="0" BorderColor="#2b3c3c" BorderWidth="1" FontAttributes="Bold" BackgroundColor="#1968B3" BorderRadius="35" TextColor="White"
HorizontalOptions="End" VerticalOptions="End" WidthRequest="70" HeightRequest="70" Margin="0,0,20,22" Command="{Binding OpenSearchModalPopupCommand}">
<Button.ImageSource>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolidFontFamily}"
Glyph="{x:Static fonts:Icons.FAFilter}"
Size="20"
Color="White"/>
</Button.ImageSource>
</Button>
</Grid>

the quickest way:
Install this Nuget: https://github.com/jamesmontemagno/FloatingActionButton-for-Xamarin.Android
Position the Floating Action Button (FAB) Like this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="Tawsil.Views.DemoPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:fab="clr-namespace:Refractored.FabControl;assembly=Refractored.FabControl">
<AbsoluteLayout>
<Grid AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All">
<!-- your content here -->
</Grid>
<!-- the FAB -->
<fab:FloatingActionButtonView AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize" AbsoluteLayout.LayoutFlags="PositionProportional" ImageName="add.png" />
</AbsoluteLayout>
</ContentPage>
Don't forget to add the "add.png" icon to your resources

You can use for Android, Floating Action Button (https://developer.android.com/guide/topics/ui/floating-action-button) if you want to use native control, and a custom board for iOS.
Otherwise you can add to your Xamarin.Forms page a RelativeLayout container and specity constraints where you want. Something like this:
<ContentPage.Content>
<RelativeLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout >
<Label Text="YOUR CODE" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" />
</StackLayout>
<Button CornerRadius="25" Text="B"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Width, Factor=1, Constant=-60}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Height, Factor=1, Constant=-60}" />
</RelativeLayout>
</ContentPage.Content>
You can change Button with any other control and you can correct position just changing the value of "Constant=-60"

Using Floating action button in Xamarin Forms is easier than you might think, All you need is a little bit of AbsoluteLayout and an ImageButton that can respond to your clicks and voila! your FAB is ready
Add a custom ImageButton Control something like below:
public class ImageButton : Image
{
public static readonly BindableProperty CommandProperty =
BindableProperty.Create("Command", typeof(ICommand), typeof(ImageButton), null);
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create("CommandParameter", typeof(object), typeof(ImageButton), null);
public event EventHandler ItemTapped = (e, a) => { };
public ImageButton()
{
Initialize();
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
private ICommand TransitionCommand
{
get
{
return new Command(async () =>
{
AnchorX = 0.48;
AnchorY = 0.48;
await this.ScaleTo(0.8, 50, Easing.Linear);
await Task.Delay(100);
await this.ScaleTo(1, 50, Easing.Linear);
Command?.Execute(CommandParameter);
ItemTapped(this, EventArgs.Empty);
});
}
}
public void Initialize()
{
GestureRecognizers.Add(new TapGestureRecognizer
{
Command = TransitionCommand
});
}
}
Once you have that up and running in a layout whose parent-most is an AbsoluteLayout add the ImageButton like below:
<customControls:ImageButton Source="{Binding ImageSource}"
Command="{Binding AddCommand}" AbsoluteLayout.LayoutFlags="PositionProportional" AbsoluteLayout.LayoutBounds="1.0,1.0,-1,-1"
Margin="10" />
Where customControls is the namespace where you added your ImageButton custom control.
For better understanding check where I referenced it from which can be found here
Good luck
Revert in case you have any sort of query.

When implementing my Floating Action Button, I noticed that none of the answers here cover the shadow below the button. Here's my (hacky) solution to get a shadow below the FAB:
<Grid>
<Frame
Margin="3,8,-3,-8"
CornerRadius="35"
HasShadow="False"
HeightRequest="70"
VerticalOptions="Start"
WidthRequest="70">
<Frame.Background>
<RadialGradientBrush Center="0.5,0.5" Radius="0.5">
<GradientStop Offset="0" Color="Gray" />
<GradientStop Offset="0.5" Color="Gray" />
<GradientStop Offset="1.0" Color="#01000000" />
</RadialGradientBrush>
</Frame.Background>
</Frame>
<ImageButton
Padding="20"
Command="{Binding <YourCommand>}"
CornerRadius="35"
HeightRequest="70"
Source="<YourImage.png>"
VerticalOptions="Start" />
</Grid>
Notes:
The margin on the frame is the offset wrt to the ImageButton, so it appears below and to the right of the ImageButton.
The last color of the gradient is black, with a 1 value for alpha. Transparent did not have the desired effect on iOS, 01000000 worked nicely.
ImageButton has to be below Frame, because the xaml order determines the draw order.
Put this Grid in another Grid, to make it overlap other content.
This worked for Android and iOS, not for UWP.
I expect this is not a performant solution, because of an additional element to the view, and Xamarin's layout / drawing performance issues. A solution with custom renderers would likely perform better.
To make it nicer still, you can use a Visual State Manager. They left a few bugs in its Xamarin implementation though.

Related

Workaround for ScrollView not scrolling inside a StackLayout in Maui

I have a page that presents a list of items in a CollectionView within a ScrollView. I want the user to be able to add a new item to this list by hitting an "add" button or tapping an image at the top of the page. I first tried this using the hierarchy of views shown below. The code below is an abbreviated version of the real thing. I discovered that putting a ScrollView within a VerticalStackLayout breaks the scrolling in Maui! Here is the reported bug.
I tried deleting the VerticalStackLayout that precedes the ScrollView and the scrolling still doesn't work.
<ContentPage.Content>
<VerticalStackLayout>
<VerticalStackLayout HorizontalOptions="Center">
<Image Source="add.png">
<ImageGestureRecongnizers>
<TapGestureRecognizer... Code to add new item to MyCollection...]/>
</ImageGestureRecognizers>
</Image>
</VerticalStackLayout>
<ScrollView>
<CollectionView ItemsSource="{Binding MyCollection}">
<CollectionView.ItemTemplate>
<DataTemplate>
[Layout for displaying items in MyCollection...]
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ScrollView>
<VerticalStackLayout
</ContentPage.Content>
I'd greatly appreciate suggestions on a workaround to allow the viewing of the scrollable list and adding an item to the list by tapping an object (button or image) that's always visible on the page regardless of how the list is scrolled.
Actually for the Xaml, this will work...
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppTest01.MainPage">
<Grid RowDefinitions="30,*">
<CollectionView ItemsSource="{Binding MyCollection}"
Grid.Row="1">
<CollectionView.ItemTemplate>
<DataTemplate>
<VerticalStackLayout >
<Label Text="{Binding Name}" FontSize="Large" />
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Image Source="dotnet_bot.png" HeightRequest="100" WidthRequest="100"
Margin="0,0,50,20">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</Image.GestureRecognizers>
</Image>
</Grid>
Adjust your RowDefinition(30) for the image!
Sorry if my code is not neat, as I'm on mobile.
I ended up creating a "fancy" floating button on the bottom right of the page by:
Creating a one-row Grid
Putting both the CollectionView and Image in the one row
Defining the Image after the CollectionView so that the Image sits on top of the CollectionView
I also got rid of the ScrollView per Jason's suggestion.
<ContentPage.Content>
<Grid
<CollectionView ItemsSource="{Binding MyCollection}">
<CollectionView.ItemTemplate>
<DataTemplate>
[Layout for displaying items in MyCollection...]
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Image Source="add.png" HeightRequest="40" HorizontalOptions="End"
VerticalOptions="End" Margin="0,0,50,20">
<ImageGestureRecongnizers>
<TapGestureRecognizer... Code to add new item to MyCollection...]/>
</ImageGestureRecognizers>
</Image>
</Grid>
</ContentPage.Content>
If you want to allow the viewing of the scrollable list and add items to the list by tapping an image, you can just wrap them with a Grid.
Here's the code snippet below for your reference:
XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppTest01.MainPage">
<Grid>
<CollectionView ItemsSource="{Binding MyCollection}">
<CollectionView.ItemTemplate>
<DataTemplate>
<VerticalStackLayout >
<Label Text="{Binding Name}" FontSize="Large" />
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Image Source="dotnet_bot.png" HeightRequest="100" WidthRequest="100" HorizontalOptions="End" VerticalOptions="End" Margin="0,0,50,20">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</Image.GestureRecognizers>
</Image>
</Grid>
</ContentPage>
Code-behind:
public partial class MainPage : ContentPage
{
public ObservableCollection<MyModel> MyCollection { get; set; }
public MainPage()
    {
            InitializeComponent();
MyCollection = new ObservableCollection<MyModel>
{
new MyModel{ Name="1"},
new MyModel{ Name="2"},
};
BindingContext = this;
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++) {
MyCollection.Add(new MyModel { Name = i+"" });
}
}
}
Model:
public class MyModel
{
public string Name { get; set; }
}

Resume and modify some graphic elements present in another page

I have created a ControlTemplate in the app.xaml, defining the graphic part, only in the cs I would like to take some elements, to which I have given the name, to assign them the click event, but it signals me that they do not exist in the current context .
Someone who could kindly tell me how to please?
Someone, if possible, modify the source of the webview present in the main page, directly from app.xaml.cs
My code in the template is this:`
<Application.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="HeaderFooterTemplate"><ContentPresenter />
<StackLayout>
<BoxView HorizontalOptions="FillAndExpand" BackgroundColor="#DDDDDD" HeightRequest="2" Margin="0" />
<ScrollView Orientation="Horizontal">
<Grid RowSpacing="0">
<StackLayout Background="#c9ced6" HeightRequest="120" MinimumHeightRequest="120" Orientation="Horizontal" Grid.Row="0" Spacing="2">
<Grid WidthRequest="100" MinimumHeightRequest="120" BackgroundColor="#373B53" Padding="10" x:Name="Dashboard">
<Image Source="homeArancione" x:Name="imgDashboard" HorizontalOptions="Center" VerticalOptions="Center" WidthRequest="30" HeightRequest="30" Grid.Row="0" />
<Label Text="DASHBOARD" TextColor="#c9ced6" FontAttributes="Bold" HorizontalOptions="Center" Grid.Row="1" />
</Grid></Grid>
And in the reference page I recall it like this:
ControlTemplate="{StaticResource HeaderFooterTemplate}"
Graphically it works, but in the cs is I can't modify the texts to the labels or assign click events to the elements because it tells me that they don't exist in the current context
If you want to change the text or trigger the click event of the label in ControlTemplate, you could use the GestureRecognizers. The x:Name could not be found in the ControlTemplate directly.
<ControlTemplate x:Key="HeaderFooterTemplate">
<!--<ContentPresenter />-->
<StackLayout>
<BoxView HorizontalOptions="FillAndExpand" BackgroundColor="#DDDDDD" HeightRequest="2" Margin="0" />
<ScrollView Orientation="Horizontal">
<Grid RowSpacing="0">
<StackLayout Background="#c9ced6" HeightRequest="120" MinimumHeightRequest="120" Orientation="Horizontal" Grid.Row="0" Spacing="2">
<Grid WidthRequest="100" MinimumHeightRequest="120" BackgroundColor="#373B53" Padding="10" x:Name="Dashboard">
<Image Source="homeArancione" x:Name="imgDashboard" HorizontalOptions="Center" VerticalOptions="Center" WidthRequest="30" HeightRequest="30" Grid.Row="0" />
<Label Text="DASHBOARD" TextColor="#c9ced6" FontAttributes="Bold" HorizontalOptions="Center" Grid.Row="1">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
</Label.GestureRecognizers>
</Label>
</Grid>
</StackLayout>
</Grid>
</ScrollView>
</StackLayout>
</ControlTemplate>
Code behind: App.xaml.cs
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
Console.WriteLine("-----------");
var label = sender as Label;
label.Text = "Hello";
}
Update:
update the text of the label as soon as the page loads, without
having to click
Xamarin provide GetTemplateChild to get a named element from a template. The GetTemplateChild method should only be called after the OnApplyTemplate method has been called. If you want to call the OnApplyTemplate method, this template should be added for the contentpage.
You could refer to the thread i done before.
Xamarin SwipeView Open Left Item In ControlTemplate

Android9 Flexlayout bug?

I have a flexlayout inside of a frame, a grid and a stacklayout:
<CollectionView x:Name="collectionView"
Margin="20"
SelectionMode="Single"
SelectionChanged="OnSelectionChanged">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="10" />
</CollectionView.ItemsLayout>
<!-- Define the appearance of each item in the list -->
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Margin="0,10,0,0" Spacing="0">
<StackLayout Orientation="Horizontal" Margin="0">
<Label FontSize="Medium" FontAttributes="Bold" Text="{Binding Name, Mode=OneWay}" />
<Label Text="{Binding Bottle.ID, Mode=OneWay}" FontSize="Medium"
FontAttributes="Italic" VerticalTextAlignment="Center"
Margin="20,0,0,0"/>
</StackLayout>
<Frame CornerRadius="10" BorderColor="Black" IsVisible="{Binding IsConnected, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}" Padding="10">
<Label Text="not connected" FontSize="Small"/>
</Frame>
<Frame CornerRadius="10" BorderColor="Black" IsVisible="{Binding IsConnected, Mode=OneWay}" Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0">
<FlexLayout BindableLayout.ItemsSource="{Binding Bottle.Components, Mode=OneWay}"
Direction="Row" JustifyContent="Start" Wrap="Wrap"
AlignItems="Center" AlignContent="Start"
>
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label FontSize="Small" Text="{Binding DisplayString}" VerticalOptions="CenterAndExpand" Margin="0,0,10,0" Padding="0"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
<StackLayout Orientation="Horizontal" IsVisible="{Binding IsConnected, Mode=OneWay}" VerticalOptions="FillAndExpand">
<Label FontSize="Small" Text="in " IsVisible="{Binding Bottle.IsCarrier, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}"/>
<Label FontSize="Small" Text="{Binding Bottle.CarrierGas, Mode=OneWay}"/>
<Label FontSize="Small" Text=" 100%" IsVisible="{Binding Bottle.IsCarrier, Mode=OneWay}" />
</StackLayout>
</StackLayout>
<Label FontSize="Small" Text="(ISO)" FontAttributes="Italic" Grid.Column="1" IsVisible="False">
<Label.Triggers>
<MultiTrigger TargetType="Label">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Bottle.IsISOCertified, Mode=OneWay}" Value="True" />
<BindingCondition Binding="{Binding IsConnected, Mode=OneWay}" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="IsVisible" Value="True" />
</MultiTrigger>
</Label.Triggers>
</Label>
</Grid>
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
At a specific input data, the flexlayout is not displayed correctly (see first element in the list):
If I remove the frame, the texts are displayed correctly:
If I keep the frame and increase the margin or the padding of the flexlayout elements by one, the text is displayed correctly in 2 lines.
<DataTemplate>
<Label FontSize="Small" Text="{Binding DisplayString}" VerticalOptions="CenterAndExpand" Margin="0,0,11,0"/>
</DataTemplate>
Also if I increase the fontsize to medium, it is displayed correctly. Plus, if I do not restart the application but let visual studio automatically update the form, after decreasing the font size, the display is correct. However, if I restart the Android Emulator, the problem is there again.
With font size medium:
<DataTemplate>
<Label FontSize="Medium" Text="{Binding DisplayString}" VerticalOptions="CenterAndExpand" Margin="0,0,10,0"/>
</DataTemplate>
After resetting it to small, but not restarting the app:
I guess, increasing the Margin/Padding or increasing the font size or removing the frame would not work correctly on the long run, because there's always going to be some data/screen size combination which breaks the layout.
It seems to me, that the layout only fails in some corner cases. However, when the element widths (+margins) are over some limit, the flexlayout recognizes that the elements have to be displayed in 2 lines, and after that the layout is ok.
Is this a bug of xamarin/android, or is there a problem in my code? Could anyone suggest a workaround, solution, that works reliably?
Some extra information, not sure what is necessary:
Target Android Version: Android 9.0 (API Level 28 - Pie)
emulator: Pixel 2 Pie 9.0 - API28 1080x1920 420dpi
Edit: I also tested with 1020x1920, 1000x1920, 800x1920, 1200x1920 display size, all of them showed the same problem. So either the setting in Android Device Manager was not taken over (hw.lcd.width), or the problem is independent of the display size.
Edit2: changing the display size in Settings/Display affects the layout, and fixes it, if I set it to lower or larger. But I guess it is only changing the FontSize parameter of the labels.
While testing #ToolmakerSteve's solution, I found out, that the children bound.Width-s are higher if I remove the last item in the list (NO 2100ppm). Then I called measure for each children, and I saw, that the widths match those bound.Width-s when the last item was missing. So I ended up with this code:
//https://stackoverflow.com/a/70526712/9963147
class FlexLayoutImproveMeasure : FlexLayout
{
protected override void LayoutChildren(double x, double y, double width, double height)
{
base.LayoutChildren(x, y, width, height);
foreach (var child in Children)
{
Rectangle cb = child.Bounds;
Size s = child.Measure(double.PositiveInfinity, cb.Height).Request;
double newW = s.Width;
var childBounds2 = new Rectangle(cb.X, cb.Y, newW, cb.Height);
child.Layout(childBounds2);
}
}
}
I discovered that FlexLayout sometimes doesn't give each label quite enough room; the labels get truncated.
Here is a subclass of FlexLayout, that adds a small amount of width to each Label.
FlexLayoutImproveMeasure.cs:
using Xamarin.Forms;
namespace XFSOAnswers
{
class FlexLayoutImproveMeasure : FlexLayout
{
protected override void LayoutChildren(double x, double y, double width, double height)
{
base.LayoutChildren(x, y, width, height);
//var childBoundss = new List<Rectangle>();
foreach (var child in Children)
{
Rectangle cb = child.Bounds;
// Make each child slightly wider: labels were being truncated.
// Less than 2 didn't always work. Increase as needed, up to right margin.
const int extraW = 2;
double newX = cb.X;
// TBD whether part of the problem is label that isn't pixel-aligned.
// (So far, seems more reliable to increase extraW instead.)
//MAYBE double newX = Math.Floor(cb.X);
double newW = cb.Width + extraW;
// TBD whether it helps to ensure width extends to next pixel.
// (So far, seems more reliable to increase extraW instead.)
//MAYBE double newW = Math.Ceiling(cb.Width) + extraW;
//double ceilingRight = Math.Ceiling(cb.X + cb.Width);
//MAYBE double newW = ceilingRight - Math.Floor(cb.X) + extraW;
var childBounds2 = new Rectangle(newX, cb.Y, newW, cb.Height);
child.Layout(childBounds2);
//childBoundss.Add(child.Bounds);
//childBoundss.Add(cb);
}
}
}
}
Use this in place of FlexLayout. This is done by adding to a page an xmlns: declaration to access a namespace in your project. Here the namespace is XFSOAnswers. Change xmlns:local line to refer to whatever namespace you put the above class in.
XAML of a page:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XFSOAnswers"
x:Class="XFSOAnswers.FlexLayoutInItemTemplatePage">
<ContentPage.Content>
<StackLayout>
<!-- labels truncated at width in range 188-194 -->
<CollectionView ItemsSource="{Binding Items}"
WidthRequest="190" HorizontalOptions="Center"
Margin="20" >
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame CornerRadius="10" BorderColor="Black"
BackgroundColor="LightBlue" Padding="10">
<local:FlexLayoutImproveMeasure
Wrap="Wrap"
BindableLayout.ItemsSource="{Binding Items2}"
BackgroundColor="LightGray" >
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label FontSize="Small" Text="{Binding .}"
VerticalOptions="Start" HorizontalOptions="Start" Margin="0,0,10,0" Padding="0"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</local:FlexLayoutImproveMeasure>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
page's code behind has the bindings:
using System.Collections.Generic;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace XFSOAnswers
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FlexLayoutInItemTemplatePage : ContentPage
{
public FlexLayoutInItemTemplatePage()
{
InitializeComponent();
BindingContext = this;
}
public List<ListModel> Items { get; } = new List<ListModel> {
new ListModel(),
new ListModel(),
};
}
public class ListModel
{
public List<string> Items2 { get; } = new List<string> {
"aaa", "bbbbbbbbb", "cc", "dddd", "e",
};
}
}
Items2 strings chosen such that at WidthRequest=190, "dddd" barely fit on first line. Tests were done with and without the final "e", to see what FlexLayout did in both cases.
Original FlexLayout, truncating labels:
Using FlexLayoutImproveMeasure with extraW = 2:

How to bind the content of a control to a property of type VIEW in XAML

I would like to make a generic settings page for my App. I have a list of different types of settings, each one corresponding to the following interface:
public interface ISettings
{
string SectionLabel { get; }
View SectionView { get; }
}
Each type of settings represent a given aspect or section of the settings of the Application. The goal is to enable each type of settings to define it's own apprearance and the way to adjust it's own value.
I have a settings page with a list of all the settings :
public List settingsSections { get => App.AppSettings.SettingsSections; }
In the XAML settings page, I would like to display them like this :
<StackLayout BindableLayout.ItemsSource="{Binding settingsSections, Source={x:Reference this}}" Padding="0" BackgroundColor="White">
<BindableLayout.ItemTemplate>
<DataTemplate>
<xct:Expander>
<xct:Expander.Header>
<StackLayout Orientation="Horizontal">
<Image Source="{Helpers:ImageResource Images.filledDownArrow.png}" HeightRequest="{Binding Source={x:Reference heightRef}, Path=Height, Converter={Helpers:IntMultiplier Multiplier=0.99}}"
HorizontalOptions="Start"
VerticalOptions="Center">
<Image.Triggers>
<DataTrigger TargetType="Image"
Binding="{Binding Source={RelativeSource AncestorType={x:Type xct:Expander}}, Path=IsExpanded}"
Value="True">
<Setter Property="Source"
Value="{Helpers:ImageResource Images.filledRightArrow.png}" />
</DataTrigger>
</Image.Triggers>
</Image>
<Label x:Name="heightRef" Text="{Binding SectionLabel}"
FontAttributes="Bold"
FontSize="Medium" />
</StackLayout>
</xct:Expander.Header>
<xct:Expander.Content="{Binding SectionView}"/>
</xct:Expander>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
But of course, this line don't work:
<xct:Expander.Content="{Binding View}"/>
How can I achieve my goal ? Can someone help me ? I think I can't use custom control because I should change the type of the control for every line/section of the stacklayout according to the actual type of the setting which is referred to.
Option 1
You don't have to specify xct:Expander.Content at that point whatever you put there will be implicitly setting the Content property.
Replace
<xct:Expander.Content="{Binding SectionView}"/>
With
<ContentView Content="{Binding SectionView}"/>
Option 2
Specify it this way:
<DataTemplate>
<xct:Expander Content="{Binding SectionView}">
<xct:Expander.Header>
...

Xamarin.Forms BoxView Height match Width

I am trying to get the box view of a content page to 'FillAndExpand' horizontally while making the height equal to it's width. So far for the xaml I've got:
<ContentPage Title="About" >
<StackLayout>
<BoxView x:Name ="imageBoxView" Color="AliceBlue" HorizontalOptions="FillAndExpand" />
</StackLayout>
</ContentPage>
But I don't know what value to keep for the Height Request.
You can set the binding context to itself, and then bind the HeightRequest to the view's Width.
Note: This will not work in the XAML preview mode in Visual Studio, but will work at runtime on the device.
<StackLayout>
<BoxView x:Name ="imageBoxView" Color="AliceBlue"
HorizontalOptions="FillAndExpand"
BindingContext="{x:Reference imageBoxView}"
HeightRequest="{Binding Width}" />
</StackLayout>