unity ScrollRect列表自动居中

代码环节

    public enum ScrollDir
    {
        Horizontal,
        Vertical
    }

    // 列表元素自动居中
    public class ListChildAutoCenter : MonoBehaviour, IEndDragHandler, IDragHandler, IBeginDragHandler
    {
        public ScrollDir Dir = ScrollDir.Horizontal;

        [Header("对齐中点速度")]
        public float ToCenterTime = 0.3f;
        [Header("中心点放大倍数")]
        public float CenterScale = 1f;
        [Header("非中心点放大倍数")]
        public float UnCenterScale = 1f;
        private ScrollRect _scrollView;

        private Transform _content;
        //private RectTransform _content_recttsf;
        private List<float> _childrenPos = new List<float>();   //各个Item处于中心点时候的content的位置
        private float _targetPos;

        private bool execute = false;

        private Action<int> dragEndCallback;
        #region 测试
        //public Button btn;
        //public Button btn2;
        //public int index = 0;
        #endregion

        /// <summary>
        /// 当前中心child索引
        /// </summary>
        private int _curCenterChildIndex = -1;

        private void Awake()
        {
            //btn.onClick.AddListener(() =>
            //{
            //    InitList();
            //});
            //btn2.onClick.AddListener(() =>
            //{
            //    if (!execute) return;
            //    SkipIndex(index);
            //});
            //InitList(0, (index) =>
            //{
            //    SkipIndex(index);
            //});
        }

        /// <summary>
        /// 指定Index居中
        /// </summary>
        public void SkipIndex(int index)
        {
            _curCenterChildIndex = index;
            _targetPos = _childrenPos[index];
            switch (Dir)
            {
                case ScrollDir.Horizontal:
                    _content.DOLocalMoveX(_targetPos, ToCenterTime);
                    break;
                case ScrollDir.Vertical:
                    _content.DOLocalMoveY(_targetPos, ToCenterTime);
                    break;
            }
            SetCellScale();
        }


        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="index">初始化定位的index</param>
        /// <param name="callback">滑动结束后的回调</param>
        public void InitList(int index, Action<int> callback)
        {
            _childrenPos.Clear();
            _scrollView = GetComponent<ScrollRect>();
            if (_scrollView == null)
            {
                Debug.LogError("ScrollRect is null");
                return;
            }
            _content = _scrollView.content;

            LayoutGroup layoutGroup = null;
            layoutGroup = _content.GetComponent<LayoutGroup>();
            //_content_recttsf = _content.GetComponent<RectTransform>();
            if (layoutGroup == null)
            {
                Debug.LogError("LayoutGroup component is null");
            }
            _scrollView.movementType = ScrollRect.MovementType.Unrestricted;
            float spacing = 0f;
            //根据dir计算坐标,Horizontal:存x,Vertical:存y
            switch (Dir)
            {
                case ScrollDir.Horizontal:
                    if (layoutGroup is HorizontalLayoutGroup)
                    {
                        float childPosX = _scrollView.GetComponent<RectTransform>().rect.width * 0.5f - GetChildItemWidth(0) * 0.5f;
                        spacing = (layoutGroup as HorizontalLayoutGroup).spacing;
                        _childrenPos.Add(childPosX);
                        for (int i = 1; i < _content.childCount; i++)
                        {
                            childPosX -= GetChildItemWidth(i) * 0.5f + GetChildItemWidth(i - 1) * 0.5f + spacing;
                            _childrenPos.Add(childPosX);
                        }
                    }
                    else if (layoutGroup is GridLayoutGroup)
                    {
                        GridLayoutGroup grid = layoutGroup as GridLayoutGroup;
                        //获取到中心点的位置,也是第一个cell处于中心点时候的位置
                        var aa = _scrollView.GetComponent<RectTransform>().rect.width;
                        float childPosX = _scrollView.GetComponent<RectTransform>().rect.width * 0.5f - grid.cellSize.x * 0.5f;
                        _childrenPos.Add(childPosX);
                        for (int i = 0; i < _content.childCount - 1; i++)
                        {
                            childPosX -= grid.cellSize.x + grid.spacing.x;
                            _childrenPos.Add(childPosX);
                        }
                    }
                    else
                    {
                        Debug.LogError("Horizontal ScrollView is using VerticalLayoutGroup");
                    }
                    break;
                case ScrollDir.Vertical:
                    if (layoutGroup is VerticalLayoutGroup)
                    {
                        float childPosY = -_scrollView.GetComponent<RectTransform>().rect.height * 0.5f + GetChildItemHeight(0) * 0.5f;
                        spacing = (layoutGroup as VerticalLayoutGroup).spacing;
                        _childrenPos.Add(childPosY);
                        for (int i = 1; i < _content.childCount; i++)
                        {
                            childPosY += GetChildItemHeight(i) * 0.5f + GetChildItemHeight(i - 1) * 0.5f + spacing;
                            _childrenPos.Add(childPosY);
                        }
                    }
                    else if (layoutGroup is GridLayoutGroup)
                    {
                        GridLayoutGroup grid = layoutGroup as GridLayoutGroup;
                        float childPosY = -_scrollView.GetComponent<RectTransform>().rect.height * 0.5f + grid.cellSize.y * 0.5f;
                        _childrenPos.Add(childPosY);
                        for (int i = 1; i < _content.childCount; i++)
                        {
                            childPosY += grid.cellSize.y + grid.spacing.y;
                            _childrenPos.Add(childPosY);
                        }
                    }
                    else
                    {
                        Debug.LogError("Vertical ScrollView is using HorizontalLayoutGroup");
                    }
                    break;
            }
            execute = true;
            dragEndCallback = callback;
            SkipIndex(index);
        }

        /// <summary>
        /// 根据拖动来改变每一个子物体的缩放
        /// </summary>
        private void SetCellScale()
        {
            //大小无变化就直接return
            if (CenterScale == 1 && UnCenterScale == 1) return;

            for (int i = 0; i < _content.childCount; i++)
            {
                GameObject centerChild = _content.GetChild(i).gameObject;
                if (i == _curCenterChildIndex)
                    centerChild.transform.localScale = CenterScale * Vector3.one;
                else
                    centerChild.transform.localScale = UnCenterScale * Vector3.one;
            }
        }

        private float GetChildItemWidth(int index)
        {
            return (_content.GetChild(index) as RectTransform).sizeDelta.x;
        }

        private float GetChildItemHeight(int index)
        {
            return (_content.GetChild(index) as RectTransform).sizeDelta.y;
        }

        public void OnDrag(PointerEventData eventData)
        {
            if (!execute) return;

            //大小无变化就直接return
            if (CenterScale == 1 && UnCenterScale == 1) return;
            //实时更新并且改变缩放
            switch (Dir)
            {
                case ScrollDir.Horizontal:
                    _targetPos = FindClosestChildPos(_content.localPosition.x, out _curCenterChildIndex);
                    break;
                case ScrollDir.Vertical:
                    _targetPos = FindClosestChildPos(_content.localPosition.y, out _curCenterChildIndex);
                    break;
            }
            SetCellScale();
        }

        public void OnEndDrag(PointerEventData eventData)
        {

            if (!execute) return;
            _scrollView.StopMovement();
            switch (Dir)
            {
                //居中的动画效果我用的是Dotween,当然也可以用其他方式
                case ScrollDir.Horizontal:
                    _targetPos = FindClosestChildPos(_content.localPosition.x, out _curCenterChildIndex);
                    _content.DOLocalMoveX(_targetPos, ToCenterTime);
                    break;
                case ScrollDir.Vertical:
                    _targetPos = FindClosestChildPos(_content.localPosition.y, out _curCenterChildIndex);
                    _content.DOLocalMoveY(_targetPos, ToCenterTime);
                    break;
            }
            SetCellScale();
            if (dragEndCallback != null)
                dragEndCallback(_curCenterChildIndex);
            //Debug.Log("当前位置-----Index:" + _curCenterChildIndex);
        }

        public void OnBeginDrag(PointerEventData eventData)
        {
            if (!execute) return;
            _content.DOKill();
            _curCenterChildIndex = -1;
        }

        /// <summary>
        /// 检测当前位置最靠近哪个item
        /// </summary>
        private float FindClosestChildPos(float currentPos, out int curCenterChildIndex)
        {
            float closest = 0;
            float distance = Mathf.Infinity;
            curCenterChildIndex = -1;
            for (int i = 0; i < _childrenPos.Count; i++)
            {
                float p = _childrenPos[i];
                float d = Mathf.Abs(p - currentPos);
                if (d < distance)
                {
                    distance = d;
                    closest = p;
                    curCenterChildIndex = i;
                }
                else
                    break;
            }
            return closest;
        }
    }
 

unity环节设置

脚本挂在和ScrollRect同级

MovementType用Unrestricted模式

Content父节点锚点设置

关键接口:

InitList:初始化的时候调用。

SkipIndex:让指定的元素居中。

注意事项!

因为要提前记录每一个item所在的坐标,所以要先给ScrollRect赋值在去调用InitList接口,正常滑动就用InitList传的回调去刷新界面数据,手动居中就调用SkipIndex。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值