-Preface-
It's been a long time since I blogged. I recently started the development of Unity. At the beginning, I was doing UI writing logic. At present, the main task is to understand the module development of Unity UI. In this chapter, let's learn about the ScrollView function that has been used more recently.
The ScrollView function in Unity is a single scrolling area, but in our daily game development, the functions required to use ScrollView are more like using List. The View is composed of repeated prefabs, and items with different contents are displayed according to different data. In fact, if you don't consider the optimization of performance and personality, I think that Unity's ScrollView is very powerful, and it can easily achieve the required functions with automatic layout.
-text-
-Implementation regardless of performance-
First, create a new ScrollView component in the scene, and Unity will automatically generate it for us as shown in the figure
Content is the root node of our element addition, we can instantiate Prefabs through GameObject and add them to Content. There is nothing to say about adding logic. Say the next two points of attention:
1. Since the scrolling content is self-adapted according to the height of the content, when we add a child node under the content, the height of the content needs to be updated. There is a way that does not require us to calculate, which is to mount the ContentSizeFitter script on the content
2. Mount the adaptive script according to the requirements, select one of GridLayoutGroup, VerticalLayoutGroup, and HorizontalLayoutGroup that is suitable for actual development, and set the parameters.
question
There is a big problem with the above method:
1. Rendering pressure: For items that are not displayed, Unity will also remember to calculate and render. Although I don’t know the UnityUI rendering steps for the time being, ScrollView is the Mask mask used. Mask is generally calculated and submitted for rendering, but only in the vertex shader. clipped in
2. Calculation pressure: If the ScrollView is a player’s backpack, there may be 1,000 pieces of backpack data. If the instance processes so much data and UI assignment at the same time in 1 and 2 frames, it will be very stuck and affect the game experience. If special circumstances do not Doing optimization generally requires adding a progress bar.
Optimization method
The optimization method for ScrollView is similar no matter what engine or language it is. It is to calculate the source data interval required to display the item in a Viewport window according to the current Bar value. The following code is written based on the x-lua framework, and only realizes vertical sliding, which means the same, as long as you can understand it.
To realize this ScrollView, we will distinguish it from the original name and name it ListView, but it is still ScrollView in its core.
1. Create ListView data
local list_db = {
-- 布局信息
Layout = {
Padding = {
Left = 10,
Right = 10,
Top = 30,
Bottom = 30
},
CellSize = {
x = 170, y = 174
},
Spacing = {
x = 10, y = 10
}
},
-- prefabs路径
PrefabsPath = false,
-- 逻辑类
LogicClass = false
}
This ListView needs to be like the Layout layout information that comes with Unity, so that it is convenient to set the coordinates when setting the item.
2. Create a ListView
-- 创建
UIListView.OnCreate = function(self, list_model)
base.OnCreate(self)
self.unity_scroll_view = UIUtil.FindComponent(self.transform, typeof(CS.UnityEngine.UI.ScrollRect))
if IsNull(self.unity_scroll_view) then
Logger.LogError("Unity Scroll View is Null-->??")
end
self.content_trans = self.unity_scroll_view.content
self.unity_scroll_bar = self.unity_scroll_view.verticalScrollbar
-- 检测item是否加载
if not GameObjectPool:GetInstance():CheckHasCached(list_model.PrefabsPath) then
GameObjectPool:GetInstance():CoPreLoadGameObjectAsync(list_model.PrefabsPath, 1)
end
self.logic_cls = list_model.LogicClass
self.render_prefabs_path = list_model.PrefabsPath
self.cache_prefabs = {}
self.list_model = list_model
__InitCalColAndRowNum(self)
end
The list_model here is an instance of the list_db above
3. Calculate the required rows and columns in the viewport
-- 计算有多少列
local function __InitCalColAndRowNum(self)
--计算列
local layout = self.list_model.Layout
local valid_width = self.content_trans.rect.width - layout.Padding.Left - layout.Padding.Right
local valid_height = self.unity_scroll_view.viewport.rect.height - layout.Padding.Top - layout.Padding.Bottom
-- 列
self.col = Mathf.Floor(valid_width / (layout.CellSize.x + layout.Spacing.x))
-- 最大实例化出来的行数,超过的动态算
self.max_row = Mathf.Ceil((valid_height + layout.Spacing.y) / (layout.CellSize.y + layout.Spacing.y))
self.valid_height = valid_height
end
Because of the Vertical direction here, the columns are fixed, and max_row is the largest row that can be accommodated in the viewport. The multiplication of this row and column is the minimum number of Prefabs required.
4. Set the data source datasource
The list is driven by data, how many items are needed after how much data is there, so here is based on the datasource passed in from the outside, here it does not matter what the data type is, but the requirement is an array.
-- 计算列表高度
local function __CalContentHeight(self)
local layout = self.list_model.Layout
local total_row = Mathf.Ceil(#self.data_source / self.col)
local height = total_row * (layout.CellSize.y + layout.Spacing.y) + layout.Padding.Top + layout.Padding.Bottom - layout.Spacing.y
self.content_trans.sizeDelta = Vector2.New(0,height)
self.total_row = total_row
end
-- 刷新list
local function __Refresh(self)
local new_index_vec = __CalDataIndexIntervalByScrollbarValue(self)
if self.index_vec == new_index_vec then
return
end
self.index_vec = new_index_vec
--__MoveAllToCache(self)
if not self.using_prefabs then
self.using_prefabs = {}
end
local layout = self.list_model.Layout
local item_index = 1
local allFromUsing = true
for index = new_index_vec.x, new_index_vec.y do
local data = self.data_source[index]
local item
if allFromUsing and #self.using_prefabs > item_index then
item = self.using_prefabs[item_index]
item_index = item_index + 1
if self.render_handler then
self.render_handler:RunWith(item,data)
end
elseif #self.cache_prefabs > 0 then
allFromUsing = false
item = table.remove(self.cache_prefabs, 1)
item:SetActive(true)
if self.render_handler then
self.render_handler:RunWith(item, data)
end
table.insert(self.using_prefabs, item)
else
allFromUsing = false
local go = GameObjectPool:GetInstance():GetLoadedGameObject(self.render_prefabs_path)
if IsNull(go) then
Logger.LogError("UIListView GetLoadedGameObject Fail-->>Path:" .. self.render_prefabs_path)
return
end
local go_trans = go.transform
go_trans:SetParent(self.content_trans)
go_trans.anchorMin = Vector2.New(0,1)
go_trans.anchorMax = Vector3.New(0,1)
go_trans.localPosition = Vector3.zero
go_trans.localScale = Vector3.one
go_trans.name = self.logic_cls.__cname .. tostring(RenderItemCnt)
RenderItemCnt = RenderItemCnt + 1
item = self:AddComponent(self.logic_cls, go)
item.transform.sizeDelta = Vector2.New(layout.CellSize.x,layout.CellSize.y)
if self.render_handler then
self.render_handler:RunWith(item, data)
end
item:SetActive(true)
table.insert(self.using_prefabs, item)
end
item.transform.anchoredPosition = Vector2.New(__CalPositionByIndex(self,index))
end
if allFromUsing and #self.using_prefabs > item_index then
for i = item_index, #self.using_prefabs do
local tmp_item = self.using_prefabs[item_index]
table.remove(self.using_prefabs,item_index)
table.insert(self.cache_prefabs,tmp_item)
end
end
end
First, calculate the height of the content that needs to be set through the array length of the datasource. The function is similar to ContentSizeFitter, but here is calculated by yourself. Next, refresh the list. First, you need to calculate the starting point index and ending point index of the datasource according to the value of the current scrollbar. The calculation method is as follows
local function __CalDataIndexIntervalByScrollbarValue(self)
local start_row = Mathf.Floor((1 - self.scroll_value) * self.total_row)
local end_row = start_row + self.max_row
local start_index = (start_row - 3) * self.col + 1
local end_index = end_row * self.col
if start_index < 1 then
start_index = 1
end
if end_index > #self.data_source then
local diff = end_index - #self.data_source
end_index = #self.data_source
start_index = start_index - diff
if start_index < 1 then
start_index = 1
end
end
return Vector2.New(start_index,end_index)
end
First of all, we know how many lines are needed in total. According to the value, we can get how many lines are currently in, plus how many lines are needed for the viewport content, and convert it to index.
5. Calculate the coordinates according to the index of the data
-- 通过index计算坐标
local function __CalPositionByIndex(self,index)
local layout = self.list_model.Layout
local col = (index - 1) % self.col
local row = Mathf.Floor((index - 1) / self.col)
local x = col * (layout.CellSize.x + layout.Spacing.x) + layout.Padding.Left + layout.CellSize.x / 2
local y = row * (layout.CellSize.y + layout.Spacing.y) + layout.Padding.Top + layout.CellSize.y / 2
y = y * -1
return x,y
end
Here you need to use the layout data in the previous list_db to set the specific coordinates
6. Callback rendering
After setting, you can call back to the holder of the ListView through a render function set before, tell it what item I use and what data, and let it use this data and item object to do some logic.
over~
I have only just learned Unity for a short time, and I am still exploring. If you have any questions, please correct me, thank you~