NGUI所见即所得之UIGrid & UITable

尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com

 

       你是不是对 UIGrid 和 UITable 定位计算方法还模糊不清,那么这篇文章就是你需要的。

       NGUI 提供了 Grid 和 Table 组件,支持的参数很少,功能也很鸡肋,完全不能像 CSS 的 Box 模型那样随心所欲的布局,而且使用 UIGrid 和 UITable 的时候经常会相对 UIGrid 和 UITable 挂载的 Transform 出现偏移,之前使用的时候,都是根据经验来规避的。今天打算把UIGrid 和 UITable 的排列规则看下, 才形成此文。

 

        UIGrid 和 UITable 的原理很简单,对子 Transform 的 List 进行排序,然后更加不同的规则进行定位排列(UIGrid 和 UITable 还是有很大不同的)。

 

排序(Sort)

        UIGrid 和 UITable 定义了5种排列方式(其实是3种,None默认不排序即Transform的默认排序,Custom虽然提供virtual 可以重载):

C#代码   收藏代码
  1. public enum Sorting  
  2. {  
  3.     None,  
  4.     Alphabetic,  
  5.     Horizontal,  
  6.     Vertical,  
  7.     Custom,  
  8. }  

         对应的三种排序方法:集 Alphabetic 按照名字字符串排序,Horizontal 和 Vertical 按照localPosition 进行的排序

C#代码   收藏代码
  1. static public int SortByName (Transform a, Transform b) { return string.Compare(a.name, b.name); }  
  2. static public int SortHorizontal (Transform a, Transform b) { return a.localPosition.x.CompareTo(b.localPosition.x); }  
  3. static public int SortVertical (Transform a, Transform b) { return b.localPosition.y.CompareTo(a.localPosition.y); }  

         这里说下,虽然提供了Custom方式,第一感觉NGUI的developer考虑很周到,但是提供的确实重载 virtual 函数的方式,D.S.Qiu 觉得这种方式太不好了,为了一个方法就要写一个 子类去重载,个人觉得指定一个委托,扩展起来会更直观,但这一要求开发者一开始就得指定这个 Custom Sort Delegate。

 

UIGrid定位原理

        下面这段代码是 Reposition() 的一部分,原理很简单:根据定义的cellHeight 和cellWidth 来调整子 Transform 的 localPosition。这里还是吐槽下:Reposition() 的代码太多容易了,至少我看到了这段代码在 Reposition 中出现了两次,完全多余,其实就是处理流程就应该是: 先获取所有子 Transform List ,然后对List 排序,最后就是下面这段定位代码了。

C#代码   收藏代码
  1. for (int i = 0, imax = list.Count; i < imax; ++i)  
  2. {  
  3.     Transform t = list[i];  
  4.     if (!NGUITools.GetActive(t.gameObject) && hideInactive) continue;  
  5.     float depth = t.localPosition.z;  
  6.     Vector3 pos = (arrangement == Arrangement.Horizontal) ?  
  7.         new Vector3(cellWidth * x, -cellHeight * y, depth) :  
  8.         new Vector3(cellWidth * y, -cellHeight * x, depth);  
  9.   
  10.     if (animateSmoothly && Application.isPlaying)  
  11.     {  
  12.         SpringPosition.Begin(t.gameObject, pos, 15f).updateScrollView = true;  
  13.     }  
  14.     else t.localPosition = pos;  
  15.   
  16.     if (++x >= maxPerLine && maxPerLine > 0)  
  17.     {  
  18.         x = 0;  
  19.         ++y;  
  20.     }  
  21. }  

 

UITable 定位原理

       UITable 的定位方法在 ReositionVaribleSize 中,跟UIGrid 最大不同点是:UIGrid 只根据定义的cellHeight 和 cellWidth 来计算位置,UITable 根据“内容“(UIWidget)来计算位置的。

C#代码   收藏代码
  1. protected void RepositionVariableSize (List<Transform> children)  
  2. {  
  3.     float xOffset = 0;  
  4.     float yOffset = 0;  
  5.   
  6.     int cols = columns > 0 ? children.Count / columns + 1 : 1;  
  7.     int rows = columns > 0 ? columns : children.Count;  
  8.   
  9.     Bounds[,] bounds = new Bounds[cols, rows];  
  10.     Bounds[] boundsRows = new Bounds[rows];  
  11.     Bounds[] boundsCols = new Bounds[cols];  
  12.   
  13.     int x = 0;  
  14.     int y = 0;  
  15.         //这个循环计算每行,每列,每个cell 的内容的编辑 Bounds  
  16.     for (int i = 0, imax = children.Count; i < imax; ++i)  
  17.     {  
  18.         Transform t = children[i];  
  19.         Bounds b = NGUIMath.CalculateRelativeWidgetBounds(t, !hideInactive);  
  20.   
  21.         Vector3 scale = t.localScale;  
  22.         b.min = Vector3.Scale(b.min, scale);  
  23.         b.max = Vector3.Scale(b.max, scale);  
  24.         bounds[y, x] = b;  
  25.   
  26.         boundsRows[x].Encapsulate(b);  
  27.         boundsCols[y].Encapsulate(b);  
  28.   
  29.         if (++x >= columns && columns > 0)  
  30.         {  
  31.             x = 0;  
  32.             ++y;  
  33.         }  
  34.     }  
  35.   
  36.     x = 0;  
  37.     y = 0;  
  38.         //计算位置  
  39.     for (int i = 0, imax = children.Count; i < imax; ++i)  
  40.     {  
  41.         Transform t = children[i];  
  42.         Bounds b = bounds[y, x];  
  43.         Bounds br = boundsRows[x];  
  44.         Bounds bc = boundsCols[y];  
  45.   
  46.         Vector3 pos = t.localPosition;  
  47.         pos.x = xOffset + b.extents.x - b.center.x;  
  48.         pos.x += b.min.x - br.min.x + padding.x;   //以每列最右边的x值为标准,即保证每列最左边的基准点  
  49.   
  50.         if (direction == Direction.Down)  
  51.         {  
  52.             pos.y = -yOffset - b.extents.y - b.center.y;  
  53.             pos.y += (b.max.y - b.min.y - bc.max.y + bc.min.y) * 0.5f - padding.y;  
  54.         }  
  55.         else  
  56.         {  
  57.             pos.y = yOffset + (b.extents.y - b.center.y);  
  58.             pos.y -= (b.max.y - b.min.y - bc.max.y + bc.min.y) * 0.5f - padding.y;  
  59.         }  
  60.   
  61.         xOffset += br.max.x - br.min.x + padding.x * 2f;  
  62.   
  63.         t.localPosition = pos;  
  64.   
  65.         if (++x >= columns && columns > 0)  
  66.         {  
  67.             x = 0;  
  68.             ++y;  
  69.   
  70.             xOffset = 0f;  
  71.             yOffset += bc.size.y + padding.y * 2f;  
  72.         }  
  73.     }  
  74. }  

          这里还是有吐槽:根据 Bounds 的定义 b.extens.x - b.center.x + b.min.x == 0 ,也就是这部完全是没有必要的,看来UIGrid 的 developer,不光代码逻辑不清晰,难道连大脑都是豆腐花做的么。

C#代码   收藏代码
  1. pos.x = xOffset + b.extents.x - b.center.x;  
  2. pos.x += b.min.x - br.min.x + padding.x;  

           类似计算 y 的值也其他更直接方法:

C#代码   收藏代码
  1. (b.max.y - b.min.y - bc.max.y + bc.min.y) * 0.5f  

 

使用经验

      1.UIGrid 没有考虑Bounds ,根据UIGrid 的计算公式可以知道:UIGrid 的第一个元素的 localPosition 的 x 和 y 一定都是 0 ,所以要位置,必须调整UIGrid 的localPosition ,但是实际在有可能调整的是 子对象,然后再 Scene 窗口看是没问题的(注意此时还没有重排),一运行就会出现位置的偏移。

      2.UITable 的子组件的 x 总是以 每列最左为 起始基准点的, y 则是每行居中对齐 :(b.max.y - b.min.y - bc.max.y + bc.min.y) * 0.5f 这行代码起始就是计算当前组件和所在行中心点的偏移。

   下图是将NGUI 其中一个组件的 sprite 左移了,就出现下面的排列:


 

       虽然 NGUI 提供了 UIGrid 和 UITable ,起始是非常之不完善,几乎做不了什么功能。这里分享两条经验:

               1.使用UIGrid时,调整界面的时候让 UIGrid 的 transform 和 其第一个子组件的 transform 相同,这样经过计算之后,位置就是之前调整想要的。或者将 第一个子组件 transform 重置,这样调整UIGrid 的              transform 位置看到的效果就是真实的。

               2.使用UITable 让每列元素的左边界都相同,即左对齐。可以看到 NGUI Example 的 Question Log 的 Table 的所有组件(UILable UISprite)的 pivot 都调整为 left 。

 

        总之,就是根据UIGrid 和UITable 的排列原理做相应的调整。

 

 

小结:

       这篇文章相对于NGUI所见即所得系列其他文章来说,简单很多。最近要做一个界面根据内容自适应,挺复杂的,一堆莫名其妙的问题。之前一直觉得 Unity 的 UI 没有Window MFC 等开发直接拖拽方式那么直观。NGUI 虽然很庞大,但NGUI越来越容易让我吐槽了,可能是对NGUI的家底多少掌握的缘故吧。

        很久就听说Unity要出自己的 UI 了,其实D.S.Qiu 也一直有想尝试自己写一点UI的可视化编辑工具(Visual Editor)。昨天不经意看到 Cocos2D 的 UI 编辑器 CocoStudio 感觉很强大,然后顺手google Unity有没有这方面的工具,果然还是发现了 UIToolkit , bitverse RagePixel 和 EWS editor ,也就说 Unity其实也有些 UI 可视化工具的。尤其 bitverse 支持的组件特别丰富,很强大,只可惜没有集成 Batch  DrawCall 的功能。

 

       如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

       转载请在文首注明出处:http://dsqiu.iteye.com/blog/2034883

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值