Unity实现图片轮播组件
游戏中有时候会见到图片轮播的效果,那么这里就自己封装了一个,包括自动轮播、切页按钮控制、页码下标更新、滑动轮播、切页后的回调等等 。
下面,先上一个简陋的gif动态效果图
从图中可以看出,该示例包括了三张图片的轮播,左右分别是上一张和下一张的按钮,右下角显示了当前是第几章的页码下标。
直接上脚本:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.UI; namespace UnityEngine.UI { [AddComponentMenu("UI/Slidershow", 39)] //添加菜单 [ExecuteInEditMode] //编辑模式下可执行 [DisallowMultipleComponent] //不可重复 [RequireComponent(typeof(RectTransform))] //依赖于RectTransform组件 public class Slideshow : UIBehaviour,IPointerDownHandler,IPointerUpHandler { public enum MovementType { /// <summary> /// 循环 /// </summary> Circulation, //循环,轮播到最后一页之后,直接回到第一页 /// <summary> /// 来回往复 /// </summary> PingPong, //来回往复,轮播到最后一页之后,倒序轮播,到第一页之后,同理 } public enum MoveDir { Left, Right, } [SerializeField] private MovementType m_movement = MovementType.Circulation; public MovementType Movement { get { return m_movement; } set { m_movement = value; } } [SerializeField] private RectTransform m_content; public RectTransform Content { get { return m_content; } set { m_content = value; } } [SerializeField] private Button m_lastPageButton; public Button LastPageButton { get { return m_lastPageButton; } set { m_lastPageButton = value; } } [SerializeField] private Button m_nextPageButton; public Button NextPageButton { get { return m_nextPageButton; } set { m_nextPageButton = value; } } /// <summary> /// 自动轮播时长 /// </summary> [SerializeField] private float m_showTime = 2.0f; public float ShowTime { get { return m_showTime; } set { m_showTime = value; } } /// <summary> /// 是否自动轮播 /// </summary> [SerializeField] private bool m_autoSlide = false; public bool AutoSlide { get { return m_autoSlide; }set { m_autoSlide = value; } } /// <summary> /// 自动轮播方向,-1表示向左,1表示向右 /// </summary> private MoveDir m_autoSlideDir = MoveDir.Right; /// <summary> /// 是否允许拖动切页 /// </summary> [SerializeField] private bool m_allowDrag = true; public bool AllowDrag { get { return m_allowDrag; }set { m_allowDrag = value; } } /// <summary> /// 当前显示页的页码,下标从0开始 /// </summary> private int m_curPageIndex = 0; public int CurPageIndex { get { return m_curPageIndex; } } /// <summary> /// 最大页码 /// </summary> private int m_maxPageIndex = 0; public int MaxPageIndex { get { return m_maxPageIndex; } } /// <summary> /// 圆圈页码ToggleGroup /// </summary> [SerializeField] private ToggleGroup m_pageToggleGroup; public ToggleGroup PageToggleGroup { get { return m_pageToggleGroup; } set { m_pageToggleGroup = value; } } /// <summary> /// 圆圈页码Toggle List /// </summary> private List<Toggle> m_pageToggleList; public List<Toggle> PageToggleLise { get { return m_pageToggleList; }} //item数目 private int m_itemNum = 0; public int ItemNum { get { return m_itemNum; } } //以Toggle为Key,返回页码 private Dictionary<Toggle, int> m_togglePageNumDic = null; private float m_time = 0f; private List<float> m_childItemPos = new List<float>(); private GridLayoutGroup m_grid = null; protected override void Awake() { base.Awake(); if (null == m_content) { throw new Exception("Slideshow content is null"); } else { m_grid = m_content.GetComponent<GridLayoutGroup>(); if (m_grid == null) { throw new Exception("Slideshow content is miss GridLayoutGroup Component"); } InitChildItemPos(); } if (null != m_lastPageButton) { m_lastPageButton.onClick.AddListener(OnLastPageButtonClick); } if (null != m_nextPageButton) { m_nextPageButton.onClick.AddListener(OnNextPageButtonClick); } if (null != m_pageToggleGroup) { int toggleNum = m_pageToggleGroup.transform.childCount; if (toggleNum > 0) { m_pageToggleList = new List<Toggle>(); m_togglePageNumDic = new Dictionary<Toggle, int>(); for (int i = 0; i < toggleNum; i++) { Toggle childToggle = m_pageToggleGroup.transform.GetChild(i).GetComponent<Toggle>(); if (null != childToggle) { m_pageToggleList.Add(childToggle); m_togglePageNumDic.Add(childToggle, i); childToggle.onValueChanged.AddListener(OnPageToggleValueChanged); } } m_itemNum = m_pageToggleList.Count; m_maxPageIndex = m_pageToggleList.Count - 1; } } UpdateCutPageButtonActive(m_curPageIndex); } private void InitChildItemPos() { int childCount = m_content.transform.childCount; float cellSizeX = m_grid.cellSize.x; float spacingX = m_grid.spacing.x; float posX = -cellSizeX * 0.5f; m_childItemPos.Add(posX); for (int i = 1; i < childCount; i++) { posX -= cellSizeX + spacingX; m_childItemPos.Add(posX); } } private void OnPageToggleValueChanged(bool ison) { if (ison) { Toggle activeToggle = GetActivePageToggle(); if (m_togglePageNumDic.ContainsKey(activeToggle)) { int page = m_togglePageNumDic[activeToggle]; SwitchToPageNum(page); } } } private Toggle GetActivePageToggle() { if (m_pageToggleGroup == null || m_pageToggleList == null || m_pageToggleList.Count <= 0) { return null; } for (int i = 0; i < m_pageToggleList.Count; i++) { if (m_pageToggleList[i].isOn) { return m_pageToggleList[i]; } } return null; } /// <summary> /// 切换至某页 /// </summary> /// <param name="pageNum">页码</param> private void SwitchToPageNum(int pageNum) { if (pageNum < 0 || pageNum > m_maxPageIndex) { throw new Exception("page num is error"); } if (pageNum == m_curPageIndex) { //目标页与当前页是同一页 return; } m_curPageIndex = pageNum; if (m_movement == MovementType.PingPong) { UpdateCutPageButtonActive(m_curPageIndex); } Vector3 pos = m_content.localPosition; m_content.localPosition = new Vector3(m_childItemPos[m_curPageIndex], pos.y, pos.z); m_pageToggleList[m_curPageIndex].isOn = true; if (m_onValueChanged != null) { //执行回调 m_onValueChanged.Invoke(m_pageToggleList[m_curPageIndex].gameObject); } } /// <summary> /// 根据页码更新切页按钮active /// </summary> /// <param name="pageNum"></param> private void UpdateCutPageButtonActive(int pageNum) { if (pageNum == 0) { UpdateLastButtonActive(false); UpdateNextButtonActive(true); } else if (pageNum == m_maxPageIndex) { UpdateLastButtonActive(true); UpdateNextButtonActive(false); } else { UpdateLastButtonActive(true); UpdateNextButtonActive(true); } } private void OnNextPageButtonClick() { m_time = Time.time; //重新计时 switch (m_movement) { case MovementType.Circulation: SwitchToPageNum((m_curPageIndex + 1) % m_itemNum); break; case MovementType.PingPong: //该模式下,会自动隐藏切页按钮 SwitchToPageNum(m_curPageIndex + 1); break; default: break; } Debug.Log(m_content.localPosition); } private void OnLastPageButtonClick() { m_time = Time.time; //重新计时 switch (m_movement) { case MovementType.Circulation: SwitchToPageNum((m_curPageIndex + m_itemNum - 1) % m_itemNum); break; case MovementType.PingPong: //该模式下,会自动隐藏切页按钮 SwitchToPageNum(m_curPageIndex - 1); break; default: break; } } private void UpdateLastButtonActive(bool activeSelf) { if (null == m_lastPageButton) { throw new Exception("Last Page Button is null"); } bool curActive = m_lastPageButton.gameObject.activeSelf; if (curActive != activeSelf) { m_lastPageButton.gameObject.SetActive(activeSelf); } } private void UpdateNextButtonActive(bool activeSelf) { if (null == m_nextPageButton) { throw new Exception("Next Page Button is null"); } bool curActive = m_nextPageButton.gameObject.activeSelf; if (curActive != activeSelf) { m_nextPageButton.gameObject.SetActive(activeSelf); } } private Vector3 m_originDragPos = Vector3.zero; private Vector3 m_desDragPos = Vector3.zero; private bool m_isDrag = false; public void OnPointerDown(PointerEventData eventData) { if (!m_allowDrag) { return; } if (eventData.button != PointerEventData.InputButton.Left) { return; } if (!IsActive()) { return; } m_isDrag = true; m_originDragPos = eventData.position; } public void OnPointerUp(PointerEventData eventData) { m_desDragPos = eventData.position; MoveDir dir = MoveDir.Right; if (m_desDragPos.x < m_originDragPos.x) { dir = MoveDir.Left; } switch (dir) { case MoveDir.Left: if (m_movement == MovementType.Circulation || (m_movement == MovementType.PingPong && m_curPageIndex != 0)) { OnLastPageButtonClick(); } break; case MoveDir.Right: if (m_movement == MovementType.Circulation || (m_movement == MovementType.PingPong && m_curPageIndex != m_maxPageIndex)) { OnNextPageButtonClick(); } break; } m_isDrag = false; } /// <summary> /// 切页后回调函数 /// </summary> [Serializable] public class SlideshowEvent : UnityEvent<GameObject> { } [SerializeField] private SlideshowEvent m_onValueChanged = new SlideshowEvent(); public SlideshowEvent OnValueChanged { get { return m_onValueChanged; } set { m_onValueChanged = value; } } public override bool IsActive() { return base.IsActive() && m_content != null; } private void Update() { if (m_autoSlide && !m_isDrag) { if (Time.time > m_time + m_showTime) { m_time = Time.time; switch (m_movement) { case MovementType.Circulation: m_autoSlideDir = MoveDir.Right; break; case MovementType.PingPong: if (m_curPageIndex == 0) { m_autoSlideDir = MoveDir.Right; } else if (m_curPageIndex == m_maxPageIndex) { m_autoSlideDir = MoveDir.Left; } break; } switch (m_autoSlideDir) { case MoveDir.Left: OnLastPageButtonClick(); break; case MoveDir.Right: OnNextPageButtonClick(); break; } } } } } }
这里提供了一个枚举MovementType,该枚举定义了两种循环方式,其中Circulation循环,是指轮播到最后一页之后,直接回到第一页;而PingPong相信大家你熟悉了,就是来回往复的。
其中还提供了对每张图显示的时长进行设置,还有是否允许自动轮播的控制,是否允许拖动切页控制,等等。。其实将图片作为轮播子元素只是其中之一而已,完全可以将ScrollRect作为轮播子元素,这样每个子元素又可以滑动阅览了。
这里还提供了两个编辑器脚本,一个是SlideshowEditor(依赖Slideshow组件),另一个是给用户提供菜单用的CreateSlideshow,代码分别如下:
using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class CreateSlideshow : Editor { private static GameObject m_slideshowPrefab = null; private static GameObject m_canvas = null; [MenuItem("GameObject/UI/Slideshow")] static void CreateSlideshowUI(MenuCommand menuCommand) { if (null == m_slideshowPrefab) { m_slideshowPrefab = Resources.Load<GameObject>("Slideshow"); if (null == m_slideshowPrefab) { Debug.LogError("Prefab Slideshow is null"); return; } } m_canvas = menuCommand.context as GameObject; if (m【文章来自:http://www.yidunidc.com/gfcdn.html 欢迎留下您的宝贵建议】_canvas == null || m_canvas.GetComponentInParent<Canvas>() == null) { m_canvas = GetOrCreateCanvasGameObject(); } GameObject go = GameObject.Instantiate(m_slideshowPrefab, m_canvas.transform); go.transform.localPosition = Vector3.zero; go.name = "Slideshow"; Selection.activeGameObject = go; } static public GameObject GetOrCreateCanvasGameObject() { GameObject selectedGo = Selection.activeGameObject; Canvas canvas = (selectedGo != null) ? selectedGo.GetComponentInParent<Canvas>() : null; if (canvas != null && canvas.gameObject.activeInHierarchy) return canvas.gameObject; canvas = Object.FindObjectOfType(typeof(Canvas)) as Canvas; if (canvas != null && canvas.gameObject.activeInHierarchy) return canvas.gameObject; return CreateCanvas(); } public static GameObject CreateCanvas() { var root = new GameObject("Canvas"); root.layer = LayerMask.NameToLayer("UI"); Canvas canvas = root.AddComponent<Canvas>(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; root.AddComponent<CanvasScaler>(); root.AddComponent<GraphicRaycaster>(); Undo.RegisterCreatedObjectUndo(root, "Create " + root.name); CreateEventSystem(); return root; } public static void CreateEventSystem() { var esys = Object.FindObjectOfType<EventSystem>(); if (esys == null) { var eventSystem = new GameObject("EventSystem"); GameObjectUtility.SetParentAndAlign(eventSystem, null); esys = eventSystem.AddComponent<EventSystem>(); eventSystem.AddComponent<StandaloneInputModule>(); Undo.RegisterCreatedObjectUndo(eventSystem, "Create " + eventSystem.name); } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor.Advertisements; using UnityEngine.UI; namespace UnityEditor.UI { [CustomEditor(typeof(Slideshow), true)] public class SlideshowEditor : Editor { SerializedProperty m_movement; SerializedProperty m_content; SerializedProperty m_lastPageButton; SerializedProperty m_nextPageButton; SerializedProperty m_showTime; SerializedProperty m_pageToggleGroup; SerializedProperty m_onValueChanged; SerializedProperty m_allowDrag; SerializedProperty m_autoSlide; protected virtual void OnEnable() { m_movement = serializedObject.FindProperty("m_movement"); m_content = serializedObject.FindProperty("m_content"); m_lastPageButton = serializedObject.FindProperty("m_lastPageButton"); m_nextPageButton = serializedObject.FindProperty("m_nextPageButton"); m_showTime = serializedObject.FindProperty("m_showTime"); m_pageToggleGroup = serializedObject.FindProperty("m_pageToggleGroup"); m_onValueChanged = serializedObject.FindProperty("m_onValueChanged"); m_allowDrag = serializedObject.FindProperty("m_allowDrag"); m_autoSlide = serializedObject.FindProperty("m_autoSlide"); } public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(m_movement); EditorGUILayout.PropertyField(m_content); EditorGUILayout.PropertyField(m_lastPageButton); EditorGUILayout.PropertyField(m_nextPageButton); EditorGUILayout.PropertyField(m_allowDrag); EditorGUILayout.PropertyField(m_autoSlide); EditorGUILayout.PropertyField(m_showTime); EditorGUILayout.PropertyField(m_pageToggleGroup); EditorGUILayout.Space(); EditorGUILayout.PropertyField(m_onValueChanged); //不加这句代码,在编辑模式下,无法将物体拖拽赋值 serializedObject.ApplyModifiedProperties(); } } }
这两个脚本中使用了一些拓展编辑器的知识,后续在另外写博客介绍 。
其中脚本CreateSlideshow中使用UGUI源码中的DefaultControls脚本里的方法,有兴趣可以去下载查阅。
Demo工程下载地址
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持海外IDC网。