完成作用:ScrollRect,格子动态缩放大小,滑动完毕主动定位中心格子

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

使用说明:

现在只支撑横向从左往右列表,需求设置ScrollRect仅横向滑动,ScrollViewExtras挂到ScrollRect组件地点节点,ScrollRect的格子锚点、中心点设置在左上角,Content,设置好Y坐标,运行时主动修正X坐标。

GridSpace:格子横向间隔

IsSnap:是否敞开主动定位

IsScale:是否敞开主动缩放

ScaleCueve:缩放曲线,让缩放作用更流通

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

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

Todo

  1. 现在只支撑横向从左往右滑动,待扩展同时支撑横向左右、纵向上下4个方向,通过配置参数挑选滑动方向。
  2. 支撑恣意方向滑动时,需求动态设置格子锚点的方位。
  3. 现仅支撑主动定位到屏幕屏幕中心,待扩展为恣意指定方位。
  4. 优化主动定位功用,让定位期间的格子移动更加丝滑流通。

作用演示:

封闭主动定位,封闭主动缩放:

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

敞开主动定位,封闭主动缩放:

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

封闭主动定位,敞开主动缩放:

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

敞开主动定位,敞开主动缩放:

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

完成原理:

  1. 动态缩放功用完成:判别Transform改动时,核算列表可视区域内所有格子与列表中心的偏移量,依据偏移量缩放格子,忽略可视区域外的格子,减少不必要的核算量。
  2. 主动定位功用完成:初始化核算每个格子的方位,每次滑动完毕后,核算每个格子到列表中心的偏移量,偏移量最小的格子就是列表最中心的格子,再把它主动移动到列表中心的方位,完成定位。

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

完成代码:

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链接:

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

我正在参加技术社区创作者签约方案招募活动,点击链接报名投稿。