当前位置:网站首页>[unity ugui] scrollrect dynamically scales the grid size and automatically locates the middle grid
[unity ugui] scrollrect dynamically scales the grid size and automatically locates the middle grid
2022-07-04 17:30:00 【InfoQ】
![null](https://static001.geekbang.org/infoq/00/00df0058907fc3d9b44f97e5e415a808.gif)
Instructions :
![null](/img/d1/5a81ca36150295dc91ee4dc83c4e41.png)
![null](/img/0d/a8f4424add7785375741bac4f0b802.png)
Effect demonstration :
![null](https://static001.geekbang.org/infoq/fe/fe5eb384e697954529180cb42c251258.gif)
![null](https://static001.geekbang.org/infoq/2b/2b2f1693b367444f535bb01de2df1366.gif)
![null](https://static001.geekbang.org/infoq/bf/bf0e47094d851706e7783f77e5c25785.gif)
![null](https://static001.geekbang.org/infoq/c3/c3649caeaeb8bfe3e04be02a555bba65.gif)
Realization principle :
![null](https://static001.geekbang.org/infoq/90/90c226d799cda9bb5f59be1f7461f11c.gif)
Implementation code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
[RequireComponent(typeof(ScrollRect))]
public class ScrollViewExtras : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
private enum SnapState
{
None,
Inertia,
Reverse,
}
public Action OnScrollStartDrag;
public Action<int> OnScrollEndDrag;
public Action<int> OnSelectGridChanged;
[SerializeField] private float gridSpace = 20;
[SerializeField] private bool isSnap;
[SerializeField] private bool isScale;
[SerializeField] private AnimationCurve scaleCurve = new AnimationCurve(new Keyframe(0,1),new Keyframe(1,0.5f));
// Initialize invariant fields
private ScrollRect scrollRect;
private RectTransform contentRectTrans;
private Vector2 scrollHalfSize; // List size
private Vector2 gridSize;
private List<RectTransform> gridList = new List<RectTransform>();
private List<Vector2> gridCenterList = new List<Vector2>();
private float snapDecelerate;
private const float snapReverseSpeed = 500;
private bool isInited = false;
// Dynamically changing fields
private SnapState snapState = SnapState.None;
private int curSelectIndex;
//----------
private void Start()
{
Init();
}
private void Update()
{
if(isInited)
{
switch(snapState)
{
case SnapState.Inertia:
UpdateSnapInertia();
break;
case SnapState.Reverse:
UpdateSnapReverse();
break;
default:
break;
}
if(contentRectTrans.hasChanged)
{
if(isScale)
UpdateScale();
else
UpdateSelectGrid();
}
}
}
#region --- Drag Event
public void OnBeginDrag(PointerEventData eventData)
{
OnScrollStartDrag?.Invoke();
BreakSnap();
}
public void OnDrag(PointerEventData eventData)
{
}
public void OnEndDrag(PointerEventData eventData)
{
StartSnap();
}
#endregion
public void Init()
{
scrollRect = GetComponent<ScrollRect>();
if(!scrollRect.horizontal)
Debug.LogError(" Currently, only horizontal lists from left to right are supported ");
contentRectTrans = scrollRect.content;
if(contentRectTrans.pivot.x > 0)
Debug.LogError(" Currently, only horizontal lists from left to right are supported ");
//scrollCenter = scrollRect.viewport.rect.center;
scrollHalfSize = scrollRect.viewport.rect.size * 0.5f;
for(int i = 0; i < contentRectTrans.childCount; i++)
{
gridList.Add(contentRectTrans.GetChild(i) as RectTransform);
}
if(gridList.Count > 0)
gridSize = gridList[0].rect.size;
snapDecelerate = scrollRect.decelerationRate;
//if(snapDecelerate < 0.1f)
// snapDecelerate = 0.1f;
if(gridList.Count == 0)
return;
// The first grid coordinate
Vector2 gridInitPos = gridList[0].anchoredPosition;
gridInitPos.x = scrollHalfSize.x - gridSize.x * 0.5f;
// Grid spacing
Vector2 gridOffset = Vector2.zero;
gridOffset.x = gridSize.x + gridSpace;
// Calculate the canvas size
Vector2 contentSize = contentRectTrans.rect.size;
contentSize.x = gridSize.x * gridList.Count + gridSpace * (gridList.Count - 1) + scrollHalfSize.x * 2 - gridSize.x;
// Set the canvas size
contentRectTrans.sizeDelta = contentSize;
contentRectTrans.anchoredPosition = new Vector2(0,contentRectTrans.anchoredPosition.y);
// Set the coordinates of each grid
for(int i = 0; i < gridList.Count; i++)
{
gridList[i].anchoredPosition = gridInitPos + gridOffset * i;
gridList[i].anchorMin = new Vector2(0,1);
gridList[i].anchorMax = new Vector2(0,1);
gridList[i].pivot = new Vector2(0,1);
gridCenterList.Add(gridList[i].anchoredPosition + gridSize * 0.5f);
}
if(isScale)
UpdateScale();
isInited = true;
curSelectIndex = 0;
}
#region --- Snap ---
private Vector2 snapTargetPos;
private void StartSnap()
{
if(isSnap)
{
if(gridList.Count > 0)
{
snapState = SnapState.Inertia;
}
}
}
private void UpdateSnapInertia()
{
if(scrollRect.velocity.x > -snapReverseSpeed && scrollRect.velocity.x < snapReverseSpeed)
{
// reverse
StartSnapReverse();
return;
}
}
private void StartSnapReverse()
{
snapState = SnapState.Reverse;
scrollRect.StopMovement();
// Canvas coordinates of the center of the current screen
float centerPos = Mathf.Abs(contentRectTrans.anchoredPosition.x) + scrollHalfSize.x;
float temOffset;
float minOffet = float.MaxValue;
for(int i = 0; i < gridCenterList.Count; i++)
{
if(!gridList[i].gameObject.activeSelf)
continue;
// Grid center coordinates
temOffset = centerPos - gridCenterList[i].x;
// Compare the minimum distance
if(Mathf.Abs(temOffset) < Mathf.Abs(minOffet))
{
minOffet = temOffset;
// The grid is in the middle , Reverses the coordinates of the canvas
snapTargetPos.x = -(gridCenterList[i].x - scrollHalfSize.x);
}
}
snapTargetPos.y = contentRectTrans.anchoredPosition.y;
}
private void UpdateSnapReverse()
{
if(Mathf.Abs(contentRectTrans.anchoredPosition.x - snapTargetPos.x) < 1)
{
contentRectTrans.anchoredPosition = snapTargetPos;
EndSnap();
return;
}
contentRectTrans.anchoredPosition = Vector2.Lerp(contentRectTrans.anchoredPosition,snapTargetPos,snapDecelerate);
}
private void EndSnap()
{
if(snapState == SnapState.None)
return;
scrollRect.StopMovement();
snapState = SnapState.None;
if(isScale)
UpdateScale();
OnScrollEndDrag?.Invoke(curSelectIndex);
}
private void BreakSnap()
{
if(snapState != SnapState.None)
snapState = SnapState.None;
}
#endregion
#region --- Scale ---
int tempIndex;
float tempCenter;
float tempOffset;
float minDistance;
Vector3 tempScale;
Vector2 tempAnPos;
private void UpdateScale()
{
minDistance = float.MaxValue;
tempCenter = Mathf.Abs(contentRectTrans.anchoredPosition.x) + scrollHalfSize.x;
for(int i = 0; i < gridCenterList.Count; i++)
{
if(!gridList[i].gameObject.activeSelf)
continue;
// Distance from grid center to screen center
tempOffset = Mathf.Abs(tempCenter - gridCenterList[i].x);
if(tempOffset > scrollHalfSize.x + gridSize.x)
continue;
// Calculate the scaling value
tempScale.x = scaleCurve.Evaluate(tempOffset / scrollHalfSize.x);
tempScale.y = tempScale.x;
tempScale.z = 1;
// Modify zoom
gridList[i].localScale = tempScale;
// Change location ( The anchor is in the upper left corner , Make sure the grid is still in the middle after scaling )
tempAnPos.x = gridCenterList[i].x - gridSize.x * 0.5f * tempScale.x;
tempAnPos.y = gridCenterList[i].y + gridSize.y * (0.5f * tempScale.y - 1);
gridList[i].anchoredPosition = tempAnPos;
// Compare the minimum distance
if(tempOffset < minDistance)
{
minDistance = tempOffset;
tempIndex = i;
}
}
if(curSelectIndex != tempIndex)
{
curSelectIndex = tempIndex;
OnSelectGridChanged?.Invoke(curSelectIndex);
}
}
private void UpdateSelectGrid()
{
minDistance = float.MaxValue;
tempCenter = Mathf.Abs(contentRectTrans.anchoredPosition.x) + scrollHalfSize.x;
for(int i = 0; i < gridCenterList.Count; i++)
{
if(!gridList[i].gameObject.activeSelf)
continue;
// Distance from grid center to screen center
tempOffset = Mathf.Abs(tempCenter - gridCenterList[i].x);
if(tempOffset > scrollHalfSize.x + gridSize.x)
continue;
// Compare the minimum distance
if(tempOffset < minDistance)
{
minDistance = tempOffset;
tempIndex = i;
}
}
if(curSelectIndex != tempIndex)
{
curSelectIndex = tempIndex;
OnSelectGridChanged?.Invoke(curSelectIndex);
}
}
#endregion
}
Demo link :
边栏推荐
- 解读数据安全治理能力评估框架2.0,第四批DSG评估征集中
- kaili不能输入中文怎么办???
- 一加10 Pro和iPhone 13怎么选?
- The test experience "tortured" by the PMP test is worth your review
- S2b2b solution for lighting industry: efficiently enable the industrial supply chain and improve the economic benefits of enterprises
- Years of training, towards Kata 3.0! Enter the safe container experience out of the box | dragon lizard Technology
- Solution du système de gestion de la chaîne d'approvisionnement du parc logistique intelligent
- 整理混乱的头文件,我用include what you use
- MD5加密的两种方式
- 安信证券网上开户安全吗 开户收费吗
猜你喜欢
一加10 Pro和iPhone 13怎么选?
OPPO小布推出预训练大模型OBERT,晋升KgCLUE榜首
Visual studio 2019 (localdb) mssqllocaldb SQL Server 2014 database version is 852 and cannot be opened. This server supports 782
开发者,MySQL专栏完更,助你轻松从安装到入门进阶
KS007基于JSP实现人个人博客系统
How to implement a delay queue?
整理混乱的头文件,我用include what you use
第十八届IET交直流输电国际会议(ACDC2022)于线上成功举办
Load test practice of pingcode performance test
The company needs to be monitored. How do ZABBIX and Prometheus choose? That's the right choice!
随机推荐
居家打工年入800多万,一共五份全职工作,他还有时间打游戏
kaili不能输入中文怎么办???
智慧物流园区供应链管理系统解决方案:数智化供应链赋能物流运输行业供应链新模式
雨量预警广播自动化数据平台BWII 型广播预警监测仪
第十八届IET交直流输电国际会议(ACDC2022)于线上成功举办
[acwing] 58 weeks 4490 dyeing
Chow Tai Fook fulfills the "centenary commitment" and sincerely serves to promote green environmental protection
PingCode 性能测试之负载测试实践
【模板】【luogu P4630】Duathlon 铁人两项(圆方树)
leetcode:421. The maximum XOR value of two numbers in the array
基于wifi控制的51单片机温度报警器
Implementation of super large-scale warehouse clusters in large commercial banks
Analysis of abnormal frequency of minor GC in container environment
图像检索(image retrieval)
Pytorch deep learning quick start tutorial
昆明三环闭合工程将经过这些地方,有在你家附近的吗?
Vb无法访问数据库stocks
Go language loop statement (under Lesson 10)
安信证券排名 网上开户安全吗
安信证券手机版下载 网上开户安全吗