写点什么

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

作者:萧然🐳
  • 2022 年 7 月 04 日
  • 本文字数:4886 字

    阅读完需:约 16 分钟

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

实现效果: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 链接:

https://download.csdn.net/download/qq_39108767/85827448

https://download.csdn.net/download/qq_39108767/85827448

发布于: 刚刚阅读数: 3
用户头像

萧然🐳

关注

还未添加个人签名 2020.03.26 加入

还未添加个人简介

评论

发布
暂无评论
【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子_游戏开发_萧然🐳_InfoQ写作社区