欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Unity实现图片轮播组件

程序员文章站 2023-11-22 16:54:28
游戏中有时候会见到图片轮播的效果,那么这里就自己封装了一个,包括自动轮播、切页按钮控制、页码下标更新、滑动轮播、切页后的回调等等 。 下面,先上一个简陋的gif动态效...

游戏中有时候会见到图片轮播的效果,那么这里就自己封装了一个,包括自动轮播、切页按钮控制、页码下标更新、滑动轮播、切页后的回调等等 。

下面,先上一个简陋的gif动态效果图

Unity实现图片轮播组件

从图中可以看出,该示例包括了三张图片的轮播,左右分别是上一张和下一张的按钮,右下角显示了当前是第几章的页码下标。

直接上脚本:

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

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