Accordion Type Layout

I am trying to make a GUI using the new 4.6 GUI components. This screen needs to be able to expand and contract depending on the values that the user enters.

For example if the user checks a certain checkbox, several more input fields need to show up. If they uncheck that box, those input fields need to be hidden.

I am trying to do it with the auto layout components, but can’t get it to work.

Does anyone know how to make this happen?

Hey,

After reading your topic i had this urge to see if accordion effect could be achieved with the gui layout elements so i decided to give it a shot and here are my results.

Also i am attaching the project from the video to this post, you guys feel free to use, share or do whatever you feel like with it : ]

1795357–114332–Accordion.zip (169 KB)

13 Likes

I love this community. Thank you. I will have to modify a little bit because I need to have multiple sections open at the same time. Maybe that is called a collapsible and not an accordion.

Great job!

1 Like

Hey,

I’ve made a collapsible version that functions just like the accordion.

1796048–114369–Collapsible.zip (169 KB)

8 Likes

Thanks for making this ChoMPi, I’ve added this to the Useful 4.6 Scripts thread, hope you don’t mind. :slight_smile:

No problem.

I used the collapsible version along with knowledge from this tutorial on layouts to try solve the same issue as you had but i am still having difficulty. I was hoping i could add a cell on the gird that would contain a less constrained mix of buttons, labels and input boxes. This box will not collapse however.

Using the downloadable example, could you tell me how i would add, for example, a button that is 40x40 units large when expanded then disappears to show only the heading when closed?

Thanks in advance to anyone who can help me with this.

I added on the Accordion scripts that you can’t click again on the Tab.

UIAccordion.cs

using UnityEngine;
using System.Collections;

namespace UnityEngine.UI
{
    [RequireComponent(typeof(VerticalLayoutGroup)), RequireComponent(typeof(ContentSizeFitter)), RequireComponent(typeof(ToggleGroup))]
    public class UIAccordion : MonoBehaviour {
      
        public enum Transition
        {
            Instant,
            Tween
        }
      
        [SerializeField] private Transition m_Transition = Transition.Instant;
        [SerializeField] private float m_TransitionDuration = 0.3f;
        private Transform m_activeAccrodionElement;

        /// <summary>
        /// Gets or sets the transition.
        /// </summary>
        /// <value>The transition.</value>
        public Transition transition
        {
            get { return this.m_Transition; }
            set { this.m_Transition = value; }
        }
      
        /// <summary>
        /// Gets or sets the duration of the transition.
        /// </summary>
        /// <value>The duration of the transition.</value>
        public float transitionDuration
        {
            get { return this.m_TransitionDuration; }
            set { this.m_TransitionDuration = value; }
        }

        /// <summary>
        /// Gets or sets the active Accrodion Element.
        /// </summary>
        /// <value>The active Accrodion Element</value>
        public Transform activeAccrodionElement
        {
            get { return this.m_activeAccrodionElement; }
            set { this.m_activeAccrodionElement = value; }
        }
    }
}

UIAccordionElement.cs

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI.Tweens;
using System;
using System.Collections;

namespace UnityEngine.UI
{
    [RequireComponent(typeof(RectTransform)), RequireComponent(typeof(LayoutElement))]
    public class UIAccordionElement : Toggle {

        [SerializeField] private float m_MinHeight = 18f;
       
        private UIAccordion m_Accordion;
        private RectTransform m_RectTransform;
        private LayoutElement m_LayoutElement;
       
        [NonSerialized]
        private readonly TweenRunner<FloatTween> m_FloatTweenRunner;
       
        protected UIAccordionElement()
        {
            if (this.m_FloatTweenRunner == null)
                this.m_FloatTweenRunner = new TweenRunner<FloatTween>();
           
            this.m_FloatTweenRunner.Init(this);
        }
       
        protected override void Awake()
        {
            base.Awake();
            base.transition = Transition.None;
            base.toggleTransition = ToggleTransition.None;
            this.m_Accordion = this.gameObject.GetComponentInParent<UIAccordion>();
            this.m_RectTransform = this.transform as RectTransform;
            this.m_LayoutElement = this.gameObject.GetComponent<LayoutElement>();
            if(this.isOn)
                this.m_Accordion.activeAccrodionElement = this.m_RectTransform;
            this.onValueChanged.AddListener(OnValueChanged);
        }
       
        protected override void OnValidate()
        {
            base.OnValidate();
           
            if (this.group == null)
            {
                ToggleGroup tg = this.GetComponentInParent<ToggleGroup>();
               
                if (tg != null)
                {
                    this.group = tg;
                }
            }
           
            LayoutElement le = this.gameObject.GetComponent<LayoutElement>();
           
            if (le != null)
            {
                if (this.isOn)
                {
                    le.preferredHeight = -1f;
                }
                else
                {
                    le.preferredHeight = this.m_MinHeight;
                }
            }
        }
       
        public void OnValueChanged(bool state)
        {
            if (this.m_LayoutElement == null)
                return;
            if (this.m_Accordion.activeAccrodionElement == this.m_RectTransform && state == true)
                return;
            this.m_Accordion.activeAccrodionElement = this.m_RectTransform;

            UIAccordion.Transition transition = (this.m_Accordion != null) ? this.m_Accordion.transition : UIAccordion.Transition.Instant;
           
            if (transition == UIAccordion.Transition.Instant)
            {
                if (state)
                {
                    this.m_LayoutElement.preferredHeight = -1f;
                }
                else
                {
                    this.m_LayoutElement.preferredHeight = this.m_MinHeight;
                }
            }
            else if (transition == UIAccordion.Transition.Tween)
            {
                if (state)
                {
                    this.StartTween(this.m_MinHeight, this.GetExpandedHeight());
                }
                else
                {
                    this.StartTween(this.m_RectTransform.rect.height, this.m_MinHeight);
                }
            }
        }
       
        protected float GetExpandedHeight()
        {
            if (this.m_LayoutElement == null)
                return this.m_MinHeight;
           
            float originalPrefH = this.m_LayoutElement.preferredHeight;
            this.m_LayoutElement.preferredHeight = -1f;
            float h = LayoutUtility.GetPreferredHeight(this.m_RectTransform);
            this.m_LayoutElement.preferredHeight = originalPrefH;
           
            return h;
        }
       
        protected void StartTween(float startFloat, float targetFloat)
        {
            float duration = (this.m_Accordion != null) ? this.m_Accordion.transitionDuration : 0.3f;
           
            FloatTween info = new FloatTween
            {
                duration = duration,
                startFloat = startFloat,
                targetFloat = targetFloat
            };
            info.AddOnChangedCallback(SetHeight);
            info.ignoreTimeScale = true;
            this.m_FloatTweenRunner.StartTween(info);
        }
       
        protected void SetHeight(float height)
        {
            if (this.m_LayoutElement == null)
                return;
               
            this.m_LayoutElement.preferredHeight = height;
        }
    }
}

If you have any ideas how it could be better solved please feel free to share :slight_smile: .

-Keyjin

If you have a child of an AccordianElement, such as an image, which then has a child, such as some text, when its collapsed the text is still shown. Any way to fix this?

You could use a mask.

1 Like

Has this been tested on Unity 5?

Yes, works wonderfully.

I seem to be having trouble getting it to work then.

I want a persistent label present like the heading in the demo video, but with nested buttons that are shown when the accordion is on. I can’t seem to get the child elements to hide when the accordion is turned off.

EDIT: Used the collapsible one linked above instead. Its working great! I hope it works well inside a dynamically sized scroll pane.

Hi Chompi. the Collapsible version Works fine. But the transition happens in both directions. anyway to make sure that when it expands, it is downward.

Maybe this is a bit late for you, but if you want it to expand downward, set your Y Pivot on the rect transform containing the Accordion to 1 instead of 0.5.

1 Like

Hi, this is a amazing project. But I’m having some problens to build the project. I recived the following message:

Assets/Accordion/Scripts/UIAccordionElement.cs(40,41): error CS0115: `UnityEngine.UI.UIAccordionElement.OnValidate()’ is marked as an override but no suitable method found to override

Anyone know how to solve it?
Thanks!

When I have a button as the top element, instead of a simple text, the event is beeing caught by that button. So the accorion won’t open. I need that button to define the gameobject im working on. Is there a simple solution to that ?

You need to use

using UnityEngine.UI.Extensions.Tweens;

for Unity 5.3

Using Unity 5.3.2f, when I try and compile I get:
error CS0115: `UnityEngine.UI.UIAccordionElement.OnValidate()’ is marked as an override but no suitable method found to override

1 Like

I may just be an idiot but I cannot get this to work. Anything particular I need to know about the heirarchy or any specific settings. I have an Accordion Group with an Accordion Element child. How does the setup need to be under accordion Element?

Edit: Ok I got it working