效果图
源码
// ******************************************************************
// /\ /| @file MoveButtonlist.cs
// \ V/ @brief 移动按钮列表组件
// | "") @author Shadowrabbit, [email protected]
// / |
// / \\ @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, [email protected]
// / |
// / \\ @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, [email protected]
// / |
// / \\ @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;
}
}
}
文章评论