Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly use an AbsoluteLayout inside a StackLayout inside a ScrollView in Xamarin?

OK, I give up. 3 days spent trying to accomplish this simple thing.

Here is my scenario (not all on the same XAML file):

<TabbedPage>
  <NavigationPage>
    <ContentPage/>
    <ContentPage/>
    <CarouselPage>
      <CarouselContentPage>

This is the CarouselContentPage:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyApp.Layouts.PointInfoDataTemplate"
             >
  <ContentPage.Content>  
    <ScrollView Orientation="Vertical" BackgroundColor="Navy" VerticalOptions="Fill">
      <StackLayout x:Name="MyStackLayout" HorizontalOptions="StartAndExpand" VerticalOptions="Start" BackgroundColor="Gray">
        <Label x:Name="NameLabel" FontSize="Medium" HorizontalOptions="Center"/>
        <Label x:Name="DescLabel" FontSize="Medium" HorizontalOptions="Center" />
        <AbsoluteLayout x:Name="ImgsContainer" VerticalOptions="Start" BackgroundColor="Green">
          <Image x:Name="BackImg" Aspect="AspectFit" VerticalOptions="Start"/>
          <Image x:Name="MidImg" Aspect="AspectFit" VerticalOptions="Start"/>
          <Image x:Name="FrontImg" Aspect="AspectFit" VerticalOptions="Start"/>
        </AbsoluteLayout>
      </StackLayout>
    </ScrollView>
  </ContentPage.Content>
</ContentPage>

What I need is simple, a vertical scroll for a page with a few Labels vertically stacked and after that I need to create "one image" which is made of 3 images on top of each other (like 3 layers overlapped). For that I need the AbsoluteLayout just for the images, right?

I've tried all possible combinations of VerticalOptions for all the views in this file and nothing works.

The problem is that the scroll always leave a big space under the images. And with the Green color, so it means the AbsoluteLayout is not shrinking its height after the image resize. The images can have variable sizes and shapes (although the 3 inside each page will all have the same dimensions) and they are all larger than any smartphone screen (zoom feature later). So I need the images to use all the Width available and keep the aspect ratio (AspectFit I believe).

All the images are Embedded Resources. Here is the code behind CarouselContentPage:

public PointInfoDataTemplate(PointModel point)
{
    InitializeComponent();
    NameLabel.Text = point.nome;
    DescLabel.Text = point.apelido;
    BackImg.Source = ImageSource.FromResource(point.backimg);
    MidImg.Source = ImageSource.FromResource(point.midimg);
    FrontImg.Source = ImageSource.FromResource(point.frontimg);
    //BackImg.SizeChanged += delegate {
    //    ImgsContainer.Layout(new Rectangle(imgsContainer.Bounds.X, imgsContainer.Bounds.Y, BackImg.Bounds.Width, BackImg.Bounds.Height));
    //    MyStackLayout.Layout(new Rectangle(imgsContainer.Bounds.X, imgsContainer.Bounds.Y, BackImg.Bounds.Width, BackImg.Bounds.Height));
    //};
}

I even tried resizing the Layouts after the SizeChanged event, but it didn't work either.

So maybe I'm doing something wrong, here's all my code. I'm stuck in it for 3 days, I don't know what else to do.

I tried using a RelativeLayout for the ScrollView.Content. And after an entire day trying to understand it (and not sure if I fully did) I had a worse problem. The RelativeLayout would overflow the Scroll height and hide part of the images behind the tabs (base app container).

But I'd really like to preserve the StackLayout->items + AbsoluteLayout approach.

I appreciate any help.

like image 786
Dpedrinha Avatar asked Apr 22 '16 02:04

Dpedrinha


3 Answers

I found out that the problem was on the AbsoluteLayout not handling its child resize events. Either a problem with the Image element not dispatching the proper events or the AbsoluteLayout not handling them well. So all I needed was to create this event handling. Added a listener to the Image.SizeChanged to set the AbsoluteLayout height.

        private void BackImg_SizeChanged(object sender, EventArgs e)
        {
            ImgsContainer.HeightRequest = BackImg.Height;
        }

That works for my case, but if you have a more complex child disposition for AbsoluteLayout you will need something like that:

private void ChildAdded_EventHandler(object sender, EventArgs e) {
    double neededHeight = 0;
    foreach(var child in sender) {
        if(neededHeight < child.Y+child.Height) {
            neededHeight = child.Y+child.Height;
        }
    }
    sender.HeightRequest = neededHeight;
}

And you will have to add this event handler to AbsoluteLayout.ChildAdded and AbsoluteLayout.ChildRemoved;

like image 185
Dpedrinha Avatar answered Sep 29 '22 02:09

Dpedrinha


If the Grid solution works, that may be easier, but since you asked about AbsoluteLayout...

To get AbsoluteLayout to work, you don't use the same VerticalOptions on the children that you would for other views.

Instead, there are other ways to indicate exactly how to lay out the child with respect to the Absolute Layout. Using C# instead of Xaml, you'd do something like:

AbsoluteLayout.SetLayoutFlags(BackImg,
     AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(BackImg,
     new Rectangle(0, 0, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));

And the same thing for MidImg and FrontImg.

SetLayoutFlags tells the system what aspects of the child's size and/or position to do proportionally to the parent. Then SetLayoutBounds gives the details of how to lay it out. In this case, the code is saying to lay out BackImg at the top left corner.

like image 42
DavidS Avatar answered Sep 29 '22 03:09

DavidS


It's in C# not XAML but it should be easy to convert

Grid imageContainer = new Grid() {    
    HeightRequest = 400,

    Children = {
        new Image(), //Image 1
        new Image(), //Image 2 on top of image 1
        new Image(), //Image 3 on top of image 2 and 1
    }
}

Content = new ScrollView() {    
    Content = new Grid() {
        RowDefinitions = {
            new RowDefinition(){Height = new GridLength(1, GridUnitType.Auto)}, //label1
            new RowDefinition(){Height = new GridLength(1, GridUnitType.Auto)}, //label2
            new RowDefinition(){Height = new GridLength(1, GridUnitType.Auto)}, //image container
        },

        Children ={
            {new Label(), 0, 0},   //Label 1
            {new Label(), 0, 1},   //Label 2
            {imageContainer, 0, 2} //image container
        }
    }
};
like image 45
FriedSloth Avatar answered Sep 29 '22 02:09

FriedSloth