Dynamically sized scroll-able list. Need some help here!

Alright, guys! I’ll try to explain to my best ability.

What I am trying to do:
I am trying to make a horizontal scroll-able list of images that all have the same size (1024x1024px). This list contains 1 to x images based on the state of the game.

All images should be correctly scaled to fit a %-value of the vertical screen size while still keeping their aspect ratio so that the image-list look the same on most/all screen sizes. The images should be aligned to the left side of the list (so if you only have one image in the list, it doesn’t appear in the middle of the list, but at the left side).

The list itself (we’re talking the scroll-able area) is re-sized to the content of the list (this is so that the player can scroll between images when the list is too long for the screen size, but that you can’t scroll “away” from from the images if you don’t have enough images to go outside the screen).

- Does this make sense? Should be easy, right?

How I have done it this far:
Step 1:
Created a panel that takes up the desired screen-space and set the anchors to the same position as the re-size-handles like shown here in Figure 1. It will contain the Scroll Rect-component and control its child panel.

Figure 1: This is the panel that will contain the other elements as well as the canvas.

Step 2:
Created a new child panel that stretches vertically to fill its parent with anchors and pivot point aligned at the left side like shown in here in Figure 2. This panel will be the parent of all the images in the list.

Figure 2: Here is the child panel.

To make sure that the children-images is placed correctly in line, this panel has a Horizontal Layout Group-component. Child Alignment is set to “Middle Center” and Child Force Expand is set to only work on the height of the images (check Figure 3).

I also added a Content Size Fitter-component on this panel with a Horizontal Fit to “Preferred Size” so that the panel it self expands horizontally, effectively giving me a bigger scrolling area as the list grows (check Figure 3).

Figure 3: The values of my Horizontal Layout Group and Content Size Fitter.
2358428--159894--Figure 3.png

Step 3:
Created x number of child images that are set to Image Type “Simple” and Preserve Aspect to “true”.

The problem:
The images are not actually placed by the Horizontal Layout Group, but scaled. Even though the Image-component displays the image with the correct aspect ratio, the Rect Transform-component (in this case; controlled by the Horizontal Layout Group) is still stretched pretty bad (effect is of course worse the smaller the screen is because… pixels…).

I’ve tried to give the images a Layout Element-component, playing around with the Preferred Width to override this horizontal stretching. But because these values are pixel-based, I can make it look good on one screen size, on others I end up with pretty much the same problem.

I tried to give the images a Aspect Ratio Fitter-component, trying get the height to control the width of the image Rect Transforms. But because the Rect Transform is already controlled by the Horizontal Layout Group, that is not allowed.

I figured the Layout Element-component is what lets me make the exceptions in the parents layout controls. So I tried to extend the Layout Element-component with some tools that lets me define the values with percentage instead. But I’m no programmer… and the backend of the UI system is a little hard to get around, at least with all these layout group dependencies and Rect Transforms.

Figure 4: Here is how much wider than the image the Rect Transform has streatched.

Figure 5: When all my testing images are put in, this is how long the whole scroll-area becomes.

Have I done something wrong here? Can’t figure it out…
Just post if you need more info.

Here are some more images:

Figure 6: This is pretty much what I’m looking for. I used Layout Element on all images and set Preferred Width to something that looked nice.


Figure 7: After reducing my screen size, its back to this.

Figure 8: Here I’m using the same technique from a small screen size.

Figure 9: When I change to a larger screen size, this is what I end up with. Not what I’m looking for…

I put up a skeleton of the project that shows the problem in a .zip-file so you can take a closer look at the problem. Check the link bellow.

The build-platform is set to PC, Mac & Linux so the re-sizing will only be based on aspect ratio. You can still see the problem when scaling up and down, but for a better perspective: try to switch the build-platform to OSX or Android and try out one of the resolution-based sizes.

NEW LINK: https://drive.google.com/file/d/0Bx_uJDu3XtfsTDZlT19UbFZMYTA/view?usp=sharing

Hi
Tried to download your sample, however the link no longer works :S.

Looking at your issue, here’s a few tips from what I can see.

  • To enable scaling up/down of the parent container, make sure you set it’s anchors to FILL, that way it will size with the canvas. (if you hold down the ALT key when changing the Anchors, it will also resize the RectTransform to match, if you don’t want that, dont hold ALT)
  • If you don’t want it to scale down below a certain size (like with screenshot 3), use Min width rather than preferred. then it will scale up but not scale below a certain value
  • From the looks of it, you need to play with he anchors of the images or child components

As an alternative, try the new ScollSnap control in the UI Extensions project (the Fixed Item scroll with multiple child option in the Editor menu, GameObject → UI → Extensions → Fixed Item scroll). or the Horizontal Scroll Snap (two similar controls with different patterns.

Let me know how you get on.

Hey! Thanks for quick reply!

I’ve been working on my own horizontal layout group that forces the scaling and positioning for the children and parent size fitting to work like I want it to. But it’s really clunky at this point. The original solution is much more elegant if I just got it to work… :I

I looked over your bullet points!

  • The anchor points of the parent should be correctly positioned. I have plans for other UI-content over and under the panel want the layout on. Changing the anchors to fill-mode would just fill the whole parent, in this case making no room for those other UI-elements.
  • But changing the Min-width… then I’m still messing around with pixel sizes, right? Won’t I end up with same problems on smaller screen sizes ?
  • About the anchors of the images, they are controlled by the Horizontal Layout Group, can’t change them :confused:

I looked at the horizontal snap scroll, but it doesn’t fix my problem. Sorry :confused:
Didn’t find the Fixed Item Scroll. But that was pretty much the same, right?

I uploaded the project to my google drive instead so the link doesn’t go nuclear again, so if you got the time for it please take a look.

NEW LINK: https://drive.google.com/file/d/0Bx_uJDu3XtfsTDZlT19UbFZMYTA/view?usp=sharing

If you’re still wondering what I’m rambling about, I could upload a screencast-video explaining what I’m trying to do!
Thanks again!

A screencast would help so we are not talking at cross purposes

Made screencast! Hope this helps!

I think you were on the right track with layout elements on each child. It’s just that you’ll have to have a script modify the preferred width based on whatever the height gets set to by the scaling of the parent scroll area.

So each child could have something like myLayoutElement.preferredWidth = myRectTransform.rect.height;
and you could do that each update if needed but if your resolution isn’t changing you can just do it once.

Thanks for quick answer!
Any way to see the source code of the Layout Element-component?
I don’t want to override something that may ruin the rest of the script. Maybe I can just do it right after the “base.something();” without taking up to much resources?

Wellwellwellwell…
3 lines of code did the magic!

It’s not dynamic for different usage now, but if more people are messing around with this…
Here you go:

using UnityEngine;
using UnityEngine.UI;
using System;

public class MyLayoutElement : LayoutElement
{
    private RectTransform RectTrans;

    protected override void Start()
    {
        DoSomething(base.Start);
    }

    protected override void OnValidate()
    {
        DoSomething(base.OnValidate);
    }

    private void DoSomething(Action callback)
    {
        if (callback != null) callback();
        RectTrans = transform.parent.GetComponent<RectTransform>();
        preferredWidth = RectTrans.rect.height;
    }
}

Thanks to @DWilliams for pointing me in the right direction!