Search code examples
unity-game-enginepluginsandroid-chips

Unity3D - Do we have a plugin or anyone have worked on Tag collection input view, see screenshot


I need a Unity plugin with following features (like Android Chips):

User will search the tags from a list and selected item from list will be shown as a tag. Tag will have text with a cross. Mobile device width will be the maximum horizontal space and if its full, next tag will go in next line.

enter image description here


Solution

  • Note! Before spending too much time on this answer, be sure to check out https://stackoverflow.com/a/38479097/294884


    There is no existing good package for this. Butit is fairly easy to do this using Unity UI. You must familiarize yourself with HorizontalLayoutGroup, etc, and how to add UI items in code. Start from Unity tutorial. Enjoy!


    I went ahead and made a demo project showing just how to do it.

    enter image description here

    The chain is like this: it has a depth of four

    All Rows .. VerticalLayoutGroup, ContentSizeFitter
    (EXPAND MUST BE "OFF")
    (MUST BE HORIZONTAL >>UNCONSTRAINED<<, VERT PREFERRED)
     One Row ... HorizontalLayoutGroup, ContentSizeFitter  ** CFO OFF!
     (EXPAND MUST BE "OFF")
     (MUST BE HORIZONTAL PREFERRED, VERT PREFERRED)
      One Unit .... HorizontalLayoutGroup, ContentSizeFitter   **CFO ON!
      (EXPAND MUST BE "ON")
      (MUST BE HORIZONTAL PREFERRED, VERT PREFERRED)
        Text on the left (inherently sizes in Unity)
        The UI.Button ..... LayoutElement: choose and set "Min" Width/Height
          Text below button ...... nothing (probably won't need this)
     Another row...
     Another row...
     
     CFE means ChildForceExpand button, set correctly as shown!
     For all three ContentSizeFitters, select "Preferred" both ways
    

    You have to carefully set all the items in the chain exactly like that. This is the art of autolayout in Unity! It does take a little while to get expert with it.

    FULL DEMO PROJECT TO DOWNLOAD .... STATIC EXAMPLE:

    http://speedy.sh/xcPcc/Teste.zip

    Simply add and delete items or rows as you wish in the Editor to get started.


    Next! What about reflowing the layout automatically, when you add/subtract items in code.

    Here below is a FULL SCIPT which does just that.

    Full demo project........

    Click to download: http://speedy.sh/5XtkX/Teste2.zip

    Launch the project. Go to the scene, actually hit Play.

    Now while Play'ing, literally duplicate or delete either Items or whole Rows:

    enter image description here

    enter image description here

    Then hit the "test" button to run Flow() ...

    enter image description here

    It will fix the layout flush left ...

    enter image description here

    Here's the script. It is absolutely straightforward - just attach it to the highest level (the level that holds "all the rows") and it figures everything out automatically.

    // FattieFlow - flush left fitting for Unity reactive
    
    // for simplicity, it does assume the only things under here
    // are the relevant rows and items, and, the model row.
    
    // NOTE ----
    // this is deliberately programmed in the most illustrative manner.
    
    // To use - just call Flow() any time to completely correct the layout.
    
    // To put in a project: simply copy the "all rows" and the "model rows"
    // in to your own scene.  That's it.
    
    // To make your layout in editor. Just enable the model so you can see it.
    // Just duplicate the model item/row a few times to see what you're doing.
    // Change the colors/sizes/spacings in any way you wish.  Done.
    // Eliminate the non-model items you used to layout.  Roll tide.
    
    // To test.  Hit Play.  Literally add or delete "items" or rows,
    // so that the flow is wrong.  Run the "Flow()" function and it
    // will fix everything regardless.
    
    using UnityEngine;
    using UnityEngine.UI;
    using System.Collections;
    
    public class FattieFlow:MonoBehaviour
        {
        public GameObject modelRow;
        public GameObject modelItem;
        
        
        void Awake()
            {
            modelRow.SetActive(false);
            modelItem.SetActive(false);
            // (it's a little untidy having the model (which is inactive)
            // sibling to the real rows, so just be careful with it...)
            modelRow.transform.SetAsFirstSibling();
            
            // simple example of how you might add an item
            Invoke("_teste", 2f);
            }
        
        void _teste()
            {
            ExampleAddItem("added this");
            Flow();
            }
        public void ExampleAddItem(string label)
            {
            if (transform.childCount < 2) _addARow();
            GameObject nu = Instantiate(modelItem);
            nu.name = "dynamically created item.";
            nu.transform.SetParent(transform.GetChild(1),false);
            nu.SetActive(true);
            Canvas.ForceUpdateCanvases();
            }
        
        float screen;
        
        public void Flow()
            {
            screen = GetComponent<RectTransform>().rect.width;
            
            // move downwards any which need to be moved downwards
            int row = 0;
            while (row < transform.childCount)  // (dynamic)
                {
                if (transform.GetChild(row).gameObject.activeSelf) FlowRow(row);
                ++row;
                }
            
            // move upwards any which can be moved upwards
            row = 0;
            while (row < transform.childCount)
                {
                if (transform.GetChild(row).gameObject.activeSelf) UnflowRow(row);
                ++row;
                }
            
            // account perfectly for spacing, regardless of the user's layout
            // (the most elegant algorithm is to simply ABA)
            row = 0;
            while (row < transform.childCount)  // (dynamic)
                {
                if (transform.GetChild(row).gameObject.activeSelf) FlowRow(row);
                ++row;
                }
            
            // remove any dud rows
            }
        
        private void UnflowRow(int r)
            {
            // so where possible move any from below us, into this row
            
            if (r == transform.childCount-1) return;
            Transform thisRow = transform.GetChild(r);
            Transform nextRow = transform.GetChild(r+1);
            
            while (_nominalWidthOfFirst(nextRow) < _availableSpaceOnRight(thisRow))
                {
                Transform moveMeUp = nextRow.GetChild(0);
                moveMeUp.SetParent(thisRow, false);
                moveMeUp.SetAsLastSibling();
                Canvas.ForceUpdateCanvases();
                }
            }
        
        private float _availableSpaceOnRight(Transform someRow)
            {
            return screen - someRow.GetComponent<RectTransform>().rect.width;
            }
        
        private float _nominalWidthOfFirst(Transform someRow)
            {
            if (someRow.childCount == 0) return screen*2f;
            return someRow.GetChild(0).GetComponent<RectTransform>().rect.width;
            }
        
        private void FlowRow(int r)
            {
            Transform row = transform.GetChild(r);
            
            // it's worth noting this is an indeterminate algorithm.
            // if you're not into compsci, don't worry about this. much.
            
            while (row.GetComponent<RectTransform>().rect.width > screen)
                {
                int k = row.childCount;
                
                if (k<1) return;    // setup problem!
                if (k==1) return;   // one item is too wide for screen!
                
                Transform lastOnThisRow = row.GetChild(k-1);
                MoveToStartOf( lastOnThisRow, r+1 );
                }
            }
        
        private void MoveToStartOf(Transform item, int newRow)
            {
            while (newRow >= transform.childCount)  // may have to add a row
                _addARow();
            
            Transform moveToThisRow = transform.GetChild(newRow);
            
            item.SetParent(moveToThisRow, false);
            item.SetAsFirstSibling();
            Canvas.ForceUpdateCanvases();
            }
        
        private void _addARow()
            {
            GameObject r = Instantiate(modelRow);
            r.name = "dynamically created row.";
            r.SetActive(true);
            r.transform.SetParent(transform,false);
            Canvas.ForceUpdateCanvases();
            // just remove the model unit
            while(r.transform.childCount>0)
                {
                Debug.Log("Deleting model");
                DestroyImmediate(r.transform.GetChild(0).gameObject);
                Canvas.ForceUpdateCanvases();
                }
            }
        }
    

    The above package is a great tutorial on Unity.UI. But be sure to check out this fantastic QA: https://stackoverflow.com/a/38479097/294884