Search Unity

scroll view / horizontal layout group, make child image 100% height while keeping aspect ratio

Discussion in 'UGUI & TextMesh Pro' started by rpuls, Oct 15, 2019.

  1. rpuls

    rpuls

    Joined:
    Feb 3, 2017
    Posts:
    101
    First of all, I apologize if this is off topic for this forum, I am totally new to this area of the forum, but couldn't find another topic that sounded right...

    I'm trying to make a responsive horizontal scroll layout, where each child element should fit parents height and width should follow along based the image aspect ratio.

    Right now I have a horizontal layout group, each child element is an UI image with turned on preserve aspect ratio so the image never get squeezed in either height or width. but they doesn't seem to scale right. As you see the image below, the child elements should be bigger so that they take up all the height, but they don't.



    I have tried turning on `control child size - height` on the layout group, and as you can see in the image below, this does actually force the components height to match parent. Now the problems is just that since the width of the child is fixed the image it self is not expanding in height...



    Only way I have been able to achieve the behavior that I want is by adding an aspect ratio fitter on each child, setting it to `height controls width` and set the ratio to the same aspect ratio as the image has. The layout group forces the child component to fit its height, and the aspect ratio fitter actively increases the child components width to match the aspect ratio of the image, make it nice and responsive. wide screens will see more children at once, narrow screens will see less and more will overflow, but since it is a scroll rect that is no problem.

    The problem is that I am not allowed to set the aspect ratio fitter on the children when the parent has a layout group. (there is a warning on the component about this) and when I click play it behaves weird, the children looses their width and get stacked on top of each other.

    So my question is.
    What settings should I use on the layout group (and/or) the content size fitter? And what settings should I use on the child image, in order to get:

    • child height = 100%
    • child width = height x aspect ratio of image (without using aspect ratio fitter)
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Here's what I believe is happening:

    Your Horizontal Layout Group is taking the total available width and dividing it up evenly among your individual items.

    The total available width is being determined by the Content Size Fitter, which is set to use preferred horizontal size. So it is adding up the preferred horizontal size of all children (plus padding/spacing from the layout group).

    The children have Image components, so their preferred size is being calculated automatically based on their sprite. But this calculates a preferred width and a preferred height based on the sprite's dimensions and "pixels per unit". This is an absolute size, not a ratio--it doesn't know that you are trying to determine a proportional width based off a fixed height.


    Now, you could very easily change the "preferred width" to be any value you want, either by messing with your sprite's settings or by attaching a "layout element" component to the items and setting its "preferred width" explicitly to whatever number you want. So you could pick a number that works for your height. However, if you subsequently change the height, this won't automatically adjust.


    In order for it to adjust automatically, it would have to be doing something like this:
    • Determine the height of the group
    • Determine the preferred aspect ratios of the items
    • Based on the above, calculate the necessary width per item to have that aspect ratio at that height
    • Set the width of the overall group based on that desired width (taking into account padding/spacing)
    I suspect that is just a bit beyond the complexity of what Unity's auto layout logic can handle. You could write a custom script to do it, though.
     
  3. rpuls

    rpuls

    Joined:
    Feb 3, 2017
    Posts:
    101
    Hey, thanks for you response. However, I feel quite confident that this should be possible quite easily, as I did the exact same thing for a vertical scroll view, while using preferred fit vertical (no fixed height, if i add more child elements it just works). I tried to copy that layout group, and just "flip" everything to be horizontal instead of vertical. It just doesn't behave the same way. Or maybe I did something wrong.

    And yes I could indeed write a script, but I am trying to avoid using script for layouts, as I can't test the responsiveness when not in play mode as the scripts are then not running. The script required would have to do the exact same thing as the aspect ratio fitter. "height crontrols width.... width = height * magic number". and this just feels wrong trying to replicate something with scripting when there is a dedicated component for that exact job, that is just not allowed in this case. o_O
     
  4. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    The option in the middle section of my previous post doesn't require that you give the container a fixed width, just the children. It would automatically adjust to different numbers of children, just not to different heights.

    It is my suspicion that you just "got lucky" in your vertical example. Maybe the preferred heights of your images happened to match your current screen resolution (or at least were high enough that your fixed-aspect-ratio size was limited by width instead of height), or maybe you were using a different type of content (e.g. text). I think it is pretty unlikely that you swapped axes, did everything else the same, and got fundamentally different behavior.

    It is possible to make your own scripts run in edit mode, if you want.

    And you might think that aspect ratio fitter is exactly what you need, but odds are that you only think that because your understanding of aspect ratio fitter and layout groups is imperfect. Even if Unity "lets you" use an aspect ratio fitter and it successfully forces the child width to be what you think it should be, you'd still need something in your scene that modifies the total container width to match, or else you'll just end up with children that are bigger or smaller than the amount of space being reserved for them. Since content size fitter works based on preferred size (not actual size), I don't think you've got any such mechanism in place.

    I think what you are imagining is something that's similar to aspect ratio fitter but that controls the object's preferred size instead of its actual size. It's true that a lot of the underlying logic would be the same, but that doesn't imply that there's anything you could do with the existing aspect ratio fitter (short of rewriting its source code) that would actually accomplish your goal.

    And probably the reason that component doesn't already exist is that auto layout uses the preferred size to calculate what the actual size should be, so if you change the preferred size based on the actual size then you potentially need to recalculate everything, and theoretically there's danger of an infinite loop.
     
  5. rpuls

    rpuls

    Joined:
    Feb 3, 2017
    Posts:
    101
    Maybe my explanation is bad. I think we are talking past each other. Or my knowledge about unitys UI system is completely off track.

    I'm trying to make this responsive, but most importantly is that the aspect ratio of the images (child elements of the layout group) is respected at all time. So I really don't understand how giving them a fixed width can achieve a responsive design. My list can consist of 1 child element or 100, the amount will vary, and it doesn't matter as they are allowed to overflow the viewport horizontally because they are in a scroll rect.

    Let's say that I put preferred width 100 on each child element. Then in order to respect the aspect ratio of the image I which is H = 1,6W then the height must be 160. How is that ever going to be responsive ?

    The only possible way I see of calculating the size (W & H) of the child elements is by looking at screen height (in my case the height "item list") which has the horizontal layout group. subtract padding top and bottom. whatever is left should now be considered as 100% available height. Each child should always have this height. And now since we know their height, and we know the magic number of the aspect ratio, I really don't see why it is complicated to set the correct width of the children.

    And thanks for informing about the runInEditMode feature. I'am definitely gonna be using this for debugging an experimenting purposes! :)
     
  6. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    It's definitely possible to make an auto-sizing UI that behaves the way that you want. But Unity isn't making one game, they're making an engine, so they have the much harder task of designing an auto-sizing UI that behaves in every possible way that anyone could ever want. At some point they have to make a decision about which models they are going to support and which they are not.

    A Horizontal Layout Group doesn't take its size from it's children, it gives a size to its children based on how much width is available. This is so that you can have layouts where the elements stretch or shrink to fit the available space without scrolling (which is one of many possible things that people using Unity sometimes want).

    Obviously some people also want to have layouts where the children stay the same size and the container grows, so there's a workaround for that using Content Size Fitter. The Horizontal Layout Group doesn't change, it still takes its own size and divides it up among the children, but the Content Size Fitter adjusts the group's total width based on the preferred size of the children.

    But how do you know the "preferred" size of a sprite? It can theoretically stretch to any size.

    So Unity provides a default "preferred" size based on the actual size and import settings of the sprite, and then it lets you override that by setting a different size with Layout Element if you want.

    But Unity doesn't (AFAIK) provide a component that dynamically changes the preferred width based on the available height.

    Unity provides a component (Aspect Ratio Fitter) that dynamically changes the actual width based on the actual height (or based on the parent's height). But Content Size Fitter doesn't look at the actual width (because the Horizontal Layout Group is changing that), it looks at the preferred width. So changing the actual width doesn't hook up to the rest of the auto-sizing system that you're currently using.

    You could write your own component that sets the preferred width based on current height (or based on parent's current height). Just realize that layouts could exist where changing your preferred width somehow indirectly causes that height to change, so there's no inherent guarantee that you will reach an equilibrium (though you probably will in most cases).

    You also could write your own component that sets the width of your container based on something other than the preferred widths of the children (to replace the Content Size Fitter). But you should be careful what you choose to base it on, because remember that the Horizontal Layout Group is going to actively try to change the child widths, so if you use their current actual widths as input you may get some very strange effects.

    You could also write your own replacement for Horizontal Layout Group that controls sizes using some other algorithm.

    At the extreme, you could write one specialized component that exists solely for this one layout and controls the sizes and positions of absolutely everything all by itself using absolutely any rules that you can figure out how to write in C#.

    Or you could decide that's too much work and that you're OK with a moderately-less-auto-resizing UI that assumes the container's sizeDelta.y is never going to change (note: you could still change its apparent size via scaling, which is what a Canvas Scaler would do to cope with different resolutions).

    Or you can hope that I'm wrong and that there's some way of accomplishing your goal using existing Unity components that I'm just not aware of. (But since no one else has chimed into this thread so far, I think it's unlikely you'll learn of such a way just by waiting for more replies.)
     
  7. rpuls

    rpuls

    Joined:
    Feb 3, 2017
    Posts:
    101
    Thanks for all your information, it have been very helpful for trouble shooting. I finally got to the bottom of why it worked vertically so easily, and why replicating the setting for the horizontal layout kept behaving differently. It was due to my settings on the canvas scaler...



    These setting are good for horizontal lists, I'm matching the width so making components span full width and scale their height accordingly works very well. My plank header will keep its aspect ratio at all time because it stretches in width and is top aligned. The "item list" horizontal layout works the same way, it also stretches in width and the content size fitter sets its height (right?). All the children have preferred width and height, and are scaled with the width of the screen due to those canvas scaler setting. I feel quite confident that this it whats going on.


    Now the problem is that I am not able to easily change these settings, since I still need the vertical list in my UI and in the same canvas keeping the wooden plank header intact through all the different menus below it regardless of their design.... I'm not sure how to proceed, but I might experiment with writing my own componenet that will address the case of a horizontal list item in a canvas scaler that matches width.
     
  8. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    If the Canvas Scaler was the reason, then it sounds like you never actually changed the sizeDelta of your containers in your tests, you only changed your window size? Sounds like your test process was not very robust.