From 8c5ab0b24a670de64d530c2488be5fab78c33f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Fri, 27 Mar 2026 13:37:18 +0800 Subject: [PATCH] =?UTF-8?q?=E8=99=9A=E6=8B=9F=E5=88=97=E8=A1=A8=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=8A=A8=E6=80=81ScrollBarWenScrollable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Editor/RecyclerView/RecyclerViewEditor.cs | 9 ++ Runtime/RecyclerView/RecyclerView.cs | 109 ++++++++++++++++++++-- 2 files changed, 110 insertions(+), 8 deletions(-) diff --git a/Editor/RecyclerView/RecyclerViewEditor.cs b/Editor/RecyclerView/RecyclerViewEditor.cs index d8d886f..69d2dfb 100644 --- a/Editor/RecyclerView/RecyclerViewEditor.cs +++ b/Editor/RecyclerView/RecyclerViewEditor.cs @@ -57,6 +57,7 @@ namespace AlicizaX.UI.Editor private SerializedProperty _templates; private SerializedProperty _showScrollBar; + private SerializedProperty _showScrollBarOnlyWhenScrollable; private SerializedProperty _scrollbar; #endregion @@ -120,6 +121,7 @@ namespace AlicizaX.UI.Editor { _templates = serializedObject.FindProperty("templates"); _showScrollBar = serializedObject.FindProperty("showScrollBar"); + _showScrollBarOnlyWhenScrollable = serializedObject.FindProperty("showScrollBarOnlyWhenScrollable"); _scrollbar = serializedObject.FindProperty("scrollbar"); } @@ -303,6 +305,13 @@ namespace AlicizaX.UI.Editor { HandleScrollBarToggle(); } + + if (_showScrollBar.boolValue) + { + EditorGUILayout.PropertyField( + _showScrollBarOnlyWhenScrollable, + new GUIContent("Only When Scrollable", "Hide the scrollbar and disable list scrolling when content does not overflow the viewport.")); + } } } diff --git a/Runtime/RecyclerView/RecyclerView.cs b/Runtime/RecyclerView/RecyclerView.cs index 4184220..803b8da 100644 --- a/Runtime/RecyclerView/RecyclerView.cs +++ b/Runtime/RecyclerView/RecyclerView.cs @@ -38,6 +38,7 @@ namespace AlicizaX.UI [HideInInspector] [SerializeField] private ViewHolder[] templates; [HideInInspector] [SerializeField] private RectTransform content; [HideInInspector] [SerializeField] private bool showScrollBar; + [HideInInspector] [SerializeField] private bool showScrollBarOnlyWhenScrollable; [HideInInspector] [SerializeField] private Scrollbar scrollbar; #endregion @@ -200,6 +201,20 @@ namespace AlicizaX.UI } } + /// + /// 是否仅在内容超出可视区域时显示滚动条并允许滚动 + /// + public bool ShowScrollBarOnlyWhenScrollable + { + get => showScrollBarOnlyWhenScrollable; + set + { + if (showScrollBarOnlyWhenScrollable == value) return; + showScrollBarOnlyWhenScrollable = value; + RequestLayout(); + } + } + #endregion @@ -333,13 +348,13 @@ namespace AlicizaX.UI scroller.Snap = snap; scroller.OnValueChanged.AddListener(OnScrollChanged); scroller.OnMoveStoped.AddListener(OnMoveStoped); + UpdateScrollerState(); } private void ConfigureScrollbar() { if (!showScrollBar || scrollbar == null) return; - scrollbar.gameObject.SetActive(scroll); scrollbar.onValueChanged.AddListener(OnScrollbarChanged); var scrollbarEx = scrollbar.gameObject.GetComponent(); @@ -349,6 +364,7 @@ namespace AlicizaX.UI } scrollbarEx.OnDragEnd = OnScrollbarDragEnd; + UpdateScrollbarVisibility(); } #endregion @@ -428,15 +444,28 @@ namespace AlicizaX.UI /// public void RequestLayout() { + if (layoutManager == null) + { + UpdateScrollbarVisibility(); + return; + } + layoutManager.SetContentSize(); - if (scroller == null) return; + if (scroller == null) + { + UpdateScrollbarVisibility(); + return; + } scroller.Direction = direction; scroller.ViewSize = layoutManager.ViewportSize; scroller.ContentSize = layoutManager.ContentSize; + scroller.Position = Mathf.Clamp(scroller.Position, 0, scroller.MaxPosition); + UpdateScrollerState(); UpdateScrollbarVisibility(); + UpdateScrollbarValue(scroller.Position); } #endregion @@ -661,11 +690,14 @@ namespace AlicizaX.UI private void UpdateScrollbarVisibility() { - if (scrollbar == null || scroller == null || layoutManager.ContentSize == Vector2.zero) + if (scrollbar == null) + { return; + } bool shouldShow = ShouldShowScrollbar(); scrollbar.gameObject.SetActive(shouldShow); + scrollbar.interactable = shouldShow; if (shouldShow) { @@ -676,16 +708,22 @@ namespace AlicizaX.UI private bool ShouldShowScrollbar() { - if (direction == Direction.Custom) return false; + if (!showScrollBar || !scroll || scrollbar == null || scroller == null || layoutManager == null || !SupportsOverflowCheck()) + { + return false; + } - if (direction == Direction.Vertical) + if (!HasValidScrollMetrics()) { - return layoutManager.ContentSize.y > layoutManager.ViewportSize.y; + return false; } - else // Horizontal + + if (!ShouldLimitScrollToOverflow()) { - return layoutManager.ContentSize.x > layoutManager.ViewportSize.x; + return true; } + + return HasScrollableContent(); } private void ConfigureScrollbarDirection() @@ -707,6 +745,61 @@ namespace AlicizaX.UI } } + private void UpdateScrollerState() + { + if (scroller == null) + { + return; + } + + scroller.enabled = scroll && (!ShouldLimitScrollToOverflow() || HasScrollableContent()); + } + + private bool ShouldLimitScrollToOverflow() + { + return showScrollBar && showScrollBarOnlyWhenScrollable && SupportsOverflowCheck(); + } + + private bool HasScrollableContent() + { + if (layoutManager == null) + { + return false; + } + + if (direction == Direction.Vertical) + { + return layoutManager.ContentSize.y > layoutManager.ViewportSize.y; + } + + if (direction == Direction.Horizontal) + { + return layoutManager.ContentSize.x > layoutManager.ViewportSize.x; + } + + return false; + } + + private bool SupportsOverflowCheck() + { + return direction == Direction.Vertical || direction == Direction.Horizontal; + } + + private bool HasValidScrollMetrics() + { + if (direction == Direction.Vertical) + { + return layoutManager.ContentSize.y > 0f && layoutManager.ViewportSize.y > 0f; + } + + if (direction == Direction.Horizontal) + { + return layoutManager.ContentSize.x > 0f && layoutManager.ViewportSize.x > 0f; + } + + return false; + } + private void SnapToNearestItem() { int index = layoutManager.PositionToIndex(GetScrollPosition());