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工程下载地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • UGUI轮播图组件实现方法详解

    本文实例为大家分享了UGUI轮播图组件实现的具体代码,供大家参考,具体内容如下 要用到,于是就自已做了一个,自认为封装上还是OK的,开发于unity5.1.2. 支持自动轮播.手势切换.代码调用切换,支持水平和竖直两个方向以及正负方向轮播,轮播索引改变有回调可以用,也可以获取到当前处于正中的子元素. 要注意的是,向轮播列表中加入新元素不能直接setparent,要调用该组件的AddChild方法 下面是鄙人的代码: /// 主要关注属性.事件及函数: /// public int Current

  • Unity实现图片轮播组件

    游戏中有时候会见到图片轮播的效果,那么这里就自己封装了一个,包括自动轮播.切页按钮控制.页码下标更新.滑动轮播.切页后的回调等等 . 下面,先上一个简陋的gif动态效果图 从图中可以看出,该示例包括了三张图片的轮播,左右分别是上一张和下一张的按钮,右下角显示了当前是第几章的页码下标. 直接上脚本: using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using U

  • 值得分享的JavaScript实现图片轮播组件

    本文实例为大家分享了JavaScript实现图片轮播组件的使用方法,供大家参考,具体内容如下 效果: 自动循环播放图片,下方有按钮可以切换到对应图片. 添加一个动画来实现图片切换. 鼠标停在图片上时,轮播停止,出现左右两个箭头,点击可以切换图片. 鼠标移开图片区域时,从当前位置继续轮播. 提供一个接口,可以设置轮播方向,是否循环,间隔时间. 点击查看demo 对HTML.CSS的要求: <div class="carousel-box"> <div class=&qu

  • 利用Vue实现移动端图片轮播组件的方法实例

    前言 轮播图的插件也有很多,用jQuery写起来也不难,这里分享的是关于利用Vue实现移动端图片轮播组件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: wc-swiper 基于 Vue 的移动端的图片轮播组件. Why 之前一直在用 vue-awesome-swiper, 功能很齐全, 但是唯一的问题就是体积比较大. 我只是想要一个简单的图片轮播, 但是却要引入 100多k 大小的文件, 这样是不对的. 特点 支持自动播放 & 无限轮播 (loop) 效果 支持用户滑

  • Bootstrap图片轮播组件使用实例解析

    使用Bootstrap来编写图片轮播组件Carousel,则能够节约很多时间,图片轮播组件是一个在网页中很常见的技术,但是如果直接编写的话,需要很长的JavaScript编码,同时也不好控制大小.  同时说一下,Carousel这个词的本义是回旋木马. 一.基本目标 在网页编写多张图片的轮播组件Carousel,鼠标放在上面自带悬停效果,并且在每张图片下面配有图片说明.  由于笔者的电脑视频录制软件比较渣,也觉得没必要画太多时间在这上面,觉得只要能说明问题就行,所以下面的GIF失色比较严重,但是

  • JavaScript实现图片轮播组件代码示例

    本文介绍了JavaScript实现图片轮播组件,废话不多说了直接看下面: 效果: 自动循环播放图片,下方有按钮可以切换到对应图片. 添加一个动画来实现图片切换. 鼠标停在图片上时,轮播停止,出现左右两个箭头,点击可以切换图片. 鼠标移开图片区域时,从当前位置继续轮播. 提供一个接口,可以设置轮播方向,是否循环,间隔时间. 对HTML.CSS的要求: <div class="carousel-box"> <div class="carousel"&g

  • Bootstrap图片轮播组件Carousel使用方法详解

    Bootstrap是Twitter推出的一个开源的用于前端开发的工具包.它由Twitter的设计师Mark Otto和Jacob Thornton合作开发,是一个CSS/HTML框架.Bootstrap提供了优雅的HTML和CSS规范,它即是由动态CSS语言Less写成.Bootstrap一经推出后颇受欢迎,一直是GitHub上的热门开源项目,包括NASA的MSNBC(微软全国广播公司)的Breaking News都使用了该项目. 图片轮播组件是一个在网页中很常见的技术,但是如果直接编写的话,需

  • 微信小程序图片轮播组件gallery slider使用方法详解

    本文实例为大家分享了微信小程序图片轮播组件的具体代码,供大家参考,具体内容如下 先上效果图: wxml <scroll-view scroll-y="true" style="height:200px" class="page-body" bindscrolltolower="loadMore"> <view class="swiper"> <swiper class=&quo

  • 使用Vue制作图片轮播组件思路详解

    之前一直都没有认真的写过一个组件.以前在写业务代码的过程中,都是用的别人封装好的组件,这次尝试着写了一个图片轮播组件,虽然比不上知名的轮播组件,但它的功能基本完整,而且在写这个组件的过程中,学的东西也很多,在这里也给大家分享出来,如有疏漏,欢迎指正! 在制作这个组件之前,笔者google了不少关于轮播的文章,发现实现一个轮播的思路虽然各有不同,但是大的逻辑其实差不多,本文主要依据慕课网上焦点轮播图特效这节课,不过慕课网主要用原生JS写,而笔者则用Vue进行了重构,并且进行了一点修改.完成后的组件

  • jquery无缝图片轮播组件封装

    图片轮播在我们的前端开发中是非常常见的,下面是自己写的一个图片轮播组件,支持自动轮播,手动轮播,无缝衔接. dom结构 首先是dom结构,将所有内容放入一个大盒子,应用ul标签存放图片列表,圆点定位图片位置. <div id="box"> <ul id="banners"> <li class="banners-img"><img src="img/DSC_1913.JPG" />

  • Angular2利用组件与指令实现图片轮播组件

    前言 如果说模块系统是Angular2的灵魂,那其组件体系就是其躯体,在模块的支持下渲染出所有用户直接看得见的东西,一个项目最表层的东西就是组件呈现的视图. 而除了直接看的见的躯体之外,一个完整的"生物"还需要有感觉器官,用来感知外界与其的交互,这就是指令要做的事情. 本文将使用Angular2提供的强大的组件与指令等功能制作出一个简单的图片轮播控件,继续上文打的比方的话这就像是一个"器官",功能是呈现图片,并感知用户的点击或手势来切换图片. 一.创建组件 结束上文

随机推荐