34
votes

I want to overlay my ActivityIndicator in the middle of my form but I'm not entirely sure how I can go about this, I assume I need to wrap my stack layout in a relative layout ?

I'm doing this in code and the closest example I have found is using XAML which uses a grid as seen below:

 <ScrollView BackgroundColor="#d3d6db">
    <RelativeLayout
        VerticalOptions="FillAndExpand"
        HorizontalOptions="FillAndExpand">
        <StackLayout Orientation="Vertical"
                     VerticalOptions="FillAndExpand"
                     Padding="10"
                     RelativeLayout.XConstraint="{ConstraintExpression Type=Constant, Constant=0}"
                     RelativeLayout.YConstraint="{ConstraintExpression Type=Constant, Constant=0}">

            <Grid x:Name="SegmentGrid"
                  RowSpacing="10"
                  ColumnSpacing="10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
            </Grid>
            <WebView BackgroundColor="White" VerticalOptions="FillAndExpand" Source="{Binding DescriptionHtml}" />
        </StackLayout>

        <ActivityIndicator IsVisible="{Binding IsBusy}"
                           IsRunning="{Binding IsBusy}"
                           Color="Black"
                           VerticalOptions="CenterAndExpand"
                           HorizontalOptions="CenterAndExpand"
                           RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
                                    Property=Height,
                                    Factor=0.33}"
                           RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
                                    Property=Height,
                                    Factor=0.33}" />
    </RelativeLayout>
</ScrollView>
7

7 Answers

25
votes

If you have problems with RelativeLayout, you can also use AbsoluteLayout which works in a similar context. Sample code below:

var overlay = new AbsoluteLayout();
var content = new StackLayout();
var loadingIndicator = new ActivityIndicator();
AbsoluteLayout.SetLayoutFlags(content, AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(content, new Rectangle(0f, 0f, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
AbsoluteLayout.SetLayoutFlags(loadingIndicator, AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(loadingIndicator, new Rectangle(0.5, 0.5, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
overlay.Children.Add(content);
overlay.Children.Add(loadingIndicator);

If you would like a full working example, I have made one available @ https://github.com/teamtam/xamarin-forms-timesheet

19
votes

There's an acceptable answer but I wrote an extensions for all contentpage and think it could be nice.

public static void AddProgressDisplay (this ContentPage page)
{
    var content = page.Content;

    var grid = new Grid();
    grid.Children.Add(content);
    var gridProgress = new Grid { BackgroundColor = Color.FromHex("#64FFE0B2"), Padding = new Thickness(50) };
    gridProgress.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
    gridProgress.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
    gridProgress.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
    gridProgress.SetBinding(VisualElement.IsVisibleProperty, "IsWorking");
    var activity = new ActivityIndicator
    {
        IsEnabled = true,
        IsVisible = true,
        HorizontalOptions = LayoutOptions.FillAndExpand,
        IsRunning = true
    };
    gridProgress.Children.Add(activity, 0, 1);
    grid.Children.Add(gridProgress);
    page.Content = grid;
}

Remark: IsWorking must be member of ViewModel (implemented INotifyPropertyChanged) property.

Call extension page ctor last statement.

this.AddProgressDisplay();
12
votes

You need to use absolute layout. Follow the following hierarchy of the layout:

<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
    <StackLayout AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1">

        <!--< Your control design goes here >-->

    </StackLayout>

    <StackLayout IsVisible="{Binding IsBusy}" Padding="12"
             AbsoluteLayout.LayoutFlags="PositionProportional"
             AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">

        <ActivityIndicator IsRunning="{Binding IsBusy}" Color ="#80000000"/>

        <Label Text="Loading..." HorizontalOptions="Center" TextColor="White"/>

    </StackLayout>

</AbsoluteLayout>

With this the activity indicator will be overlaid in the middle of a stack layout.

6
votes

Yes, you should wrap your StackLayout inside Relative Layout. I have already gone through the example which you have given and acheived my requirement by formatting my XAML as below code

XAML Code

<RelativeLayout ...> 
    <StackLayout ...>
        <ActivityIndicator  IsRunning="false"
                            Color="Maroon"
                            BackgroundColor="Black"
                            VerticalOptions="CenterAndExpand"
                            HorizontalOptions="CenterAndExpand"
                            RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
                                Property=Height,
                                Factor=0.33}"
                            RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
                                Property=Height,
                                Factor=0.28}" />
    </StackLayout>
</RelativeLayout> 
1
votes

Here is a little more costumizable version of Nuri YILMAZ version.

public static void AddProgressDisplay(this ContentPage page, string isVisibleProperty = "IsBusy", string bgColor = "#1a1a1ab2")
{
    var content = page.Content;

    var grid = new Grid();
    grid.Children.Add(content);
    var gridProgress = new Grid { BackgroundColor = Color.FromHex(bgColor), Padding = new Thickness(50) };
    gridProgress.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
    gridProgress.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
    gridProgress.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
    gridProgress.SetBinding(VisualElement.IsVisibleProperty, isVisibleProperty);
    var activity = new ActivityIndicator
    {
        IsEnabled = true,
        IsVisible = true,
        HorizontalOptions = LayoutOptions.FillAndExpand,
        IsRunning = true
    };
    gridProgress.Children.Add(activity, 0, 1);
    grid.Children.Add(gridProgress);
    page.Content = grid;
}
0
votes

You can also use activity indicator inside the absolute layout. and bind the isVisible property of absolute layout to isBusy. Below is my approach

<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" AbsoluteLayout.LayoutBounds="1, 1, 1, 1"
    BackgroundColor="White" Opacity="0.5"   AbsoluteLayout.LayoutFlags="All" x:Name="indicatorFrame" IsVisible="false">

    <ActivityIndicator x:Name="indicator" IsVisible="true" IsRunning="true" IsEnabled="true" 
    HorizontalOptions="Center" VerticalOptions="Center" AbsoluteLayout.LayoutBounds="1, 1, 1, 1"
        Color="Black"    AbsoluteLayout.LayoutFlags="All" />

</AbsoluteLayout>

The binding in the cs file looks like

indicator.BindingContext = this;
indicator.SetBinding(IsVisibleProperty, "IsBusy", BindingMode.OneWay);
indicator.SetBinding(ActivityIndicator.IsRunningProperty, "IsBusy", BindingMode.OneWay);
indicatorFrame.BindingContext = this;
indicatorFrame.SetBinding(IsVisibleProperty, "IsBusy", BindingMode.OneWay);
0
votes

In code you can try this:

RelativeLayout relativeLayout = new RelativeLayout ();
StackLayout stack = new StackLayout ();
ActivityIndicator indicator = new ActivityIndicator ();

relativeLayout.Children.Add (stack);
relativeLayout.Children.Add (indicator);

RelativeLayout.SetBoundsConstraint (stack, () => relativeLayout.Bounds);

RelativeLayout.SetBoundsConstraint (indicator,
    BoundsConstraint.FromExpression (() =>  
        new Rectangle (relativeLayout.Width / 2 - 16, relativeLayout.Height / 2 - 16, 32, 32)));

Assuming width/height of the ActivityIndicator of 32/32, you can pick a size that fits your needs or calculate the size on the fly.

There still seem to be bugs in RelateiveLayout, it throws unexpected null pointer exceptions on occasion, even for solvable constraints