1. 程式人生 > >Unity之圖片輪播元件實現

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_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工程下載地址如下:
連結:http://pan.baidu.com/s/1i4Rralf 密碼:7loe
連結如有私有,請及時聯絡補充

以上知識分享,如有錯誤,歡迎指出,共同學習,共同進步