【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子
作者:萧然🐳
- 2022 年 7 月 04 日
本文字数:4886 字
阅读完需:约 16 分钟

实现效果:ScrollRect,格子动态缩放大小,滑动结束自动定位中间格子
使用说明:
目前只支持横向从左往右列表,需要设置 ScrollRect 仅横向滑动,ScrollViewExtras 挂到 ScrollRect 组件所在节点,ScrollRect 的格子锚点、中心点设置在左上角,Content,设置好 Y 坐标,运行时自动修改 X 坐标。
GridSpace:格子间距
IsSnap:是否开启自动定位
IsScale:是否开启自动缩放
ScaleCueve:缩放曲线
效果演示:
关闭自动定位,关闭自动缩放:
开启自动定位,关闭自动缩放:
关闭自动定位,开启自动缩放:
开启自动定位,开启自动缩放:
实现原理:
动态缩放功能实现:Transform 改变时,计算列表可视区域内所有格子与列表中心的偏移量,根据偏移量缩放格子。自动定位功能实现:初始化计算每个格子的位置,滑动结束后,计算每个格子到列表中心的偏移量,偏移量最小的格子就是列表最中间的格子,再把它自动移动到列表中心的位置,实现定位。
实现代码:
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 }复制代码
Demo 链接:
划线
评论
复制
发布于: 刚刚阅读数: 3
版权声明: 本文为 InfoQ 作者【萧然🐳】的原创文章。
原文链接:【http://xie.infoq.cn/article/d43b5c5339392122b7fce75a0】。文章转载请联系作者。
萧然🐳
关注
还未添加个人签名 2020.03.26 加入
还未添加个人简介










评论