lcy
2026-06-23 c607e220884246be14d35da24da6dda35a2eb407
662 武将试炼 武将副本头像条改Scroller滚动条

1.头像条非循环滑动,滑到最左/最右不会首尾相接。
2.滑动和松手停止不会自动选中中间头像;选中只由点击或外部逻辑触发。
3.选中头像显示选中框并放大;最左/最右选中时不强制居中,但必须完整显示。
4.头像显示顶部对齐
2个文件已删除
2个文件已修改
353 ■■■■■ 已修改文件
Main/System/HeroTrial/HeroTrialCarouselView.cs 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroTrial/HeroTrialCarouselView.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroTrial/HeroTrialHeroItem.cs 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroTrial/HeroTrialWin.cs 183 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/HeroTrial/HeroTrialCarouselView.cs
File was deleted
Main/System/HeroTrial/HeroTrialCarouselView.cs.meta
File was deleted
Main/System/HeroTrial/HeroTrialHeroItem.cs
@@ -3,7 +3,7 @@
using UnityEngine;
using UnityEngine.UI;
public class HeroTrialHeroItem : MonoBehaviour, ICarouselItem
public class HeroTrialHeroItem : CellView
{
    [SerializeField] Image bgImage;
    [SerializeField] Image isActImage;
@@ -15,9 +15,14 @@
    [SerializeField] TextEx goText;
    [SerializeField] ButtonEx clickButton;
    [SerializeField] Image redImage;
    [SerializeField] RectTransform scaleRoot;
    [SerializeField] float selectedScale = 1.3f;
    [SerializeField] float unselectedScale = 0.8f;
    public RectTransform rectTransform;
    public Action<int> onItemClick;
    public int dataIndex;
    Action<int> onItemClick;
    int heroDataIndex;
    uint currentHeroID;
    public RectTransform RectTransform
    {
@@ -29,20 +34,29 @@
        }
    }
    void Awake()
    public void UpdateData(uint heroID, int index, bool selected, Action<int> clickAction)
    {
        rectTransform = GetComponent<RectTransform>();
    }
    public void UpdateData(uint heroID, byte passLevelNum, int index)
    {
        dataIndex = index;
        heroDataIndex = index;
        onItemClick = clickAction;
        HeroConfig heroConfig = HeroConfig.Get((int)heroID);
        if (heroConfig == null)
            return;
        if (currentHeroID != heroID)
        {
            currentHeroID = heroID;
            RefreshStaticHeroInfo(heroConfig);
        }
        UpdateLockState(heroConfig);
        RefreshRed(heroConfig.HeroID);
        SetSelected(selected);
        clickButton.SetListener(OnClick);
    }
    void RefreshStaticHeroInfo(HeroConfig heroConfig)
    {
        isActImage.SetActive(heroConfig.IsActLimit > 0);
        qualityBgImage.SetSprite("heroheadBG" + heroConfig.Quality);
@@ -54,10 +68,6 @@
            if (skinConfig != null)
                heroHeadImage.sprite = UILoader.LoadSprite("HeroHead", skinConfig.SquareIcon);
        }
        UpdateLockState(heroConfig);
        RefreshRed(heroConfig.HeroID);
        clickButton.SetListener(() => onItemClick?.Invoke(dataIndex));
    }
    void RefreshRed(int heroID)
@@ -81,5 +91,20 @@
    {
        string bgKey = selected ? "HeroTrialHeroBgSelect" : "HeroTrialHeroBgUnSelect";
        bgImage.SetSprite(bgKey);
        RectTransform rect = scaleRoot != null ? scaleRoot : RectTransform;
        if (rect == null)
            return;
        float currentScale = selected ? selectedScale : unselectedScale;
        float maxScale = Mathf.Max(selectedScale, unselectedScale);
        float posY = (maxScale - currentScale) * rect.rect.height * 0.5f;
        rect.localScale = Vector3.one * currentScale;
        rect.anchoredPosition = rect.anchoredPosition.SetY(posY);
    }
    void OnClick()
    {
        onItemClick?.Invoke(heroDataIndex);
    }
}
Main/System/HeroTrial/HeroTrialWin.cs
@@ -1,18 +1,24 @@
using System.Collections.Generic;
using EnhancedUI.EnhancedScroller;
using UnityEngine;
using UnityEngine.UI;
public class HeroTrialWin : UIBase
{
    [SerializeField] HeroTrialCarouselView carouselView;
    [SerializeField] ScrollerController heroScroller;
    [SerializeField] ScrollerController scroller;
    [SerializeField] float heroSelectTweenTime = 0.15f;
    List<int> heroIDs;
    List<HeroTrialConfig> currentConfigs = new List<HeroTrialConfig>();
    int currentHeroIndex = -1;
    uint currentHeroID;
    byte currentPassLevelNum;
    bool currentHeroUnlocked;
    string currentHeroUnlockTip = string.Empty;
    // OnPreOpen 首次打开时用于定位到红点关卡的 dataIndex;-1 表示不跳转。
    int jumpTargetDataIndex = -1;
    bool needJumpToHeroAfterScrollerReady;
    protected override void InitComponent()
    {
@@ -20,46 +26,37 @@
    protected override void OnPreOpen()
    {
        carouselView.InitPool();
        carouselView.BindEvents();
        carouselView.OnSelectedIndexChanged += OnSelectedIndexChanged;
        SetupHeroScroller();
        heroScroller.OnRefreshCell += OnRefreshHeroCell;
        heroScroller.OnRefreshCompleteEvent += OnHeroScrollerRefreshComplete;
        scroller.OnRefreshCell += OnRefreshCell;
        HeroTrialManager.Instance.OnUpdateHeroTrialInfoEvent += OnHeroTrialInfoUpdate;
        carouselView.RefreshData();
        carouselView.InitWithIndex(GetDefaultHeroIndex());
        RefreshHeroScroller(GetDefaultHeroIndex());
        RefreshScroller();
    }
    protected override void OnPreClose()
    {
        carouselView.UnbindEvents();
        carouselView.OnSelectedIndexChanged -= OnSelectedIndexChanged;
        heroScroller.OnRefreshCell -= OnRefreshHeroCell;
        heroScroller.OnRefreshCompleteEvent -= OnHeroScrollerRefreshComplete;
        scroller.OnRefreshCell -= OnRefreshCell;
        HeroTrialManager.Instance.OnUpdateHeroTrialInfoEvent -= OnHeroTrialInfoUpdate;
    }
    void OnSelectedIndexChanged()
    {
        RefreshScroller();
    }
    void OnHeroTrialInfoUpdate()
    {
        int targetHeroIndex = -1;
        int redHeroIndex = FindRedHeroIndex();
        if (redHeroIndex >= 0)
        {
            List<int> heroIDList = HeroTrialManager.Instance.GetHeroTrialHeroIDList();
            int redHeroID = heroIDList[redHeroIndex];
            if (carouselView.CurrentHeroID != redHeroID)
            {
                carouselView.SelectIndex(redHeroIndex);
                return;
            }
            if (currentHeroID != redHeroID)
                targetHeroIndex = redHeroIndex;
        }
        RefreshHeroScroller(targetHeroIndex);
        RefreshScroller();
    }
@@ -97,13 +94,157 @@
        return -1;
    }
    void RefreshHeroScroller(int selectedIndex)
    {
        uint prevHeroID = currentHeroID;
        heroIDs = HeroTrialManager.Instance.GetHeroTrialHeroIDList();
        if (heroIDs.IsNullOrEmpty())
        {
            currentHeroIndex = -1;
            currentHeroID = 0;
            heroScroller.Refresh();
            heroScroller.Restart();
            return;
        }
        int heroIndex = selectedIndex;
        if (heroIndex < 0)
            heroIndex = FindHeroIndex(prevHeroID);
        currentHeroIndex = Mathf.Clamp(heroIndex, 0, heroIDs.Count - 1);
        currentHeroID = (uint)heroIDs[currentHeroIndex];
        SetupHeroScroller();
        heroScroller.Refresh();
        for (int i = 0; i < heroIDs.Count; i++)
        {
            heroScroller.AddCell(ScrollerDataType.Header, i);
        }
        heroScroller.Restart();
        needJumpToHeroAfterScrollerReady = true;
        JumpToCurrentHero(false);
    }
    void SelectHeroIndex(int index)
    {
        if (heroIDs.IsNullOrEmpty())
            return;
        int oldIndex = currentHeroIndex;
        currentHeroIndex = Mathf.Clamp(index, 0, heroIDs.Count - 1);
        currentHeroID = (uint)heroIDs[currentHeroIndex];
        RefreshActiveHeroItems();
        JumpToCurrentHero(true);
        if (oldIndex != currentHeroIndex)
            RefreshScroller();
    }
    void OnRefreshHeroCell(ScrollerDataType type, CellView cell)
    {
        _ = type;
        HeroTrialHeroItem heroItem = cell as HeroTrialHeroItem;
        if (heroItem == null || heroIDs.IsNullOrEmpty())
            return;
        int dataIndex = Mathf.Clamp(cell.index, 0, heroIDs.Count - 1);
        heroItem.UpdateData((uint)heroIDs[dataIndex], dataIndex, dataIndex == currentHeroIndex, SelectHeroIndex);
    }
    void OnHeroScrollerRefreshComplete()
    {
        RefreshActiveHeroItems();
        if (!needJumpToHeroAfterScrollerReady)
            return;
        needJumpToHeroAfterScrollerReady = false;
        JumpToCurrentHero(false);
    }
    void RefreshActiveHeroItems()
    {
        List<CellView> activeCells = heroScroller.GetActiveCellViews();
        if (activeCells == null)
            return;
        for (int i = 0; i < activeCells.Count; i++)
        {
            OnRefreshHeroCell(activeCells[i].type, activeCells[i]);
        }
    }
    void SetupHeroScroller()
    {
        heroScroller.horizontal = true;
        heroScroller.vertical = false;
        heroScroller.lockType = EnhanceLockType.None;
        heroScroller.m_Scorller.Loop = false;
        heroScroller.m_Scorller.snapping = false;
        heroScroller.m_Scorller.scrollDirection = EnhancedScroller.ScrollDirectionEnum.Horizontal;
        ScrollRect scrollRect = heroScroller.mScrollRect;
        if (scrollRect != null)
        {
            scrollRect.horizontal = true;
            scrollRect.vertical = false;
        }
    }
    void JumpToCurrentHero(bool useTween)
    {
        if (heroIDs.IsNullOrEmpty() || currentHeroIndex < 0)
            return;
        if (!heroScroller.m_Scorller.inited)
        {
            needJumpToHeroAfterScrollerReady = true;
            return;
        }
        float scrollerOffset = GetCurrentHeroScrollerOffset();
        EnhancedScroller.TweenType tweenType = useTween ? EnhancedScroller.TweenType.easeOutQuad : EnhancedScroller.TweenType.immediate;
        float tweenTime = useTween ? heroSelectTweenTime : 0f;
        heroScroller.m_Scorller.JumpToDataIndex(currentHeroIndex, scrollerOffset, 0.5f, true, tweenType, tweenTime, RefreshActiveHeroItems);
    }
    float GetCurrentHeroScrollerOffset()
    {
        if (heroIDs.IsNullOrEmpty())
            return 0.5f;
        float viewportSize = Mathf.Max(1f, heroScroller.m_Scorller.ScrollRectSize);
        float cellSize = Mathf.Max(1f, heroScroller.GetCellSize(currentHeroIndex));
        float halfCellOffset = cellSize * 0.5f / viewportSize;
        if (currentHeroIndex <= 0)
            return halfCellOffset;
        if (currentHeroIndex >= heroIDs.Count - 1)
            return Mathf.Clamp01(1f - halfCellOffset);
        return 0.5f;
    }
    int FindHeroIndex(uint heroID)
    {
        if (heroID == 0 || heroIDs.IsNullOrEmpty())
            return -1;
        for (int i = 0; i < heroIDs.Count; i++)
        {
            if ((uint)heroIDs[i] == heroID)
                return i;
        }
        return -1;
    }
    /// <summary>
    /// 根据当前选中的武将,刷新下方的关卡Scroll
    /// </summary>
    void RefreshScroller()
    {
        currentConfigs.Clear();
        currentHeroID = carouselView.CurrentHeroID;
        currentHeroUnlockTip = string.Empty;
        currentHeroUnlocked = currentHeroID != 0 && HeroTrialManager.Instance.IsHeroTrialHeroUnlocked((int)currentHeroID, out currentHeroUnlockTip);
        if (currentHeroID == 0 || !HeroTrialManager.Instance.TryGetHeroPassLevelNum(currentHeroID, out currentPassLevelNum))