Unity实现图片轮播组件
程序员文章站
2023-11-22 16:54:28
游戏中有时候会见到图片轮播的效果,那么这里就自己封装了一个,包括自动轮播、切页按钮控制、页码下标更新、滑动轮播、切页后的回调等等 。
下面,先上一个简陋的gif动态效...
游戏中有时候会见到图片轮播的效果,那么这里就自己封装了一个,包括自动轮播、切页按钮控制、页码下标更新、滑动轮播、切页后的回调等等 。
下面,先上一个简陋的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工程下载地址
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。