Unity實(shí)現(xiàn)圖片輪播組件
游戲中有時(shí)候會(huì)見到圖片輪播的效果,那么這里就自己封裝了一個(gè),包括自動(dòng)輪播、切頁按鈕控制、頁碼下標(biāo)更新、滑動(dòng)輪播、切頁后的回調(diào)等等 。
下面,先上一個(gè)簡(jiǎn)陋的gif動(dòng)態(tài)效果圖

從圖中可以看出,該示例包括了三張圖片的輪播,左右分別是上一張和下一張的按鈕,右下角顯示了當(dāng)前是第幾章的頁碼下標(biāo)。
直接上腳本:
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] //編輯模式下可執(zhí)行
[DisallowMultipleComponent] //不可重復(fù)
[RequireComponent(typeof(RectTransform))] //依賴于RectTransform組件
public class Slideshow : UIBehaviour,IPointerDownHandler,IPointerUpHandler
{
public enum MovementType
{
/// <summary>
/// 循環(huán)
/// </summary>
Circulation, //循環(huán),輪播到最后一頁之后,直接回到第一頁
/// <summary>
/// 來回往復(fù)
/// </summary>
PingPong, //來回往復(fù),輪播到最后一頁之后,倒序輪播,到第一頁之后,同理
}
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>
/// 自動(dòng)輪播時(shí)長(zhǎng)
/// </summary>
[SerializeField]
private float m_showTime = 2.0f;
public float ShowTime { get { return m_showTime; } set { m_showTime = value; } }
/// <summary>
/// 是否自動(dòng)輪播
/// </summary>
[SerializeField]
private bool m_autoSlide = false;
public bool AutoSlide { get { return m_autoSlide; }set { m_autoSlide = value; } }
/// <summary>
/// 自動(dòng)輪播方向,-1表示向左,1表示向右
/// </summary>
private MoveDir m_autoSlideDir = MoveDir.Right;
/// <summary>
/// 是否允許拖動(dòng)切頁
/// </summary>
[SerializeField]
private bool m_allowDrag = true;
public bool AllowDrag { get { return m_allowDrag; }set { m_allowDrag = value; } }
/// <summary>
/// 當(dāng)前顯示頁的頁碼,下標(biāo)從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數(shù)目
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)
{
//目標(biāo)頁與當(dāng)前頁是同一頁
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)
{
//執(zhí)行回調(diào)
m_onValueChanged.Invoke(m_pageToggleList[m_curPageIndex].gameObject);
}
}
/// <summary>
/// 根據(jù)頁碼更新切頁按鈕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; //重新計(jì)時(shí)
switch (m_movement)
{
case MovementType.Circulation:
SwitchToPageNum((m_curPageIndex + 1) % m_itemNum);
break;
case MovementType.PingPong:
//該模式下,會(huì)自動(dòng)隱藏切頁按鈕
SwitchToPageNum(m_curPageIndex + 1);
break;
default:
break;
}
Debug.Log(m_content.localPosition);
}
private void OnLastPageButtonClick()
{
m_time = Time.time; //重新計(jì)時(shí)
switch (m_movement)
{
case MovementType.Circulation:
SwitchToPageNum((m_curPageIndex + m_itemNum - 1) % m_itemNum);
break;
case MovementType.PingPong:
//該模式下,會(huì)自動(dòng)隱藏切頁按鈕
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>
/// 切頁后回調(diào)函數(shù)
/// </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;
}
}
}
}
}
}
這里提供了一個(gè)枚舉MovementType,該枚舉定義了兩種循環(huán)方式,其中Circulation循環(huán),是指輪播到最后一頁之后,直接回到第一頁;而PingPong相信大家你熟悉了,就是來回往復(fù)的。
其中還提供了對(duì)每張圖顯示的時(shí)長(zhǎng)進(jìn)行設(shè)置,還有是否允許自動(dòng)輪播的控制,是否允許拖動(dòng)切頁控制,等等。。其實(shí)將圖片作為輪播子元素只是其中之一而已,完全可以將ScrollRect作為輪播子元素,這樣每個(gè)子元素又可以滑動(dòng)閱覽了。
這里還提供了兩個(gè)編輯器腳本,一個(gè)是SlideshowEditor(依賴Slideshow組件),另一個(gè)是給用戶提供菜單用的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();
}
}
}
這兩個(gè)腳本中使用了一些拓展編輯器的知識(shí),后續(xù)在另外寫博客介紹 。
其中腳本CreateSlideshow中使用UGUI源碼中的DefaultControls腳本里的方法,有興趣可以去下載查閱。
Demo工程下載地址
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#如何從byte[]中直接讀取Structure實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于利用C#如何從byte[]里直接讀取Structure的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
C#內(nèi)置隊(duì)列類Queue用法實(shí)例
這篇文章主要介紹了C#內(nèi)置隊(duì)列類Queue用法,實(shí)例分析了C#內(nèi)置隊(duì)列的添加、移除等相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04
C#利用ScriptControl動(dòng)態(tài)執(zhí)行JS和VBS腳本
C#中利用ScriptControl動(dòng)態(tài)執(zhí)行JS和VBS腳本的實(shí)現(xiàn)方法,需要的朋友可以參考下2013-04-04
Unity3D啟動(dòng)外部程序并傳遞參數(shù)的實(shí)現(xiàn)
這篇文章主要介紹了Unity3D啟動(dòng)外部程序并傳遞參數(shù)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04
基于C#實(shí)現(xiàn)自定義計(jì)算的Excel數(shù)據(jù)透視表
數(shù)據(jù)透視表(Pivot?Table)是一種數(shù)據(jù)分析工具,通常用于對(duì)大量數(shù)據(jù)進(jìn)行匯總、分析和展示,本文主要介紹了C#實(shí)現(xiàn)自定義計(jì)算的Excel數(shù)據(jù)透視表的相關(guān)知識(shí),感興趣的可以了解下2023-12-12
C#實(shí)現(xiàn)語音播報(bào)功能的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何使用C#實(shí)現(xiàn)語音播報(bào)功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2024-02-02

