如何使用 Unity UI 滚动到 ScrollRect 中的特定元素?
How to scroll to a specific element in ScrollRect with Unity UI?
我使用 Unity 5.1 为手机游戏制作了一个注册表单。
为此,我使用 Unity UI 组件:ScrollRect + Autolayout(垂直布局)+ 文本(标签)+ 输入字段。
这部分工作正常。
但是,当键盘打开时,所选字段位于键盘下方。有没有办法以编程方式滚动表单以使所选字段进入视图?
我已经尝试使用 ScrollRect.verticalNormalizedPosition
并且它可以很好地滚动一些,但是我无法让选定的字段出现在我想要的地方。
感谢您的帮助!
我将给您一段我的代码片段,因为我想对您有所帮助。希望这对您有所帮助!
protected ScrollRect scrollRect;
protected RectTransform contentPanel;
public void SnapTo(RectTransform target)
{
Canvas.ForceUpdateCanvases();
contentPanel.anchoredPosition =
(Vector2)scrollRect.transform.InverseTransformPoint(contentPanel.position)
- (Vector2)scrollRect.transform.InverseTransformPoint(target.position);
}
是的,这可以使用编码垂直滚动,请试试这个代码:
//Set Scrollbar Value - For Displaying last message of content
Canvas.ForceUpdateCanvases ();
verticleScrollbar.value = 0f;
Canvas.ForceUpdateCanvases ();
当我开发聊天功能时,这段代码对我来说工作正常。
这是我将选定对象固定到 ScrollRect 中的方法
private ScrollRect scrollRect;
private RectTransform contentPanel;
public void ScrollReposition(RectTransform obj)
{
var objPosition = (Vector2)scrollRect.transform.InverseTransformPoint(obj.position);
var scrollHeight = scrollRect.GetComponent<RectTransform>().rect.height;
var objHeight = obj.rect.height;
if (objPosition.y > scrollHeight / 2)
{
contentPanel.localPosition = new Vector2(contentPanel.localPosition.x,
contentPanel.localPosition.y - objHeight - Padding.top);
}
if (objPosition.y < -scrollHeight / 2)
{
contentPanel.localPosition = new Vector2(contentPanel.localPosition.x,
contentPanel.localPosition.y + objHeight + Padding.bottom);
}
}
None 的建议对我有用,以下代码对我有用
这是扩展名
using UnityEngine;
using UnityEngine.UI;
namespace BlinkTalk
{
public static class ScrollRectExtensions
{
public static Vector2 GetSnapToPositionToBringChildIntoView(this ScrollRect instance, RectTransform child)
{
Canvas.ForceUpdateCanvases();
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 result = new Vector2(
0 - (viewportLocalPosition.x + childLocalPosition.x),
0 - (viewportLocalPosition.y + childLocalPosition.y)
);
return result;
}
}
}
下面是我如何使用它将内容的直接子项滚动到视图中
private void Update()
{
MyScrollRect.content.localPosition = MyScrollRect.GetSnapToPositionToBringChildIntoView(someChild);
}
width表示scroll rect中childern的宽度(假设所有childern的宽度都相同),spacing表示childern之间space,index表示要到达的目标元素
public float getSpecificItem (float pWidth, float pSpacing,int pIndex) {
return (pIndex * pWidth) - pWidth + ((pIndex - 1) * pSpacing);
}
虽然@maksymiuk 的回答是最正确的,因为它适当地考虑了锚点、枢轴和所有其他感谢 InverseTransformPoint() 函数,但它对我来说仍然不是开箱即用的——因为垂直滚动条,它也在改变它的 X 位置。所以我只是进行了更改以检查是否启用了垂直或水平滚动,如果未启用则不更改它们的轴。
public static void SnapTo( this ScrollRect scroller, RectTransform child )
{
Canvas.ForceUpdateCanvases();
var contentPos = (Vector2)scroller.transform.InverseTransformPoint( scroller.content.position );
var childPos = (Vector2)scroller.transform.InverseTransformPoint( child.position );
var endPos = contentPos - childPos;
// If no horizontal scroll, then don't change contentPos.x
if( !scroller.horizontal ) endPos.x = contentPos.x;
// If no vertical scroll, then don't change contentPos.y
if( !scroller.vertical ) endPos.y = contentPos.y;
scroller.content.anchoredPosition = endPos;
}
垂直调整:
[SerializeField]
private ScrollRect _scrollRect;
private void ScrollToCurrentElement()
{
var siblingIndex = _currentListItem.transform.GetSiblingIndex();
float pos = 1f - (float)siblingIndex / _scrollRect.content.transform.childCount;
if (pos < 0.4)
{
float correction = 1f / _scrollRect.content.transform.childCount;
pos -= correction;
}
_scrollRect.verticalNormalizedPosition = pos;
}
我这个问题版本的前提条件:
- 我要滚动到的
element
应该完全可见(最小间隙)
element
是 scrollRect
内容的直接子项
- 如果
element
已经完全可见,则保持 scoll 位置
- 我只关心垂直维度
这是最适合我的方法(感谢其他灵感):
// ScrollRect scrollRect;
// RectTransform element;
// Fully show `element` inside `scrollRect` with at least 25px clearance
scrollArea.EnsureVisibility(element, 25);
使用此扩展方法:
public static void EnsureVisibility(this ScrollRect scrollRect, RectTransform child, float padding=0)
{
Debug.Assert(child.parent == scrollRect.content,
"EnsureVisibility assumes that 'child' is directly nested in the content of 'scrollRect'");
float viewportHeight = scrollRect.viewport.rect.height;
Vector2 scrollPosition = scrollRect.content.anchoredPosition;
float elementTop = child.anchoredPosition.y;
float elementBottom = elementTop - child.rect.height;
float visibleContentTop = -scrollPosition.y - padding;
float visibleContentBottom = -scrollPosition.y - viewportHeight + padding;
float scrollDelta =
elementTop > visibleContentTop ? visibleContentTop - elementTop :
elementBottom < visibleContentBottom ? visibleContentBottom - elementBottom :
0f;
scrollPosition.y += scrollDelta;
scrollRect.content.anchoredPosition = scrollPosition;
}
Peter Morris 对具有移动类型 "elastic" 的 ScrollRects 的回答的变体。令我困扰的是,滚动矩形一直为边缘情况(第一个或最后几个元素)设置动画。希望有用:
/// <summary>
/// Thanks to
/// </summary>
/// <param name="instance"></param>
/// <param name="child"></param>
/// <returns></returns>
public static IEnumerator BringChildIntoView(this UnityEngine.UI.ScrollRect instance, RectTransform child)
{
Canvas.ForceUpdateCanvases();
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 result = new Vector2(
0 - (viewportLocalPosition.x + childLocalPosition.x),
0 - (viewportLocalPosition.y + childLocalPosition.y)
);
instance.content.localPosition = result;
yield return new WaitForUpdate();
instance.horizontalNormalizedPosition = Mathf.Clamp(instance.horizontalNormalizedPosition, 0f, 1f);
instance.verticalNormalizedPosition = Mathf.Clamp(instance.verticalNormalizedPosition, 0f, 1f);
}
虽然它引入了一帧延迟。
更新:这是一个不引入帧延迟的版本,它还考虑了内容的缩放:
/// <summary>
/// Based on
/// </summary>
/// <param name="instance"></param>
/// <param name="child"></param>
/// <returns></returns>
public static void BringChildIntoView(this UnityEngine.UI.ScrollRect instance, RectTransform child)
{
instance.content.ForceUpdateRectTransforms();
instance.viewport.ForceUpdateRectTransforms();
// now takes scaling into account
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 newContentPosition = new Vector2(
0 - ((viewportLocalPosition.x * instance.viewport.localScale.x) + (childLocalPosition.x * instance.content.localScale.x)),
0 - ((viewportLocalPosition.y * instance.viewport.localScale.y) + (childLocalPosition.y * instance.content.localScale.y))
);
// clamp positions
instance.content.localPosition = newContentPosition;
Rect contentRectInViewport = TransformRectFromTo(instance.content.transform, instance.viewport);
float deltaXMin = contentRectInViewport.xMin - instance.viewport.rect.xMin;
if(deltaXMin > 0) // clamp to <= 0
{
newContentPosition.x -= deltaXMin;
}
float deltaXMax = contentRectInViewport.xMax - instance.viewport.rect.xMax;
if (deltaXMax < 0) // clamp to >= 0
{
newContentPosition.x -= deltaXMax;
}
float deltaYMin = contentRectInViewport.yMin - instance.viewport.rect.yMin;
if (deltaYMin > 0) // clamp to <= 0
{
newContentPosition.y -= deltaYMin;
}
float deltaYMax = contentRectInViewport.yMax - instance.viewport.rect.yMax;
if (deltaYMax < 0) // clamp to >= 0
{
newContentPosition.y -= deltaYMax;
}
// apply final position
instance.content.localPosition = newContentPosition;
instance.content.ForceUpdateRectTransforms();
}
/// <summary>
/// Converts a Rect from one RectTransfrom to another RectTransfrom.
/// Hint: use the root Canvas Transform as "to" to get the reference pixel positions.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static Rect TransformRectFromTo(Transform from, Transform to)
{
RectTransform fromRectTrans = from.GetComponent<RectTransform>();
RectTransform toRectTrans = to.GetComponent<RectTransform>();
if (fromRectTrans != null && toRectTrans != null)
{
Vector3[] fromWorldCorners = new Vector3[4];
Vector3[] toLocalCorners = new Vector3[4];
Matrix4x4 toLocal = to.worldToLocalMatrix;
fromRectTrans.GetWorldCorners(fromWorldCorners);
for (int i = 0; i < 4; i++)
{
toLocalCorners[i] = toLocal.MultiplyPoint3x4(fromWorldCorners[i]);
}
return new Rect(toLocalCorners[0].x, toLocalCorners[0].y, toLocalCorners[2].x - toLocalCorners[1].x, toLocalCorners[1].y - toLocalCorners[0].y);
}
return default(Rect);
}
如果有人正在寻找平滑的滚动(使用 lerp)。
[SerializeField]
private ScrollRect _scrollRectComponent; //your scroll rect component
[SerializeField]
RectTransform _container; //content transform of the scrollrect
private IEnumerator LerpToChild(RectTransform target)
{
Vector2 _lerpTo = (Vector2)_scrollRectComponent.transform.InverseTransformPoint(_container.position) - (Vector2)_scrollRectComponent.transform.InverseTransformPoint(target.position);
bool _lerp = true;
Canvas.ForceUpdateCanvases();
while(_lerp)
{
float decelerate = Mathf.Min(10f * Time.deltaTime, 1f);
_container.anchoredPosition = Vector2.Lerp(_scrollRectComponent.transform.InverseTransformPoint(_container.position), _lerpTo, decelerate);
if (Vector2.SqrMagnitude((Vector2)_scrollRectComponent.transform.InverseTransformPoint(_container.position) - _lerpTo) < 0.25f)
{
_container.anchoredPosition = _lerpTo;
_lerp = false;
}
yield return null;
}
}
这是我为解决这个问题而创建的。将此行为放在可通过控制器 UI 导航选择的每个按钮上。
它支持完全嵌套的子对象。
它仅支持垂直 scrollrects,但可以轻松适应水平滚动。
using UnityEngine;
using System;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Legend
{
public class ScrollToOnSelect: MonoBehaviour, ISelectHandler
{
ScrollRect scrollRect;
RectTransform target;
void Start()
{
scrollRect = GetComponentInParent<ScrollRect>();
target = (RectTransform)this.transform;
}
Vector3 LocalPositionWithinAncestor(Transform ancestor, Transform target)
{
var result = Vector3.zero;
while (ancestor != target && target != null)
{
result += target.localPosition;
target = target.parent;
}
return result;
}
public void EnsureScrollVisible()
{
Canvas.ForceUpdateCanvases();
var targetPosition = LocalPositionWithinAncestor(scrollRect.content, target);
var top = (-targetPosition.y) - target.rect.height / 2;
var bottom = (-targetPosition.y) + target.rect.height / 2;
var topMargin = 100; // this is here because there are headers over the buttons sometimes
var result = scrollRect.content.anchoredPosition;
if (result.y > top - topMargin)
result.y = top - topMargin;
if (result.y + scrollRect.viewport.rect.height < bottom)
result.y = bottom - scrollRect.viewport.rect.height;
//Debug.Log($"{targetPosition} {target.rect.size} {top} {bottom} {scrollRect.content.anchoredPosition}->{result}");
scrollRect.content.anchoredPosition = result;
}
public void OnSelect(BaseEventData eventData)
{
if (scrollRect != null)
EnsureScrollVisible();
}
}
}
简单而完美
var pos = 1 - ((content.rect.height / 2 - target.localPosition.y) / content.rect.height);
scrollRect.normalizedPosition = new Vector2(0, pos);
我使用 Unity 5.1 为手机游戏制作了一个注册表单。 为此,我使用 Unity UI 组件:ScrollRect + Autolayout(垂直布局)+ 文本(标签)+ 输入字段。 这部分工作正常。
但是,当键盘打开时,所选字段位于键盘下方。有没有办法以编程方式滚动表单以使所选字段进入视图?
我已经尝试使用 ScrollRect.verticalNormalizedPosition
并且它可以很好地滚动一些,但是我无法让选定的字段出现在我想要的地方。
感谢您的帮助!
我将给您一段我的代码片段,因为我想对您有所帮助。希望这对您有所帮助!
protected ScrollRect scrollRect;
protected RectTransform contentPanel;
public void SnapTo(RectTransform target)
{
Canvas.ForceUpdateCanvases();
contentPanel.anchoredPosition =
(Vector2)scrollRect.transform.InverseTransformPoint(contentPanel.position)
- (Vector2)scrollRect.transform.InverseTransformPoint(target.position);
}
是的,这可以使用编码垂直滚动,请试试这个代码:
//Set Scrollbar Value - For Displaying last message of content
Canvas.ForceUpdateCanvases ();
verticleScrollbar.value = 0f;
Canvas.ForceUpdateCanvases ();
当我开发聊天功能时,这段代码对我来说工作正常。
这是我将选定对象固定到 ScrollRect 中的方法
private ScrollRect scrollRect;
private RectTransform contentPanel;
public void ScrollReposition(RectTransform obj)
{
var objPosition = (Vector2)scrollRect.transform.InverseTransformPoint(obj.position);
var scrollHeight = scrollRect.GetComponent<RectTransform>().rect.height;
var objHeight = obj.rect.height;
if (objPosition.y > scrollHeight / 2)
{
contentPanel.localPosition = new Vector2(contentPanel.localPosition.x,
contentPanel.localPosition.y - objHeight - Padding.top);
}
if (objPosition.y < -scrollHeight / 2)
{
contentPanel.localPosition = new Vector2(contentPanel.localPosition.x,
contentPanel.localPosition.y + objHeight + Padding.bottom);
}
}
None 的建议对我有用,以下代码对我有用
这是扩展名
using UnityEngine;
using UnityEngine.UI;
namespace BlinkTalk
{
public static class ScrollRectExtensions
{
public static Vector2 GetSnapToPositionToBringChildIntoView(this ScrollRect instance, RectTransform child)
{
Canvas.ForceUpdateCanvases();
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 result = new Vector2(
0 - (viewportLocalPosition.x + childLocalPosition.x),
0 - (viewportLocalPosition.y + childLocalPosition.y)
);
return result;
}
}
}
下面是我如何使用它将内容的直接子项滚动到视图中
private void Update()
{
MyScrollRect.content.localPosition = MyScrollRect.GetSnapToPositionToBringChildIntoView(someChild);
}
width表示scroll rect中childern的宽度(假设所有childern的宽度都相同),spacing表示childern之间space,index表示要到达的目标元素
public float getSpecificItem (float pWidth, float pSpacing,int pIndex) {
return (pIndex * pWidth) - pWidth + ((pIndex - 1) * pSpacing);
}
虽然@maksymiuk 的回答是最正确的,因为它适当地考虑了锚点、枢轴和所有其他感谢 InverseTransformPoint() 函数,但它对我来说仍然不是开箱即用的——因为垂直滚动条,它也在改变它的 X 位置。所以我只是进行了更改以检查是否启用了垂直或水平滚动,如果未启用则不更改它们的轴。
public static void SnapTo( this ScrollRect scroller, RectTransform child )
{
Canvas.ForceUpdateCanvases();
var contentPos = (Vector2)scroller.transform.InverseTransformPoint( scroller.content.position );
var childPos = (Vector2)scroller.transform.InverseTransformPoint( child.position );
var endPos = contentPos - childPos;
// If no horizontal scroll, then don't change contentPos.x
if( !scroller.horizontal ) endPos.x = contentPos.x;
// If no vertical scroll, then don't change contentPos.y
if( !scroller.vertical ) endPos.y = contentPos.y;
scroller.content.anchoredPosition = endPos;
}
垂直调整:
[SerializeField]
private ScrollRect _scrollRect;
private void ScrollToCurrentElement()
{
var siblingIndex = _currentListItem.transform.GetSiblingIndex();
float pos = 1f - (float)siblingIndex / _scrollRect.content.transform.childCount;
if (pos < 0.4)
{
float correction = 1f / _scrollRect.content.transform.childCount;
pos -= correction;
}
_scrollRect.verticalNormalizedPosition = pos;
}
我这个问题版本的前提条件:
- 我要滚动到的
element
应该完全可见(最小间隙) element
是scrollRect
内容的直接子项- 如果
element
已经完全可见,则保持 scoll 位置 - 我只关心垂直维度
这是最适合我的方法(感谢其他灵感):
// ScrollRect scrollRect;
// RectTransform element;
// Fully show `element` inside `scrollRect` with at least 25px clearance
scrollArea.EnsureVisibility(element, 25);
使用此扩展方法:
public static void EnsureVisibility(this ScrollRect scrollRect, RectTransform child, float padding=0)
{
Debug.Assert(child.parent == scrollRect.content,
"EnsureVisibility assumes that 'child' is directly nested in the content of 'scrollRect'");
float viewportHeight = scrollRect.viewport.rect.height;
Vector2 scrollPosition = scrollRect.content.anchoredPosition;
float elementTop = child.anchoredPosition.y;
float elementBottom = elementTop - child.rect.height;
float visibleContentTop = -scrollPosition.y - padding;
float visibleContentBottom = -scrollPosition.y - viewportHeight + padding;
float scrollDelta =
elementTop > visibleContentTop ? visibleContentTop - elementTop :
elementBottom < visibleContentBottom ? visibleContentBottom - elementBottom :
0f;
scrollPosition.y += scrollDelta;
scrollRect.content.anchoredPosition = scrollPosition;
}
Peter Morris 对具有移动类型 "elastic" 的 ScrollRects 的回答的变体。令我困扰的是,滚动矩形一直为边缘情况(第一个或最后几个元素)设置动画。希望有用:
/// <summary>
/// Thanks to
/// </summary>
/// <param name="instance"></param>
/// <param name="child"></param>
/// <returns></returns>
public static IEnumerator BringChildIntoView(this UnityEngine.UI.ScrollRect instance, RectTransform child)
{
Canvas.ForceUpdateCanvases();
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 result = new Vector2(
0 - (viewportLocalPosition.x + childLocalPosition.x),
0 - (viewportLocalPosition.y + childLocalPosition.y)
);
instance.content.localPosition = result;
yield return new WaitForUpdate();
instance.horizontalNormalizedPosition = Mathf.Clamp(instance.horizontalNormalizedPosition, 0f, 1f);
instance.verticalNormalizedPosition = Mathf.Clamp(instance.verticalNormalizedPosition, 0f, 1f);
}
虽然它引入了一帧延迟。
更新:这是一个不引入帧延迟的版本,它还考虑了内容的缩放:
/// <summary>
/// Based on
/// </summary>
/// <param name="instance"></param>
/// <param name="child"></param>
/// <returns></returns>
public static void BringChildIntoView(this UnityEngine.UI.ScrollRect instance, RectTransform child)
{
instance.content.ForceUpdateRectTransforms();
instance.viewport.ForceUpdateRectTransforms();
// now takes scaling into account
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 newContentPosition = new Vector2(
0 - ((viewportLocalPosition.x * instance.viewport.localScale.x) + (childLocalPosition.x * instance.content.localScale.x)),
0 - ((viewportLocalPosition.y * instance.viewport.localScale.y) + (childLocalPosition.y * instance.content.localScale.y))
);
// clamp positions
instance.content.localPosition = newContentPosition;
Rect contentRectInViewport = TransformRectFromTo(instance.content.transform, instance.viewport);
float deltaXMin = contentRectInViewport.xMin - instance.viewport.rect.xMin;
if(deltaXMin > 0) // clamp to <= 0
{
newContentPosition.x -= deltaXMin;
}
float deltaXMax = contentRectInViewport.xMax - instance.viewport.rect.xMax;
if (deltaXMax < 0) // clamp to >= 0
{
newContentPosition.x -= deltaXMax;
}
float deltaYMin = contentRectInViewport.yMin - instance.viewport.rect.yMin;
if (deltaYMin > 0) // clamp to <= 0
{
newContentPosition.y -= deltaYMin;
}
float deltaYMax = contentRectInViewport.yMax - instance.viewport.rect.yMax;
if (deltaYMax < 0) // clamp to >= 0
{
newContentPosition.y -= deltaYMax;
}
// apply final position
instance.content.localPosition = newContentPosition;
instance.content.ForceUpdateRectTransforms();
}
/// <summary>
/// Converts a Rect from one RectTransfrom to another RectTransfrom.
/// Hint: use the root Canvas Transform as "to" to get the reference pixel positions.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static Rect TransformRectFromTo(Transform from, Transform to)
{
RectTransform fromRectTrans = from.GetComponent<RectTransform>();
RectTransform toRectTrans = to.GetComponent<RectTransform>();
if (fromRectTrans != null && toRectTrans != null)
{
Vector3[] fromWorldCorners = new Vector3[4];
Vector3[] toLocalCorners = new Vector3[4];
Matrix4x4 toLocal = to.worldToLocalMatrix;
fromRectTrans.GetWorldCorners(fromWorldCorners);
for (int i = 0; i < 4; i++)
{
toLocalCorners[i] = toLocal.MultiplyPoint3x4(fromWorldCorners[i]);
}
return new Rect(toLocalCorners[0].x, toLocalCorners[0].y, toLocalCorners[2].x - toLocalCorners[1].x, toLocalCorners[1].y - toLocalCorners[0].y);
}
return default(Rect);
}
如果有人正在寻找平滑的滚动(使用 lerp)。
[SerializeField]
private ScrollRect _scrollRectComponent; //your scroll rect component
[SerializeField]
RectTransform _container; //content transform of the scrollrect
private IEnumerator LerpToChild(RectTransform target)
{
Vector2 _lerpTo = (Vector2)_scrollRectComponent.transform.InverseTransformPoint(_container.position) - (Vector2)_scrollRectComponent.transform.InverseTransformPoint(target.position);
bool _lerp = true;
Canvas.ForceUpdateCanvases();
while(_lerp)
{
float decelerate = Mathf.Min(10f * Time.deltaTime, 1f);
_container.anchoredPosition = Vector2.Lerp(_scrollRectComponent.transform.InverseTransformPoint(_container.position), _lerpTo, decelerate);
if (Vector2.SqrMagnitude((Vector2)_scrollRectComponent.transform.InverseTransformPoint(_container.position) - _lerpTo) < 0.25f)
{
_container.anchoredPosition = _lerpTo;
_lerp = false;
}
yield return null;
}
}
这是我为解决这个问题而创建的。将此行为放在可通过控制器 UI 导航选择的每个按钮上。 它支持完全嵌套的子对象。 它仅支持垂直 scrollrects,但可以轻松适应水平滚动。
using UnityEngine;
using System;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Legend
{
public class ScrollToOnSelect: MonoBehaviour, ISelectHandler
{
ScrollRect scrollRect;
RectTransform target;
void Start()
{
scrollRect = GetComponentInParent<ScrollRect>();
target = (RectTransform)this.transform;
}
Vector3 LocalPositionWithinAncestor(Transform ancestor, Transform target)
{
var result = Vector3.zero;
while (ancestor != target && target != null)
{
result += target.localPosition;
target = target.parent;
}
return result;
}
public void EnsureScrollVisible()
{
Canvas.ForceUpdateCanvases();
var targetPosition = LocalPositionWithinAncestor(scrollRect.content, target);
var top = (-targetPosition.y) - target.rect.height / 2;
var bottom = (-targetPosition.y) + target.rect.height / 2;
var topMargin = 100; // this is here because there are headers over the buttons sometimes
var result = scrollRect.content.anchoredPosition;
if (result.y > top - topMargin)
result.y = top - topMargin;
if (result.y + scrollRect.viewport.rect.height < bottom)
result.y = bottom - scrollRect.viewport.rect.height;
//Debug.Log($"{targetPosition} {target.rect.size} {top} {bottom} {scrollRect.content.anchoredPosition}->{result}");
scrollRect.content.anchoredPosition = result;
}
public void OnSelect(BaseEventData eventData)
{
if (scrollRect != null)
EnsureScrollVisible();
}
}
}
简单而完美
var pos = 1 - ((content.rect.height / 2 - target.localPosition.y) / content.rect.height);
scrollRect.normalizedPosition = new Vector2(0, pos);