Unity之图片轮播组件实现

18 篇文章 3 订阅

博客迁移

个人博客站点,欢迎访问,www.jiingfengji.tech

正文

游戏中有时候会见到图片轮播的效果,那么这里就自己封装了一个,包括自动轮播、切页按钮控制、页码下标更新、滑动轮播、切页后的回调等等
下面,先上一个简陋的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
链接如有私有,请及时联系补充

以上知识分享,如有错误,欢迎指出,共同学习,共同进步

  • 7
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
unity图片轮播切换是指在unity中使用某个组件实现多张图片轮流展示的功能。在unity中,我们可以通过使用UGUI的Image组件实现这个功能。具体实现步骤如下: 1.创建一个新的GameObject,并给其命名为Slider(或其它你想要的名称),然后将Canvas组件拖到该GameObject上。 2.在Slider中添加Scroll Rect组件和Image组件。设置Image中的Sprite为你想要轮播图片,并设置Scroll Rect中的Content的大小和位置来适应你的图片。 3.如果你要设置自动轮播,在Slider的GameObject中,添加一个空的脚本SliderScript,并在该脚本的Start函数中添加InvokeRepeating函数,可以实现定时切换图片的功能。比如,每隔2秒切换一次图片,代码如下: ``` using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SliderScript : MonoBehaviour { //定义一个变量用来存储图片 public Sprite[] images; //定义一个变量记录图片的索引 private int index = 0; //定义一下图片切换的时间 public float intervalTime = 2f; //定义一下滑动距离 private float slideDistance; //获取RectTransfrom组件 private RectTransform rectTransform; //获取ScrollRect组件 private ScrollRect scrollRect; // Start is called before the first frame update void Start() { //获取RectTransfrom组件 rectTransform = GetComponent<RectTransform>(); //获取ScrollRect组件 scrollRect = GetComponent<ScrollRect>(); //计算一下每张图片的宽度 slideDistance = rectTransform.rect.width / images.Length; //设置自动轮播 InvokeRepeating("ChangeImage", intervalTime, intervalTime); } //切换图片 private void ChangeImage() { //如果当前已经到了最后一张图片,则回到第一张 if (index == images.Length - 1) { index = 0; } else { index++; } //根据索引计算目标位置 float targetPosition = index * slideDistance; //设置滚动位置 scrollRect.horizontalNormalizedPosition = Mathf.Lerp(scrollRect.horizontalNormalizedPosition, targetPosition, Time.deltaTime * 10f); } } ``` 4.如果你想要使用左右箭头来控制图片的切换,可以在Slider中添加两个空的GameObject,并分别将它们命名为Prev和Next。然后,为每个GameObject添加Button组件,并将每个Button的OnClick属性指定为SliderScript中同名的两个公共方法Prev和Next。 5.在SliderScript脚本中声明两个公共方法Prev和Next,并在这两个方法中实现左右箭头点击时的图片切换逻辑。比如,点击左箭头显示前一张图片,点击右箭头显示后一张图片,代码如下: ``` //显示前一张图片 public void Prev() { //如果当前已经是第一张图片,则切换到最后一张 if (index == 0) { index = images.Length - 1; } else { index--; } //根据索引计算目标位置 float targetPosition = index * slideDistance; //设置滚动位置 scrollRect.horizontalNormalizedPosition = targetPosition; } //显示后一张图片 public void Next() { //如果当前已经是最后一张图片,则切换到第一张 if (index == images.Length - 1) { index = 0; } else { index++; } //根据索引计算目标位置 float targetPosition = index * slideDistance; //设置滚动位置 scrollRect.horizontalNormalizedPosition = targetPosition; } ``` 6.最后,在unity中播放游戏,就可以看到图片自动轮播和左右箭头控制切换图片的效果了。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值