【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子
  zHVImrqJzxjl 2023年11月02日 164 0


 实现效果:ScrollRect,格子动态缩放大小,滑动结束自动定位中间格子 

【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_UGUI【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_Unity_02

使用说明:

目前只支持横向从左往右列表,需要设置ScrollRect仅横向滑动,ScrollViewExtras挂到ScrollRect组件所在节点,ScrollRect的格子锚点、中心点设置在左上角,Content,设置好Y坐标,运行时自动修改X坐标。

GridSpace:格子间距

IsSnap:是否开启自动定位

IsScale:是否开启自动缩放

ScaleCueve:缩放曲线

【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_ScrollView_03【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_Unity_04

【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_UGUI_05【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_Unity_06

 效果演示:

关闭自动定位,关闭自动缩放:

【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_ScrollView_07【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_Unity_08

开启自动定位,关闭自动缩放:

 【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_游戏开发_09【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_Unity_10

关闭自动定位,开启自动缩放:

 【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_ScrollView_11【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_游戏开发_12

开启自动定位,开启自动缩放:

【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_UGUI_13【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_Unity_14

实现原理:

  1. 动态缩放功能实现:Transform改变时,计算列表可视区域内所有格子与列表中心的偏移量,根据偏移量缩放格子。
  2. 自动定位功能实现:初始化计算每个格子的位置,滑动结束后,计算每个格子到列表中心的偏移量,偏移量最小的格子就是列表最中间的格子,再把它自动移动到列表中心的位置,实现定位。

【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_UGUI_15【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_UGUI_16

实现代码:

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));

//初始化不变字段

private ScrollRect scrollRect;
private RectTransform contentRectTrans;
private Vector2 scrollHalfSize; //列表尺寸

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;

//动态变化字段

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("目前只支持横向从左往右列表");

contentRectTrans = scrollRect.content;
if(contentRectTrans.pivot.x > 0)
Debug.LogError("目前只支持横向从左往右列表");

//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;

//第一个格子坐标
Vector2 gridInitPos = gridList[0].anchoredPosition;
gridInitPos.x = scrollHalfSize.x - gridSize.x * 0.5f;

//格子间隔
Vector2 gridOffset = Vector2.zero;
gridOffset.x = gridSize.x + gridSpace;

//计算画布尺寸
Vector2 contentSize = contentRectTrans.rect.size;
contentSize.x = gridSize.x * gridList.Count + gridSpace * (gridList.Count - 1) + scrollHalfSize.x * 2 - gridSize.x;

//设置画布尺寸
contentRectTrans.sizeDelta = contentSize;
contentRectTrans.anchoredPosition = new Vector2(0,contentRectTrans.anchoredPosition.y);

//设置每个格子坐标
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)
{
//反向
StartSnapReverse();
return;
}
}
private void StartSnapReverse()
{
snapState = SnapState.Reverse;
scrollRect.StopMovement();

//当前屏幕中心的画布坐标
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;
//格子中心坐标
temOffset = centerPos - gridCenterList[i].x;
//比较最小距离
if(Mathf.Abs(temOffset) < Mathf.Abs(minOffet))
{
minOffet = temOffset;
//格子在中间,反推画布的坐标
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;
//格子中心到屏幕中心距离
tempOffset = Mathf.Abs(tempCenter - gridCenterList[i].x);
if(tempOffset > scrollHalfSize.x + gridSize.x)
continue;
//计算缩放值
tempScale.x = scaleCurve.Evaluate(tempOffset / scrollHalfSize.x);
tempScale.y = tempScale.x;
tempScale.z = 1;
//修改缩放
gridList[i].localScale = tempScale;
//修改位置(锚点在左上角,保证缩放后格子仍然在中间)
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;
//比较最小距离
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;
//格子中心到屏幕中心距离
tempOffset = Mathf.Abs(tempCenter - gridCenterList[i].x);
if(tempOffset > scrollHalfSize.x + gridSize.x)
continue;
//比较最小距离
if(tempOffset < minDistance)
{
minDistance = tempOffset;
tempIndex = i;
}
}
if(curSelectIndex != tempIndex)
{
curSelectIndex = tempIndex;
OnSelectGridChanged?.Invoke(curSelectIndex);
}
}

#endregion

}


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

zHVImrqJzxjl