Just like we broke down the logic for determining the size and relative position of cells, we can also break down the logic for their flow. Should they be arranged from top-to-bottom, or bottom-to-top? How about right-to-left or left-to-right? In this lesson we will create a new interface so that we can easily support any of these options.
IFlow
The code below is taken from the “IFlow.cs” script (located in “Scripts/Common/UI/TableView/IFlow/”):
using UnityEngine; using System.Collections; namespace TableViewHelpers { public interface IFlow { void ConfigureCell (TableViewCell cell, int index); Point GetVisibleCellRange (); Vector2 GetCellOffset (int index); Vector2 GetCellOffset (int index, int padding); } }
The first method, “ConfigureCell” will take as parameters a reusable cell which is about to be displayed, and the corresponding index for its data. This step of the process is one step closer to actually putting something on screen, so I need to take the abstract idea of cell “sizes” and turn it into something real like a “width” or “height”. In addition, I customize the “RectTransform” component’s anchor and pivot settings so that I can reuse the same cell regardless of the chosen flow’s direction. It may not make much sense to reuse a vertical table view cell in a new horizontal table view, but if you were only changing a flow direction along the same axis, it should probably be just fine.
Next we have another method, “GetVisibleCellRange”. This sounds a lot like one of the methods we implemented in the “ISpacer” interface. In fact, the implementation will use the spacer to determine the returned value. Before we can call the spacer’s method, we must determine the values to pass along. The different flows are also setup with different anchor and pivot settings on the scroll rect’s content. Because of this, determining the actual start and end of the screen range is determined differently for each.
The final two methods are basically the same, but I have provided an overloaded option. The method “GetCellOffset” is responsible for taking the generic position information from the spacer and turning it into a Vector2 which can be used to position a RectTransform component. The overloaded version of the method is used for the animated removal or insertion of cells. It makes it easy to get an “old” position for a cell so that I know the “from” and “to” positions needed during animation.
Implementations
The implementations for each of the four different flows have nearly identical code. There are only subtle differences like using values in the ‘X’ vs ‘Y’ or using a negative value instead of a positive one. Because they are so similar, I will only show and discuss the first. Feel free to review the other three which are included in the project on your own.
You can open and review the completed “TopToBottom.cs” script from the project, but I will point out and comment on several code snippets here:
ScrollRect ScrollRect; ISpacer Spacer; int ViewHeight; public TopToBottom (ScrollRect scrollRect, ISpacer spacer) { ScrollRect = scrollRect; Spacer = spacer; RectTransform rt = scrollRect.transform as RectTransform; ViewHeight = Mathf.CeilToInt(rt.sizeDelta.y); }
Each of the implementations look similar. They all have a reference to the “ScrollRect” component, the currently implemented “Spacer” and a measurement of either the Width or Height of the ScrollRect – whichever is relevant. Only the “ScrollRect” and “Spacer” are passed to the constructor, and the Width or Height is determined by using the reference.
public void ConfigureCell (TableViewCell cell, int index) { RectTransform rt = cell.transform as RectTransform; rt.anchorMin = new Vector2(0, 1); rt.anchorMax = new Vector2(1, 1); rt.pivot = new Vector2(0, 1); int height = Spacer.GetSize(index); rt.sizeDelta = new Vector2( 0, height ); cell.SetShowAnchors(TextAnchor.UpperLeft, TextAnchor.UpperLeft); cell.SetHideAnchors(TextAnchor.UpperLeft, TextAnchor.UpperRight); }
Each flow will provide slightly different configurations for a cell’s RectTransform anchor and pivot settings. In this version, I have set the anchor on ‘X’ to cover the full width of the parent because it will be scrolling vertically, and therefore it is only the height which should vary. The spacer is able to provide the varying size needed to finish setup.
Next we configure the cell’s panel positions which affect how the panel will know how to position itself and which are important for animation, in particular when adding and removing cells from the table view.
public Point GetVisibleCellRange () { int startY = Mathf.RoundToInt(ScrollRect.content.anchoredPosition.y); int endY = startY + ViewHeight; return Spacer.GetVisibleCellRange(startY, endY); }
In “GetVisibleCellRange” we use the anchored position of the “ScrollRect” content to determine where the true “viewed” area begins and then either the width or height added to that to determine where the “viewed” area ends. These parameters are then passed to the spacer and the cell range can finally be determined.
public Vector2 GetCellOffset (int index) { return GetCellOffset(index, 0); }
This “GetCellOffset” method calls the overloaded version so that we really only have one implementation that needs to be maintained. This works because we simply pass in a padding of zero which causes the padding to have no effect.
public Vector2 GetCellOffset (int index, int padding) { int position = Spacer.GetPosition(index) + padding; return new Vector2(0, -position); }
The real implementation for “GetCellOffset” takes the position information from the spacer and maps it to the correct axis – either ‘X’ or ‘Y’ on a Vector2 which can be used to position the cell.
Summary
In Part 4 (of 6) we focused on how to begin converting the abstract “size” information of a spacer into a real world “measurement” such as a position along an ‘X’ or ‘Y’ axis or as the “width” or “height” for a cell. We added a new interface which has four implementation classes, but each of the classes looks almost exactly the same – we simply swap ‘X’ for ‘Y’ or “width” for “height” as is relevant to the current flow. Because of the similarity we only reviewed the first, and I will leave it up to you to review the other three from the project on your own.