代码环节
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。