仿方舟奖励列表(左半部分)移动按钮列表组件

效果图

在这里插入图片描述

源码

// ******************************************************************
//       /\ /|       @file       MoveButtonlist.cs
//       \ V/        @brief      移动按钮列表组件
//       | "")       @author     Shadowrabbit, yingtu0401@gmail.com
//       /  |                    
//      /  \\        @Modified   2021-01-25 18:00:40
//    *(__\_\        @Copyright  Copyright (c) 2021, Shadowrabbit
// ******************************************************************

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MoveButtonList : MonoBehaviour
{
    #region Field

    public int buttonCount; //按钮的数量
    public GameObject protoButton; //按钮原型体
    public float spacingY; //Y轴偏移坐标 
    public float movingSpacingY; //Y轴位移偏移坐标
    protected Action<int> onClick; //按钮点击回调 <int:按钮在按钮组内的索引>
    protected bool isMoving; //是否在移动中

    protected readonly Dictionary<int, Button>
        mapCompButtons = new Dictionary<int, Button>(); //按钮字典 <int:按钮索引,Button:按钮组件实例>

    [SerializeField] protected int currentSelectedIndex; //当前选中的索引
    private float m_protoWidth; //原型体宽度
    private float m_protoHeight; //原型体高度
    private float m_movingProgress; //移动进度
    [SerializeField] private float movingSpeed; //移动速度 百分比/帧 

    private readonly List<Vector2> m_normalPostionList = new List<Vector2>(); //正常情况下的坐标列表
    private readonly List<Vector2> m_selectedPostionList = new List<Vector2>(); //被选中情况下的坐标列表

    #endregion

    /// <summary>
    /// 设置按钮标题
    /// </summary>
    /// <param name="_arrayTitle"></param>
    public void SetButtonTitles(string[] _arrayTitle)
    {
        if (_arrayTitle == null)
        {
            Debug.LogError("_arrayTitle == null");
            return;
        }

        for (var i = 0; i < buttonCount; i++)
        {
            var button = mapCompButtons[i];
            var compTextList = button.GetComponents<Text>();
            foreach (var compText in compTextList)
            {
                compText.text = _arrayTitle[i];
            }
        }
    }

    /// <summary>
    /// 设置点击回调
    /// </summary>
    /// <param name="_onClick"></param>
    public void AddOnClickListener(Action<int> _onClick)
    {
        onClick = _onClick;
    }

    /// <summary>
    /// 获取移动状态
    /// </summary>
    /// <returns></returns>
    public bool GetMovingState()
    {
        return isMoving;
    }

    /// <summary>
    /// 选择对应索引
    /// </summary>
    /// <param name="_index"></param>
    public void SelectIndex(int _index)
    {
        currentSelectedIndex = _index;
        //小于索引的按钮去上面 大于索引的按钮去下面
        StopCoroutine(nameof(PositionAdjust));
        StartCoroutine(nameof(PositionAdjust));
    }

    /// <summary>   
    /// 初始化
    /// </summary>
    private void Init()
    {
        //原型体校验
        CheckProtoTransform();
        //内容节点校验
        CheckContentTransform();
        //修正内容节点尺寸
        FixContentSize();
        //计算每个按钮在两种状态下的坐标
        CalcButtonPostion();
    }

    /// <summary>
    /// 原型体校验
    /// </summary>
    private void CheckProtoTransform()
    {
        //原型体校验
        if (protoButton == null)
        {
            Debug.LogError("找不到原型体");
            return;
        }

        var compProtoRectTransform = protoButton.GetComponent<RectTransform>();
        compProtoRectTransform.pivot = new Vector2(0.5f, 1);
        compProtoRectTransform.anchorMin = new Vector2(0.5f, 1);
        compProtoRectTransform.anchorMax = new Vector2(0.5f, 1);
        compProtoRectTransform.anchoredPosition = Vector2.zero;
        //获取宽高
        var rect = compProtoRectTransform.rect;
        m_protoWidth = rect.width;
        m_protoHeight = rect.height;
    }

    /// <summary>
    /// 内容节点校验
    /// </summary>
    private void CheckContentTransform()
    {
        var compRectTransformContent = GetComponent<RectTransform>();

        compRectTransformContent.pivot = new Vector2(0, 1);
        compRectTransformContent.anchorMin = new Vector2(0, 1);
        compRectTransformContent.anchorMax = new Vector2(0, 1);
    }

    /// <summary>
    /// 修正内容节点尺寸
    /// </summary>
    private void FixContentSize()
    {
        var compProtoRectTransform = transform.GetComponent<RectTransform>();
        var width = m_protoWidth;
        var height = m_protoHeight * buttonCount + (buttonCount - 2) * spacingY + movingSpacingY;
        compProtoRectTransform.sizeDelta = new Vector2(width, height);
    }

    /// <summary>
    /// 按钮组创建
    /// </summary>
    protected virtual void ButtonGroupCreate()
    {
        //根据数量创建按钮
        for (var i = 0; i < buttonCount; i++)
        {
            var buttonIndex = i; //当前的按钮索引
            var objButton = Instantiate(protoButton, transform); //按钮克隆体
            objButton.name = protoButton.name + i; //设置名字
            var compButton = objButton.GetComponent<Button>(); //button组件
            compButton.onClick.AddListener(() =>
            {
                if (isMoving) return;
                onClick?.Invoke(buttonIndex);
                SelectIndex(buttonIndex);
            }); //为每个按钮设置一个匿名点击方法 回调index
            mapCompButtons.Add(i, compButton);
        }
    }

    /// <summary>
    /// 位置调整
    /// </summary>
    /// <returns></returns>
    private IEnumerator PositionAdjust()
    {
        isMoving = true;
        m_movingProgress = 0f;
        var isMoveFinished = false; //移动是否完成
        while (!isMoveFinished)
        {
            m_movingProgress += movingSpeed; //移动进度增加
            isMoveFinished = true; //每次都假定本次完成
            for (var i = 0; i < buttonCount; i++)
            {
                var compButton = mapCompButtons[i]; //按钮组件
                var compRectTransform = compButton.GetComponent<RectTransform>(); //transform组件
                var uiPostion = compRectTransform.anchoredPosition; //当前按钮的UI坐标
                var isSelected = i <= currentSelectedIndex; //当前按钮是否处于选中状态
                var currentY = -uiPostion.y; //当前按钮的Y轴坐标 转换成正数
                //选中状态下
                if (isSelected)
                {
                    //按钮已经达到了选中状态的位置
                    if (currentY <= m_selectedPostionList[i].y) continue;
                    //按钮没有达到选中状态的位置 插值
                    compRectTransform.anchoredPosition3D = new Vector3(0,
                        -Mathf.Lerp(m_normalPostionList[i].y, m_selectedPostionList[i].y, m_movingProgress),
                        0);
                    //存在没有移动完成的按钮 移动流程判定未完成
                    isMoveFinished = false;
                }
                //没选中的状态下
                else
                {
                    //按钮已经达到了非选中状态的位置
                    if (currentY >= m_normalPostionList[i].y) continue;
                    compRectTransform.anchoredPosition3D = new Vector3(0,
                        -Mathf.Lerp(m_selectedPostionList[i].y, m_normalPostionList[i].y, m_movingProgress),
                        0);
                    isMoveFinished = false;
                }
            }

            yield return null;
        }

        isMoving = false;
    }

    /// <summary>
    /// 撤销全部监听
    /// </summary>
    private void RemoveAllListeners()
    {
        foreach (var button in mapCompButtons.Values)
        {
            button.onClick.RemoveAllListeners();
        }
    }

    /// <summary>
    /// 计算按钮在两种状态下的坐标
    /// </summary>
    private void CalcButtonPostion()
    {
        m_selectedPostionList.Clear();
        m_normalPostionList.Clear();
        for (var i = 0; i < buttonCount; i++)
        {
            m_selectedPostionList.Add(new Vector2(0, m_protoHeight * i + spacingY * i)); //选中状态下的坐标 在上方
            if (i == 0)
            {
                m_normalPostionList.Add(new Vector2(0, 0)); //第一个按钮通常情况下也在上方
                continue;
            }

            m_normalPostionList.Add(new Vector2(0,
                movingSpacingY + i * m_protoHeight + (i - 1) * spacingY)); //通常情况下的坐标 在下方
        }
    }

    /// <summary>
    /// 设置默认坐标
    /// </summary>
    private void SetDefaultPostion()
    {
        for (var i = 0; i < buttonCount; i++)
        {
            mapCompButtons.TryGetValue(i, out var compButton); //按钮组件
            if (compButton == null)
            {
                return;
            }

            var compRectTransform = compButton.GetComponent<RectTransform>(); //transform组件
            compRectTransform.anchoredPosition3D = new Vector3(0, -m_normalPostionList[i].y, 0);
        }
    }

    #region Unity Life

    private void Awake()
    {
        //初始化
        Init();
        //按钮组创建
        ButtonGroupCreate();
        //设置默认坐标
        SetDefaultPostion();
    }

    private void OnDestroy()
    {
        RemoveAllListeners();
    }

    #endregion

    #region Editor

    private void OnValidate()
    {
        Init();
        SetDefaultPostion();
    }

    /// <summary>
    /// 编辑器修正内容节点尺寸
    /// </summary>
    protected void EditorFixContentSize()
    {
        Init();
        FixContentSize();
    }

    /// <summary>
    /// 编辑器预览按钮组
    /// </summary>
    protected void EditorShowButtons()
    {
        Init();
        EditorClearItems();
        ButtonGroupCreate();
        SetDefaultPostion();
    }

    /// <summary>
    /// 编辑器模拟选中某个index 运行中限定
    /// </summary>
    protected void EditorSelect()
    {
        Init();
        SelectIndex(currentSelectedIndex);
    }

    /// <summary>
    /// 编辑器清理item
    /// </summary>
    protected void EditorClearItems()
    {
        Init();
        for (var i = transform.childCount - 1; i >= 0; i--)
        {
            var compTransformChild = transform.GetChild(i);
            DestroyImmediate(compTransformChild.gameObject);
        }

        mapCompButtons.Clear();
    }

    #endregion
}

编辑器源码

// ******************************************************************
//       /\ /|       @file       MoveButtonListEditor.cs
//       \ V/        @brief      移动按钮列表组件编辑器
//       | "")       @author     Shadowrabbit, yingtu0401@gmail.com
//       /  |                    
//      /  \\        @Modified   2021-01-26 14:59:27
//    *(__\_\        @Copyright  Copyright (c) 2021, Shadowrabbit
// ******************************************************************

using System.Reflection;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(MoveButtonList))]
public class MoveButtonListEditor : Editor
{
     private MethodInfo m_editorClearItems; //清理items
    private MethodInfo m_editorFixContentSize; //修正内容节点尺寸
    private MethodInfo m_editorShowItems; //预览布局效果
    private MethodInfo m_editorSelect; //模拟选中某个item

    private void OnEnable()
    {
        //反射获取实例私有方法
        m_editorClearItems =
            target.GetType().GetMethod("EditorClearItems", BindingFlags.Instance | BindingFlags.NonPublic);
        m_editorFixContentSize = target.GetType()
            .GetMethod("EditorFixContentSize", BindingFlags.Instance | BindingFlags.NonPublic);
        m_editorShowItems = target.GetType()
            .GetMethod("EditorShowButtons", BindingFlags.Instance | BindingFlags.NonPublic);
        m_editorSelect = target.GetType()
            .GetMethod("EditorSelect", BindingFlags.Instance | BindingFlags.NonPublic);
    }

    private void OnDisable()
    {
        m_editorClearItems = null;
        m_editorFixContentSize = null;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        serializedObject.Update();
        if (GUILayout.Button("尺寸修正"))
        {
            m_editorFixContentSize?.Invoke(target, null);
            EditorUtility.SetDirty(target);
        }

        if (GUILayout.Button("清空子节点"))
        {
            m_editorClearItems?.Invoke(target, null);
            EditorUtility.SetDirty(target);
        }

        if (GUILayout.Button("预览"))
        {
            m_editorShowItems?.Invoke(target, null);
            EditorUtility.SetDirty(target);
        }

        GUILayout.Label("********运行中限定********");
        if (GUILayout.Button("模拟选中"))
        {
            m_editorSelect?.Invoke(target, null);
            EditorUtility.SetDirty(target);
        }


        serializedObject.ApplyModifiedProperties();
    }
}

配合状态按钮效果

在这里插入图片描述

状态按钮版源码

// ******************************************************************
//       /\ /|       @file       MoveStateButtonList.cs
//       \ V/        @brief      移动状态按钮列表组件
//       | "")       @author     Shadowrabbit, yingtu0401@gmail.com
//       /  |                    
//      /  \\        @Modified   2021-01-26 16:06:58
//    *(__\_\        @Copyright  Copyright (c) 2021, Shadowrabbit
// ******************************************************************

public class MoveStateButtonList : MoveButtonList
{
    protected override void ButtonGroupCreate()
    {
        //根据数量创建按钮
        for (var i = 0; i < buttonCount; i++)
        {
            var buttonIndex = i; //当前的按钮索引
            var objButton = Instantiate(protoButton, transform); //按钮克隆体
            objButton.name = protoButton.name + i; //设置名字
            var compStateButton = objButton.GetComponent<StateButton>(); //button组件
            compStateButton.IsSelect = i == 0; //stateButton状态
            compStateButton.onClick.AddListener(() =>
            {
                if (isMoving) return;
                onClick?.Invoke(buttonIndex);
                UpdateStateButtonListSelect(buttonIndex);
                SelectIndex(buttonIndex);
            }); //为每个按钮设置一个匿名点击方法 回调index
            mapCompButtons.Add(i, compStateButton);
        }
    }

    /// <summary>
    /// 设置stateButton列表选中状态
    /// </summary>
    /// <param name="_index"></param>
    private void UpdateStateButtonListSelect(int _index)
    {
        for (var i = 0; i < buttonCount; i++)
        {
            var compStateButton = mapCompButtons[i].GetComponent<StateButton>();
            compStateButton.IsSelect = _index == i;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值