I have a reasonably complicated layout system in my UI that allows for anywhere from 1 to 16 elements. These elements are in either 1 or 2 rows, and are dynamically centered and resized based upon how many elements were added.
I do this by adding all the elements into the scene, then setting the game objects active or inactive based upon how many elements I want.
This works great – they are positioned correctly, resized, etc. There’s just one problem – any layout group changes happen instantaneously, which is jarring for the user.
Is there any way to make the layout basically lerp its changes, as opposed to instantaneously doing them?
So here’s how I did it:
-
Create the hierarchy of what you want in your layout system. Make all those layout elements empty rect transforms (maybe with aspect fitters or layout elements or whatever you need).
-
Duplicate the hierarchy, and put in your visual elements (Image, Text, etc) instead. Delete all layout components from this duped hierarchy (no grids, horizontal layouts, aspect fitters, etc).
-
In every duplicated hierarchy element, I added a script that has it smooth follow its corresponding layout element in the layout hierarchy.
This way, all the player sees is the visual element as it tries to smoothly move to the logical layout position and size.
Hi, I’ve done it like this:
you can use all your group elements as LayoutElements then Instantiate that element as a “dummy element” to know its location in layout and then Instantiate “original element” with “Ignore Layout” on so you can animate it at the position of dummy element, and when the animation is done you can set “Ignore Layout” false again.
I hope this makes sense.
Hey. Last year I found a nice solution by lerping the values of the LayoutElement
component attached to the list items. I can’t find the original thread to source this correctly. In my use case, it’s a one-dimensional layout component, not a grid (Horizontal/Vertical Layout), so I’m not sure how relevant this is.
Here’s the code I modified from the original answer. Hope it helps someone!
(CoroutineHelpers just lets me quickly run a coroutine with delay. You can write a new function to do that using a WaitForSeconds
).
using System;
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Game.InteractionSystem
{
public class NotificationBannerController : MonoBehaviour
{
public float fadeInDuration = 0.5f;
public float fadeOutDuration = 0.5f;
public float stayDuration = 6f;
public AnimationCurve fadeCurve;
private RectTransform rectTransform;
private TMP_Text text;
private LayoutElement layoutElement;
private void Start()
{
rectTransform = GetComponent<RectTransform>();
text = GetComponentInChildren<TMP_Text>();
layoutElement = GetComponent<LayoutElement>();
StartCoroutine(Show(fadeInDuration));
StartCoroutine(CoroutineHelpers.DelayedAction(fadeInDuration + stayDuration, () => StartCoroutine(Hide(fadeOutDuration))));
}
private IEnumerator Show(float duration)
{
// Sanity check
if (duration <= 0.0f)
{
Debug.LogError("Duration must be greater than 0.0f");
yield break;
}
// Wait for a frame to allow the layout to update
yield return null;
yield return null;
float startHeight = rectTransform.rect.height;
float endHeight = text.preferredHeight + 10f;
float startWidth = rectTransform.rect.width;
float endWidth = text.preferredWidth + 10f;
float time = 0.0f;
while (time < duration)
{
// Adding easing to the animation makes the animation much more fluid, compared to
// a standard linear interpolation; see the repository below for the implementation
layoutElement.preferredHeight = Mathf.Lerp(startHeight, endHeight, fadeCurve.Evaluate(time / duration));
layoutElement.preferredWidth = Mathf.Lerp(startWidth, endWidth, fadeCurve.Evaluate(time / duration));
yield return null;
time += Time.deltaTime;
}
layoutElement.preferredHeight = endHeight;
layoutElement.preferredWidth = endWidth;
}
private IEnumerator Hide(float duration)
{
// Sanity check
if (duration <= 0.0f)
{
Debug.LogError("Duration must be greater than 0.0f");
yield break;
}
float startHeight = text.preferredHeight;
float endHeight = 0;
float startWidth = text.preferredWidth;
float endWidth = 0;
float time = 0.0f;
while (time < duration)
{
// Adding easing to the animation makes the animation much more fluid, compared to
// a standard linear interpolation; see the repository below for the implementation
layoutElement.preferredHeight = Mathf.Lerp(startHeight, endHeight, fadeCurve.Evaluate(time / duration));
layoutElement.preferredWidth = Mathf.Lerp(startWidth, endWidth, fadeCurve.Evaluate(time / duration));
yield return null;
time += Time.deltaTime;
}
layoutElement.preferredHeight = endHeight;
layoutElement.preferredWidth = endWidth;
Destroy(gameObject, 1);
}
}
}