Possibility of scroll list with fixed headers?

Hello all,

I was wondering how possible it is to make a scroll list with a header at the top of the scroll list. The point of the header is essentially to act like a catergory or grouping for the scroll list objects under the header. The only example I know of is in the iPhone. If you look in the contacts area at the top there is a header with a letter(‘A’, ‘B’, ‘C’, etc, etc) The top letter, which should be ‘A’, gets bumped up when you enter the ‘B’ section of the scroll list so then the A disappears and now ‘B’ is the header and fixed at the top of the scroll list. Then as ‘C’ starts to come into view and as it scrolls to the top ‘C’ will replace the ‘B’ and of course if you start scrolling up (if you were at c) ‘C’ will be bumped down and ‘B’ will scroll in from the top of the scroll view and ‘C’ just scrolls down like a regular scroll list object.

I tried starting to implement something like this but I only got as far as the scroll list registering that a new header scroll list object is in the header position(doesnt do anything but print a debug statement stating that the object is at the top of the list) and determining which direction the list is being dragged. (I’d imagine I would need that for the diffecnce of ‘B’ bumping up ‘A’ and ‘B’ bumping down ‘C’) I think my biggest problem is having the header scroll object fixed at the top of the scroll list. I’m about to attempt to write some code to do that but I’m not exactly sure how.

I should mention I am using the EZGUI UIScrollList as a base for this. I already figured out I would have to inherit the UIScrollList into a new scroll list class to get this certain functionality.

Thanks for any suggestions and help! And if I wrote anything that isn’t clear let me know and I’ll clear it up. Or if you need more explanation please let me know and I’ll try to clarify.

-Ethan

EDIT: Here’s a link to a video showing the scroll list functionality I want. https://www.youtube.com/watch?v=eikTReAyXBc

EDIT2: Here’s my current issue (Speed/velocity of scroll list? - Questions & Answers - Unity Discussions) and blocking me from moving forward with this implementation. I’m really close I can taste it! I just need the speed.

EDIT3: I have successfully made a header scroll list with the exception of 1 problem. If a user was to swipe too fast when there are two headers at the top, the top header box collider couldn’t register that a box collider left, leaving the top header section empty(which is a major issue with a header based scroll list). I will now attempt to implemented robertbu’s answer.

I gave you problem a hard think this afternoon during a long drive, and came up with a method that’s not too bad. The normal entries in the list would be UIListButtons. The Headers would be UIListContainers with each having a UIListButton as a child. This would allow the code to move the header UIListButtons through their localPosition while leaving the parents in place. This solution does not require parallel graphics as I suggest you use above. Here is a lightly tested body of code that implements the idea. I’m sure there are improvements to be made, but it is a proof of concept. Note most of the code is setting up the list. The code that handles the header movement (or non-movement) is small.

// Note:  This script must execute after EZGUI List movement code.
//   Use Edit/Project Settings/Script Execution Order if you need it.

using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class HeaderList : MonoBehaviour {
	public TextAsset ta;              // Text file with a sorted list of strings
	public UIScrollList uisl;         // Scroll list
	public GameObject goPrefabEntry;  // Prefab for an entry
	public GameObject goPrefabHeader; // Prefab for a header
	
	private Vector3 v3Header;         // Position of the header of the list
	
	private List<Transform> artrHeader = new List<Transform>();
	
	private int iCurr = 0;             // Index of the current header being used.
	private float fHeight;             // Height of a header
	
	void Start() {
		BuildList();
		
		uisl.clipContents = false;     // Doesn't work with clipping turned on
		v3Header   = uisl.transform.position;  // Figure the position of the header
		v3Header.y = v3Header.y + uisl.viewableArea.y / 2.0f - fHeight / 2.0f;
		v3Header.z -= 0.2f;		
	}
	
	// Builds all the headers and entries for the scroll list
	void BuildList() {
		
		Transform tr = new GameObject().transform;        // Insert a blank/dummy header in the 
	    Transform trChild = new GameObject().transform;   //   beginning of the header list to 
		trChild.parent = tr;                              //   remove a boundry condition
		Vector3 v3 = Vector3.zero;
		v3.y = 10000;
	    tr.position = v3;
	    artrHeader.Add(trChild);
		
		// Parse the text file of sorted strings into a string array
		string[] arst = ta.text.Split(new char[] {'\r','

'},StringSplitOptions.RemoveEmptyEntries);

		// Insert the top header
	    char ch = 'A';
		InsertHeader (ch);
		
		// Process the sorted list of strings
		for (int i = 0; i < arst.Length; i++) {
			if (arst*[0] != ch) {  // If the first letter changed, insert a new one header*

_ ch = arst*[0];_
_
InsertHeader(ch);_
_
}*_

* // Insert a new entry.*
* GameObject go = Instantiate(goPrefabEntry) as GameObject;*
* UIListButton uilb = go.GetComponent();*
_ uilb.Text = arst*;
uilb.Data = arst;
uilb.scriptWithMethodToInvoke = this;
uilb.methodToInvoke = “DoClick”;
uisl.AddItem (go);
}*_

* tr = new GameObject().transform; // Insert a blank/dummy header at the*
* trChild = new GameObject().transform; // end of the header list to*
* trChild.parent = tr; // remove a boundry condition*
* v3 = Vector3.zero;*
* v3.y = -10000;*
* tr.position = v3;*
* artrHeader.Add(trChild);*
* }*

* // Inserts a header into the list. Note a header consists of an*
* // empty game object witha UIListItemContainer component and the*
* // actual UIListButton is a child of that object (with a*
* // localPosition of (0,0,0).*
* void InsertHeader(char ch) {*
* GameObject goParent = new GameObject();*
* goParent.AddComponent();*
* GameObject go = Instantiate(goPrefabHeader) as GameObject;*
* UIListButton uilb = go.GetComponent();*
* fHeight = uilb.height;*
* uilb.Text = ch.ToString();*
* go.transform.parent = goParent.transform;*
* uisl.AddItem (goParent);*
* artrHeader.Add (go.transform);*
* }*

* // This code needs to be executed last. You may need to modify the*
* // order of the script executions to make sure this executes last*
* void LateUpdate() {*

* // Put the header we modified last frame back in place*
* artrHeader[iCurr].localPosition = Vector3.zero;*

* // Find the header we should use. Note I rescan the*
* // list because a fast swipe might go past more than one*
* // header in a frame.*
* iCurr = 0;*
* for (int i = 0; i <= artrHeader.Count; i++) {*
_ if (artrHeader*.parent.position.y < v3Header.y)
break;
iCurr++;
}
iCurr–;*_

* // Put the header that we should use in the header position.*
* artrHeader[iCurr].localPosition = artrHeader[iCurr].InverseTransformPoint(v3Header);*

* // If the next header is pushing the current header, adjust the current header’s*
* // position.*
* if (artrHeader[iCurr+1].parent.position.y + fHeight >= v3Header.y) {*
* Vector3 v3 = artrHeader[iCurr].localPosition;*
* v3.y += (artrHeader[iCurr+1].parent.position.y + fHeight) - v3Header.y;*
* artrHeader[iCurr].localPosition = v3;*

* }*
* }*

* // Process a mouse click on an entry*
* void DoClick() {*
* string st = (string)uisl.LastClickedControl.Data;*
* Debug.Log (st);*
* }*
}

I find a creative soution, put a scrollview as a header and anther scrollview for your content and syncronize the scrollview setting the value of the scrollbar of the header from the scrollbar of the content

Simpler solution. Place the header outside the ScrollView, pivot must to be (0,1). Then the header should have a script like this:

public class HeadScrollViewController : MonoBehaviour
{
    [SerializeField] private RectTransform content;
    private RectTransform _rTrans;

    private void Start()
    {
        _rTrans = transform.GetComponent<RectTransform>();
    }

    private void Update()
    {
        _rTrans.anchoredPosition = new Vector2(content.anchoredPosition.x, 0);
    }
}

And don’t forget to move the scrollview mask higher in the hierarchy so that the header is included in it.