Forum Xamarin.Forms

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

Xamarin.Forms: adjust Image size to the width of the parent's in a BindableLayout

PacodosoPacodoso FRUniversity ✭✭✭

I'm trying to adjust the the Image size to the width of the parent's container in a BindableLayout, but I didn't found a way to achieve this.

I'm based on this other topic to achieve this.

I first tried to embed the Image in a Grid container like this:

<StackLayout x:Name="NewsList"
             BindableLayout.ItemsSource="{Binding News}">
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <Frame>
                <Grid Padding="0" Margin="0"
                      VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
                      BackgroundColor="Red">
                    <Image Source="{Binding Image}" Aspect="AspectFit"/>
                </Grid>
                <Label Text="Description" />
            </Frame>
        </DataTemplate>
    </BindableLayout>
</StackLayout

=> but we can see that I get red stripes around the images (at the top/bottom, or at the left/right):
ImageInGrid

Then, I've tried to use a CachedImage from FFImageLoading like this:

<Grid Padding="0" Margin="0"
        HorizontalOptions="FillAndExpand"
        VerticalOptions="FillAndExpand"
        BackgroundColor="Orange">
    <ffimageloading:CachedImage Source="{Binding Image}"
                    HorizontalOptions="Fill"
                    VerticalOptions="Fill"
                    Aspect="AspectFill"
                    DownsampleToViewSize="True"/>
</Grid>

=> but in this case, the images are not fully visible, or are truncated
CachedImage

=> So is there another way allowing me to display an image in the full width of the parent's container, and keeping the original ratio?

Best Answer

  • PacodosoPacodoso FRUniversity ✭✭✭
    Accepted Answer

    I finally found a solution that worked for me here, it's the solution given by Frankvans:

    class ImageFit : Image {
        protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) {
            SizeRequest sizeRequest = base.OnMeasure(double.PositiveInfinity, double.PositiveInfinity);
    
            var innerRatio = sizeRequest.Request.Width / sizeRequest.Request.Height;
    
            if (double.IsNaN(innerRatio))
                return sizeRequest;
    
            // Width needs to be adjusted
            if (double.IsInfinity(heightConstraint)) {
                // Height needs to be adjusted
                if (double.IsInfinity(widthConstraint)) {
                    widthConstraint = sizeRequest.Request.Width;
                    heightConstraint = sizeRequest.Request.Height;
                } else {
                    // Adjust height
                    heightConstraint = widthConstraint * sizeRequest.Request.Height / sizeRequest.Request.Width;
                }
            } else if (double.IsInfinity(widthConstraint)) {
                // Adjust width
                widthConstraint = heightConstraint * sizeRequest.Request.Width / sizeRequest.Request.Height;
            } else {
                // strech the image to make it fit while conserving it's ratio
                var outerRatio = widthConstraint / heightConstraint;
    
                var ratioFactor = (innerRatio >= outerRatio) ?
                    (widthConstraint / sizeRequest.Request.Width) :
                    (heightConstraint / sizeRequest.Request.Height);
    
                widthConstraint = sizeRequest.Request.Width * ratioFactor;
                heightConstraint = sizeRequest.Request.Height * ratioFactor;
            }
            sizeRequest = new SizeRequest(new Size(widthConstraint, heightConstraint));
            return sizeRequest;
        }
    }
    

Answers

  • JoeMankeJoeManke USMember ✭✭✭✭✭

    I don't see it in the XAML you posted, but are you explicitly setting the height of the row/frame/grid somewhere?

  • PacodosoPacodoso FRUniversity ✭✭✭
    No the height is not set as each row can have a different height, as the description and the image size are never the same.
  • int width = (int)Math.Truncate(Application.Current.MainPage.Width);

    You can bind your width to the value above.

    Works for me

  • ColeXColeX Member, Xamarin Team Xamurai
    edited September 2020

    So is there another way allowing me to display an image in the full width of the parent's container, and keeping the original ratio?

    Apparently, the height of the image should adjust according to the ratio , you could create RowDefinitions inside Grid and set its Height as auto .

       <StackLayout x:Name="NewsList"  BindableLayout.ItemsSource="{Binding News}">
            <BindableLayout.ItemTemplate>
                <DataTemplate>
                    <Frame>
                        <Grid Padding="0"
                          Margin="0"
                          VerticalOptions="FillAndExpand" 
                          HorizontalOptions="FillAndExpand"
                          BackgroundColor="Red">
                            <Grid.RowDefinitions>       
                                <RowDefinition Height="auto"/>              //add this line
                            </Grid.RowDefinitions>
                            <Image Source="{Binding .}" Aspect="AspectFill"/>
                        </Grid>
                    </Frame>
                </DataTemplate>
            </BindableLayout.ItemTemplate>
        </StackLayout>
    

    Here are three images (different radios )



    Test result

  • PacodosoPacodoso FRUniversity ✭✭✭

    @EmmanuelOluwagbemiga said:
    int width = (int)Math.Truncate(Application.Current.MainPage.Width);

    You can bind your width to the value above.

    Works for me

    How do you achieve this? Do you have a sample?

  • ColeXColeX Member, Xamarin Team Xamurai

    It's not related with those images , it is happening because the image size has been auto set before the downloading finish and it will never change even the downloading done.

    As you see when I download the images and show them locally , it works perfectly.

  • PacodosoPacodoso FRUniversity ✭✭✭

    @ColeX thanks for your feedback again. So is there a way to manage the case of the images are downloaded from an URL?

  • @Pierre-ChristopheDus said:

    @EmmanuelOluwagbemiga said:
    int width = (int)Math.Truncate(Application.Current.MainPage.Width);

    You can bind your width to the value above.

    Works for me

    How do you achieve this? Do you have a sample?

    @ColeX approach appears much sleeker. You should definitely stick to that.

    @Pierre-ChristopheDus said:
    @ColeX thanks for your feedback again. So is there a way to manage the case of the images are downloaded from an URL?

    Also, you might have misunderstood @ColeX, he simply meant it doesn't matter whether your images are online or locally stored. What matters is the way you configure your Image control and its parent(maybe Grid as in this case) to display them.

    I hope your problem's fully solved now. In that case, you should mark @ColeX answer as accepted.

    Happy Coding.

  • PacodosoPacodoso FRUniversity ✭✭✭

    @EmmanuelOluwagbemiga said:

    @Pierre-ChristopheDus said:

    @EmmanuelOluwagbemiga said:
    int width = (int)Math.Truncate(Application.Current.MainPage.Width);

    You can bind your width to the value above.

    Works for me

    How do you achieve this? Do you have a sample?

    @ColeX approach appears much sleeker. You should definitely stick to that.

    @Pierre-ChristopheDus said:
    @ColeX thanks for your feedback again. So is there a way to manage the case of the images are downloaded from an URL?

    Also, you might have misunderstood @ColeX, he simply meant it doesn't matter whether your images are online or locally stored. What matters is the way you configure your Image control and its parent(maybe Grid as in this case) to display them.

    I hope your problem's fully solved now. In that case, you should mark @ColeX answer as accepted.

    Happy Coding.

    I'm not sure, I think that my issue is occurring as the images are not locally stored, and the height is not updated after the images are downloaded.

  • Do you want to change the height?

    That wasn't your original question...

  • PacodosoPacodoso FRUniversity ✭✭✭

    @EmmanuelOluwagbemiga said:
    Do you want to change the height?

    That wasn't your original question...

    I want to keep the image ratio yes.

  • EmmanuelOluwagbemigaEmmanuelOluwagbemiga USMember ✭✭
    edited September 2020

    Alright, you want to keep the aspect ratio and you also want the width to fill the screen?

    That's exactly what @ColeX's answer does. Maybe you send in some screenshots (and your code too) to describe your what you're getting and how

  • PacodosoPacodoso FRUniversity ✭✭✭
    edited September 2020

    @EmmanuelOluwagbemiga said:
    Alright, you want to keep the aspect ratio and you also want the width to fill the screen?

    That's exactly what @ColeX's answer does. Maybe you send in some screenshots (and your code too) to describe your what you're getting and how

    You can see screenshots and code in my first post.

    When I try to apply the @ColeX suggestion like this:

    <Frame Padding="0" Margin="0">
        <Grid Padding="0"
            Margin="0"
            VerticalOptions="FillAndExpand" 
            HorizontalOptions="FillAndExpand"
            BackgroundColor="Red">
            <Grid.RowDefinitions>       
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>
            <Image Source="{Binding Image}" Aspect="AspectFill"/>
        </Grid>
    </Frame>
    

    We can see that all the images (as the first or the fourth) are not fully visible:

    This is probably because my images are not stored locally, as in the @ColeX sample. I don't think there is solution when the images are coming from an API.

  • I'm 100% certain that your issue is not dependent on whether the image is online or local. Set your Row definition's height to * and see if it works.

    At this point, I'm not really sure how to help anymore

    I hope you figure it out

  • PacodosoPacodoso FRUniversity ✭✭✭

    @EmmanuelOluwagbemiga said:
    I'm 100% certain that your issue is not dependent on whether the image is online or local. Set your Row definition's height to * and see if it works.

    At this point, I'm not really sure how to help anymore

    I hope you figure it out

    @ColeX maybe could you confirm us that the issue is related to the use of online images?

  • ColeXColeX Member, Xamarin Team Xamurai
    edited September 2020

    @Pierre-ChristopheDus said:

    @EmmanuelOluwagbemiga said:
    I'm 100% certain that your issue is not dependent on whether the image is online or local. Set your Row definition's height to * and see if it works.

    At this point, I'm not really sure how to help anymore

    I hope you figure it out

    @ColeX maybe could you confirm us that the issue is related to the use of online images?

    I'm not sure but i found a workaround , you can reset the size in SizeChanged event .

    Complete code

    //xaml
     <ScrollView x:Name="scroll">
        <StackLayout x:Name="NewsList"  BindableLayout.ItemsSource="{Binding News}">
            <BindableLayout.ItemTemplate>
                <DataTemplate>
                    <Frame Padding="0" Margin="0">
                        <Grid Padding="0"  Margin="0"  
                              VerticalOptions="FillAndExpand" 
                              HorizontalOptions="FillAndExpand"
                              BackgroundColor="Red"  >
                            <Image 
                                   Source ="{Binding .}" 
                                   HorizontalOptions="Start"                           
                                   SizeChanged="Image_SizeChanged"/>
                        </Grid>
                    </Frame>
                </DataTemplate>
            </BindableLayout.ItemTemplate>
        </StackLayout>
      </ScrollView>
    
    
        //code 
        private void Image_SizeChanged(object sender, EventArgs e)
        {
            var screenWidth = Application.Current.MainPage.Width;
    
            var image = sender as Image;
            var w = image.Width;
            var h = image.Height;
    
            if (h > 1 && w != screenWidth)
            {
                var ratio = w / h;
    
                image.WidthRequest = screenWidth;
                image.HeightRequest = screenWidth / ratio;          
            }
        }
    

    Output

  • @ColeX
    I'm not sure but i found a workaround , you can reset the size in SizeChanged event .

    Looks like this should solve the issue

  • PacodosoPacodoso FRUniversity ✭✭✭
    edited October 2020

    It doesn't work in my case, I have the screen that is frozen with the loader: there are infinite calls that are done to Image_SizeChanged(), as the event is called recursively.

    For the moment, I will keep the images that are not fully displayed, as I didn't found any solution.

  • PacodosoPacodoso FRUniversity ✭✭✭
    Accepted Answer

    I finally found a solution that worked for me here, it's the solution given by Frankvans:

    class ImageFit : Image {
        protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) {
            SizeRequest sizeRequest = base.OnMeasure(double.PositiveInfinity, double.PositiveInfinity);
    
            var innerRatio = sizeRequest.Request.Width / sizeRequest.Request.Height;
    
            if (double.IsNaN(innerRatio))
                return sizeRequest;
    
            // Width needs to be adjusted
            if (double.IsInfinity(heightConstraint)) {
                // Height needs to be adjusted
                if (double.IsInfinity(widthConstraint)) {
                    widthConstraint = sizeRequest.Request.Width;
                    heightConstraint = sizeRequest.Request.Height;
                } else {
                    // Adjust height
                    heightConstraint = widthConstraint * sizeRequest.Request.Height / sizeRequest.Request.Width;
                }
            } else if (double.IsInfinity(widthConstraint)) {
                // Adjust width
                widthConstraint = heightConstraint * sizeRequest.Request.Width / sizeRequest.Request.Height;
            } else {
                // strech the image to make it fit while conserving it's ratio
                var outerRatio = widthConstraint / heightConstraint;
    
                var ratioFactor = (innerRatio >= outerRatio) ?
                    (widthConstraint / sizeRequest.Request.Width) :
                    (heightConstraint / sizeRequest.Request.Height);
    
                widthConstraint = sizeRequest.Request.Width * ratioFactor;
                heightConstraint = sizeRequest.Request.Height * ratioFactor;
            }
            sizeRequest = new SizeRequest(new Size(widthConstraint, heightConstraint));
            return sizeRequest;
        }
    }
    
Sign In or Register to comment.