Your SlideShare is downloading. ×
  • Like
Ghost cat 以皮肤为主体的ui框架(唐翎)
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Now you can save presentations on your phone or tablet

Available for both IPhone and Android

Text the download link to your phone

Standard text messaging rates apply

Ghost cat 以皮肤为主体的ui框架(唐翎)

  • 2,374 views
Published

 

Published in Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
2,374
On SlideShare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
41
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. GHOST CAT
  • 2. 准备工作
    • 创建工程
    • 导入 Ghost Cat SWC 文件
    • 创建资源 FLA 文件 asset.fla
    • 创建多语言配置文件 ui.lang (可选)
  • 3. asset.fla
    • 内容如图所示
    其中 upArrow,downArrow 元件位于 scrollBar 元件中, nameText,numText 位于 render 元件中。所示文字表示的是元件在场景中的示例名。 整个图形绑定了类名 asset.Panel (此外, tabBar 和 render 由于在之后会被复制,也绑定了类名 asset.TabBar,asset.Render ,但这个类名只是用于复制,代码中并不会出现,其实无论取什么名字都没有关系)
  • 4. UI.lang
    • 多语言并不是必须的,只是这个例子中包含了此特性。在之后的代码会会自动转换。
    • 内容只有两行
    • name= 新的文字
    • title= 标题栏
    • 在后面的代码中, @name 会被自动转换为“ 新的文字 ”, @title 会被自动转换为“ 标题栏 ”,将不再进行解释
  • 5. 建立文档类
    • public class test extends Sprite
    • {
    • public function test()
    • {
    • LanguageManager.instance.load( " ui.lang " ); // 加载多语言
    • AssetManager.instance.loadResource( " asset.swf " ); // 加载资源
    • Queue.defaultQueue.addEventListener(OperationEvent.OPERATION_COMPLETE,loadCompleteHandler);// 监听资源加载完成事件
    • }
    • private function loadCompleteHandler(event:OperationEvent): void
    • {
    • stage.addChild(new ToolTipSprite());// 加入提示框
    • addChild( new Panel ());// 显示 Panel 窗体
    • }
    • }
  • 6. Panel.as ( 效果 )
    • public class Panel extends GBuilderBase // 继承此类便有拥有全部特性
    • {
    • public var titleText:GText;
    • public var mc:GMovieClip;
    • public var tabBar:GButton;
    • public var upArrow:GButton;
    • public var downArrow:GButton;
    • public var nameText:GText;
    • public var numText:GText;
    • public function Panel()
    • {
    • super ( " asset.Panel " ); // 连接资源
    • tabBar.data = "A" ;
    • nameText.text = "@name" ;
    • numText.text = "A" ;
    • mc.setLabel( null ,1); // 让动画只播放一次
    • }
    • }
    类名 asset.Panel 资源和类属性名称一一对应
  • 7. Panel.as v2 ( 效果 )
    • public class Panel extends GBuilderBase
    • {
    • public var titleText:GText;
    • public var mc:GMovieClip;
    • public var tabBar:GButton;
    • //public var upArrow:GButton;
    • //public var downArrow:GButton;
    • public var scrollBar :GHScrollBar;
    • //public var nameText:GText;
    • //public var numText:GText;
    • public var render :GListBase;
    • public function Panel()
    • {
    • super ( “asset.Panel” );// 仍然使用原来的资源
    • mc.setLabel( null ,1); // 让动画只播放一次
    • tabBar.data = "GhostCat" ;
    • render.type = UIConst.HORIZONTAL; // 设置 List 方向
    • render.itemRender = Render ; // 设置 Render
    • render.data = [ "G" , "H" , "O" , "S" , "T" , " " , "C" , "A" , "T" ];
    • scrollBar.setTarget(render,render.columnWidth * 2); // 指定滚动条
    • scrollBar.detra = render.columnWidth; // 指定滚动距离
    • new ZoomEffect( this , null ,0.1,1000,{ease:Elastic.easeIn}, true ).execute();// 展开效果
    • }
    • }
    这里直接定义父层元件,滚动条的两个按钮自动声明,而 render 则在 Render.as 中定义 设置列表的伸展方向,渲染器 ( 见下页,内容,设置滚动区域,滚动速度 最后加入一个从中间展开的效果
  • 8. Render.as
    • public class Render extends GBuilderBase
    • {
    • public var nameText:GText;
    • public var numText:GText;
    • public function Render(skin:*= null , replace:Boolean= true )
    • {
      • super (skin, replace);// 保持默认值,皮肤由外部传入
    • }
    • // 复写 data 的 set 方法来获得数据
    • public override function set data(v:*) : void
    • {
      • super .data = v;
      • nameText.text = numText.text = v;
      • this .toolTip = " 当前值: " + v;
      • TextFieldUtil.autoFontSize(numText.textField); // 调整字号使得可以全部显示
    • }
    • // 复写 selected 属性来设置选中效果
    • public override function set selected(v:Boolean) : void
    • {
      • super .selected = v;
      • this .transform.colorTransform = v ? new ColorTransform(1,1,1,1,50,50,50) : new ColorTransform();
    • }
    • }
  • 9. 已经有基类,不能直接使用 GBuildBase?
    • public class Render extends GButton
    • {
    • public var nameText:GText;
    • public var numText:GText;
    • public function Render(skin:*= null , replace:Boolean= true )
    • {
    • super (skin, replace);
    • UIBuilder.buildAll( this );
    • }
    • public override function destory(): void
    • {
    • UIBuilder.destory( this );
    • super .destory();
    • }
    • }
    • public class Render extends GButton
    • {
    • public var nameText:GText;
    • public var numText:GText;
    • public function Render(skin:*= null , replace:Boolean= true )
    • {
    • super (skin, replace);
    • nameText = new GText( this .content[ "nameText" ]);
    • numText = new GText( this .content[ "numText" ]);
    • }
    • public override function destory(): void
    • {
    • nameText.destory();
    • numText.destory();
    • super .destory();
    • }
    • }
  • 10. 关于队列 先看看需求
  • 11. 需求分析
    • 先移动列表,然后修改数据,最后将列表移回原来的位置。 (因为数据不会立即生效,因此需要缓存在变量中)
    • 更改数据后列表长度可能变化,所以需要将滚动重置。
    • 重复执行这段代码会有各种问题,因此移动过程中按钮应当是禁用的。
  • 12. 是否打算这样去做?
    • private var temp:Array;
    • public function setData(v:Array): void
    • {
      • temp = v;
      • tabBar.mouseEnabled = tabBar.mouseChildren = false ;
      • TweenUtil.to( this .render,500,{y:300,ease:Circ.easeIn,onComplete:tween1CompleteHandler});
    • }
    • private function tween1CompleteHandler(): void
    • {
      • this .render.data = temp;
      • temp = null ;
      • this .scrollBar.resetContent( true , false );// 重置滚动条
      • TweenUtil.to( this .render,500,{y:0,ease:Circ.easeOut,onComplete:tween2CompleteHandler});
    • }
    • private function tween2CompleteHandler(): void
    • {
      • tabBar.mouseEnabled = tabBar.mouseChildren = true ;
    • }
  • 13. 另一种写法
    • public function setData( v :Array): void
    • {
    • var list:Array = [
    • new SetPropertyOper( this .tabBar,{mouseEnabled: false ,mouseChildren: false }),
    • // 禁用鼠标
    • new TweenOper( this .render,500,{y:300,ease:Circ.easeIn}),
    • // 隐藏列表
    • new SetPropertyOper( this .render,{data: v }),
    • // 设置数据
    • new FunctionOper( this .scrollBar.resetContent,[true,false]),
    • // 让滚动区域回到顶端
    • new TweenOper( this .render,500,{y:0,ease:Circ.easeOut}),
    • // 显示列表
    • new SetPropertyOper( this .tabBar,{mouseEnabled: true ,mouseChildren: true })
    • // 恢复鼠标
    • ];
    • new Queue(list).execute(); // 队列执行
    • }
  • 14. Oper 类
    • 上面的代码是先创建了一个数组,数组里面都是一些继承于 Oper 的类,每个对象表示着一个具体操作。这个对象创建出来并不会立即执行,而要使用对象的 execute 方法来启动。
    • 创建数组后,则创建了另一个特殊的 Oper : Queue ,它会代理并队列调用参数数组里的 Oper 对象的 execute 方法。
    • 这种做法比加监听意义更加明确。
  • 15. Queue
    • 这是一个特殊的 Oper ,参数是一个数组包含着其他的 Oper 对象。执行它将会顺序执行所有子对象。
    • 就像这样: new Queue(list).execute();
  • 16. SetPropertyOper
    • SetPropertyOper( this .tabBar,{mouseEnabled: false ,mouseChildren: false }),
    • 这个类用于设置某个对象的属性,第一个参数是对象实例,第二个参数是一个 JSON 对象,例如这段代码就是设置 mouseEnabled,mouseChildren 的值为 false
  • 17. TweenOper
    • TweenOper( this .render,500,{y:300,ease:Circ.easeIn})
    • 和 Tween 参数相似,只不过实例化这个对象并不会马上执行 Tween 操作,而需要手动执行 execute 方法。用于队列这点是必须的。
  • 18. FunctionOper
    • FunctionOper( this .scrollBar.resetContent,[true,false])
    • 这个类非常简单,就是执行一个函数,第二个参数是参数数组。同样,你可以通过控制 execute 执行的时机来延迟执行这个函数。
    • 例如,你想加载一个文件然后完毕时执行一个方法,也可以这样写
    • new Queue([new LoadOper(“a.txt”),new FunctionOper(rHandler)]).execute();
    • 这样在加载完 a.txt 之后便会继续执行 rHandler 方法
  • 19. ……
    • 除此之外还有很多
    • 但更重要的是,你可以自己创建一个 Oper 的派生类,便可以和其他类放置在同一个队列中执行
  • 20. 一个简单的例子(设置发光提示点击某个对象,点击后继续执行)
    • public class ClickOper extends Oper
    • {
      • public var target:Sprite;
      • public function ClickOper(target:Sprite)
      • {
        • this .target = target;
        • super ();
      • }
      • public override function execute(): void
      • {
        • this .target.addEventListener(MouseEvent.CLICK,result);
        • this .target.filters = [ new GlowFilter(0xFFFFFF,1,16,16)]
        • super .execute();
      • }
      • public override function result(event:*= null ): void
      • {
        • this .target.removeEventListener(MouseEvent.CLICK,result);
        • this .target.filters = [];
        • super .result(event);
      • }
    • }
  • 21. GListBase 列表类
  • 22. 刚才示例的代码,只是将 tabBar 的类型由 Gbutton 改成了 GListBase
    • public class Panel extends GBuilderBase
    • {
    • public var titleText:GText;
    • public var mc:GMovieClip;
    • //public var tabBar:GButton;
    • public var tabBar: GListBase ;
    • public var scrollBar:GHScrollBar;
    • public var render: GListBase ;
    • public function Panel3()
    • {
    • super ( "asset.Panel" );
    • mc.setLabel( null ,1); // 让动画只播放一次
    • render.type = UIConst.HORIZONTAL;
    • render.itemRender = Render;
    • scrollBar.setTarget(render,render.columnWidth * 2);
    • scrollBar.detra = render.columnWidth;
    • tabBar.type = UIConst.HORIZONTAL;
    • tabBar.itemRender = GButton;
    • tabBar.data = [ "A-Z" , "0-10000" ];
    • tabBar.addEventListener(Event.CHANGE,tabChangeHandler);
    • tabBar.selectedIndex = 0;
    • }
    • private function tabChangeHandler(event:Event): void
    • {
    • setData(getData(tabBar.selectedIndex))
    • }
    • private function getData(type:int):Array
    • {
    • var i:int;
    • var list:Array = [];
    • if (type == 0)
    • {
    • for (i = 0;i < 26;i++)
    • {
    • list[i] = String.fromCharCode(( &quot;A&quot; ).charCodeAt(0) + i);
    • }
    • }
    • else
    • {
    • for (i = 0;i < 100000;i++)
    • {
    • list[i] = i.toString();
    • }
  • 23.
    • GListBase 只负责复制子对象实现列表效果,它没有自己的大小概念,也没有滚动条。
    • 它可以像列表那样加上滚动条使用,也可以像上面那样直接作为 TabBar 存在。
    • 一共提供横向,纵向,横向平铺 3 种布局方式
    • (不要忘了,能够复制的 MC 必须绑定类,否则无法复制,将永远直显示一个子对象)
  • 24. 列表项的数量可以达到 1W
    • GListBase 已经实现了对象池,只有显示出的子对象才会被实例化。因此显示 1W (或者更多)和显示 2 个消耗的资源是相同的。
    • 上面的例子中的第 2 个 Tab 页就使用了一个 1W 项数据的列表
  • 25. GMovieClip 动画扩展类
  • 26. 其实之前的例子已经出现了一次
    • public var mc:GMovieClip;
    • public function Panel()
    • {
    • super ( &quot; asset.Panel &quot; ); // 连接资源
    • tabBar.data = &quot;A&quot; ;
    • nameText.text = &quot;@name&quot; ;
    • numText.text = &quot;A&quot; ;
    • mc.setLabel( null ,1); // 让动画只播放一次
    • }
    • 就像注释说的那样,这段代码的作用是设置 mc 的播放范围为 null (也就是全部帧),并指定播放 1 次。这是一个通用写法,也可以换成 mc.setLoop(1) 是一样的效果。当然,如果把 1 改成 2 ,那就是播放 2 次后停止。
  • 27. GMovieClip 关键属性 / 方法
    • frameRate 可以设置动画的播放帧频,当值为负时即为倒放,默认为舞台帧频
    • frameRate 的播放控制是基于时间的,因此并不会因为 CPU 忙而产生拖慢,而只会跳帧,相对于拖慢更难被用户察觉。
    • 它是监听了 ENTER_FRAME 事件并以 getTimer 作为依据来决定当前帧的。并不会出现直接用 Timer 更新产生的闪烁,因此也不需要强制更新屏幕。
  • 28. GMovieClip 关键属性 / 方法
    • setLabel( 帧标签,播放次数 )
    • 帧标签就是 FLASH 时间线上对帧的命名,播放次数为 -1 时为无限循环(默认值)
    • queueLabel( 帧标签,播放次数 )
    • 这个方法需要和 setLabel 配合使用,它会在上一个动画播放完成后再执行。
    • clearQueue()
    • 清除当前的动画队列
  • 29.
    • 如果你的动画帧被命名分割为三个区域 : start , loop , end ,然后你希望先播放 start ,然后重复播放 loop 三次,最后播放完 end 然后停止,代码如下
    • setLabel(“start”,1);
    • queueLabel(“loop”,3);
    • queueLabel(“end”,1);
    • 整个播放过程当然也会对外发布 MovieEvent 事件,如果希望在动画完毕时处理一些事情,监听动画的 MOVIE_EMPTY 事件
  • 30.
    • queueDestory()
    • 新版本增加的方法,执行它后会在动画播放完毕后自动移除自己。于是不需要再去监听 MOVIE_EMPTY 事件了。毕竟这个需求就和动画仅播放一次一样常见……
    • 如果想反悔,可以再一次执行 clearQueue 方法。
  • 31. 并不只是矢量
    • GBitmapMovieClip 和它拥有几乎完全相同的属性和方法,但它所支持的是位图序列。
    • 与 MovieClip 不同,它的构造函数参数是一个由 BitmapData 组成的数组。而重要的帧标签部分,则是第二个可选参数:一个 FrameLabel 组成的数组。
  • 32.
    • 第二个参数并不是必须的。但如果想使用 queueLabel 这类方法的话,没有帧标签该如何操作?虽然,你也可以直接手动设置 currentFrame
    • 你可以自己手动创建这个 FrameLabel 数组,也可以直接去取一个专门的空 MovieClip 的 currentLabels 属性(这是一条捷径)
  • 33. 一个 GBitmapMovieClip 的实例
    • 只贴出相关部分( BitmapSeparateUtil , FrameLabelUtil 也是库中的类 )
    • var source:Array = BitmapSeparateUtil.separateBitmapData( new stand().bitmapData,56,91).concat(BitmapSeparateUtil.separateBitmapData( new walk().bitmapData,67,91));
    • // 从 PNG 图片中切割成动画数组
    • var labels:Array = FrameLabelUtil.createFromObject({
    • &quot;down&quot; :1, &quot;left&quot; :9, &quot;right&quot; :17, &quot;up&quot; :25,
    • &quot;leftdown&quot; :33, &quot;rightdown&quot; :41, &quot;leftup&quot; :49, &quot;rightup&quot; :57,
    • &quot;walkdown&quot; :65, &quot;walkleft&quot; :73, &quot;walkright&quot; :81, &quot;walkup&quot; :89,
    • &quot;walkleftdown&quot; :97, &quot;walkrightdown&quot; :105, &quot;walkleftup&quot; :113, &quot;walkrightup&quot; :121
    • });
    • // 创建 FrameLabel 数组
    • man = new GBitmapMovieClip(source,labels); // 创建动画对象
    • man.frameRate = 10;
  • 34. 动画缓存?
    • 矢量动画太复杂,播放不顺畅?这时候只能缓存。但具体应当如何去做呢?
    • 当矢量和位图动画拥有同样的方法的时候,缓存动画就很简单了。下面的代码最后都能生成一个 GBitmapMovieClip 对象 bmc ,它是 mc 这个 MovieClip 对象的缓存,并可以直接 addChild 显示。
    • 方法 1 :
    • bmc = new GBitmapMovieClip();
    • bmc.createFromMovieClip(mc);
    • 方法 2 :
    • bmc = new GMovieClip(mc).toGBitmapMovieClip();
  • 35. SelectGroup& ViewStack 关于页面切换
  • 36. asset.fla
    • 同理, tabBar 和 viewStack 元件类分别包含着 tab1,tab2,tab3 以及 render1,render2,render3
    • 整个元件链接了类名 asset.SelectGroup
  • 37. Panel.as v4 ( 效果 )
    • public class Panel extends GBuilderBase
    • {
    • public var tab1:ZoomButton;
    • public var tab2:ZoomButton;
    • public var tab3:ZoomButton;
    • public var tabBar:GBase;
    • public var render1:Panel;
    • public var render2:Panel2;
    • public var render3:Panel3;
    • public var viewStack:GViewState;
    • public var selectGroup:SelectGroup;
    • public function Panel()
    • {
    • super ( &quot;asset.SelectGroup&quot; );
    • // 按钮条
    • selectGroup = new SelectGroup([ tab1,tab2,tab3 ], true );
    • selectGroup.addEventListener(Event.CHANGE, changeHandler );
    • selectGroup.selectedIndex = 0;
    • // 设置切换过渡
    • viewStack.showFromRight = new TweenOper( null , 500, { x: -760, startAt:{x:0}, ease:Circ.easeInOut}, true ,1);
    • viewStack.showFromLeft = new TweenOper( null , 500, { x:760, startAt:{x:0}, ease:Circ.easeInOut}, true ,1);
    • viewStack.hideToRight = new TweenOper( null , 500, { x: -760 , ease:Circ.easeInOut}, false ,1);
    • viewStack.hideToLeft = new TweenOper( null , 500, { x:760 , ease:Circ.easeInOut}, false ,1);
    • }
    • private function changeHandler (event:Event): void
    • {
    • viewStack.selectedIndex = selectGroup.selectedIndex;
    • }
    • }
    • 其中 ZoomButton 类见下页
  • 38. ZoomButton.as
    • public class ZoomButton extends GButton
    • {
    • public function ZoomButton(skin:*= null , replace:Boolean = true , separateTextField:Boolean = false , textPadding:Padding = null )
    • {
      • super (skin, replace, separateTextField, textPadding);
      • this .delayCall = false ; // 取消延迟更新避免抖动
      • this .upState.oper = new TweenOper( this ,100,{scaleX:1.0,scaleY:1.0}, false , 1);
      • this .overState.oper = new TweenOper( this ,100,{scaleX:1.4,scaleY:1.4}, false , 1);
      • this .downState.oper = new TweenOper( this ,100,{scaleX:1.0,scaleY:1.0}, false , 1);
      • this .selectedUpState.oper = this .selectedOverState.oper = this .selectedDownState.oper = new TweenOper( this ,100,{scaleX:1.4,scaleY:1.4}, false , 1);
      • this .disabledState.oper = this .selectedDisabledState.oper = new TweenOper( this ,100,{scaleX:1.0,scaleY:1.0}, false , 1);
    • }
    • }
    • 这个类直接继承于 GButton 并设置了一些效果的初始值,以实现缩放
  • 39. SelectGroup
    • // 按钮条
    • selectGroup = new SelectGroup([ tab1,tab2,tab3 ], true );
    • selectGroup.addEventListener(Event.CHANGE, changeHandler );
    • selectGroup.selectedIndex = 0;
    • 这个类会将多个按钮传入,并提供 selectedIndex,selectedChild 属性以及 Change 事件来方便实现类似 ToggleButtonBar 的效果
    • 如果将第三个参数设置为” visible” ,还可以同样的指定多个对象仅有一个显示的效果
  • 40. GViewState
    • 此对象会将自己的 Children 当作多个 State 来处理,同时只显示其中的一个
    • ViewState 主要的特性是提供了 showFromRight,showFromLeft,hideToRight,hideToLeft 这 4 个 Effect 属性,可以在切换的时候显示转场。
  • 41. Layout 布局
  • 42. 当你并不需要动态布局的时候
    • 可以在生成界面之后手动设置 UI 的各个属性,如果有些数组无法简单的少量代码写出来的话,可以使用 LayoutUtil 静态类。
    • 对齐 LayoutUtil.silder
    • 从中部偏移 LayoutUtil.center
    • 控制外框边距 LayoutUtil.metrics
    • 百分比长宽 LayoutUtil.percent
    • 横向排列 LayoutUtil.horizontal
    • 纵向排列 LayoutUtil.vertical
  • 43. 当你需要动态布局的时候
    • 使用 Layout 系列对象,将容器作为参数传入,并设置必要的属性(方向,边距,间距等),当容器的子对象都是 GBase 对象时即可自动布局
    • AbsoluteLayout 绝对布局,可设置子对象的 left,top,right,bottom 属性。
    • LinearLayout 线性布局,子对象会按顺序排列在一起,可指定为 HORIZONTAL, VERTICAL, TILE 三种模式
  • 44. 编写自己的 Layout
    • 重写 Layout.as 的 layoutChildren 方法即可
    • protected override function layoutChildren(x:Number, y:Number, w:Number, h:Number) {
    • var rx:Number = (w - paddingLeft - paddingRight) / 2;
    • var ry:Number = (h - paddingTop - paddingBottom) / 2;
    • var sx:Number = x + paddingLeft + rx;
    • var sy:Number = y + paddingTop + ry;
    • var len:int = target.numChildren;
    • var br:Number = this .rotation / 180 * Math.PI;
    • for ( var i:int = 0;i < len;i++){
    • var obj:DisplayObject = target.getChildAt(i);
    • var r:Number = i / len * Math.PI * 2 + br;
    • var p:Point = new Point(sx + rx * Math.cos(r),sy + ry * Math.sin(r));
    • obj.x = p.x;
    • obj.y = p.y;
    • }
    • }
    • 这段代码会让子对象以椭圆形式排列
  • 45. 当你希望容器本身也能根据子对象来决定自己大小的时候
    • Layout 对象需要实现 measureChildren 方法,最后通过执行 super .measureChildren(width,height) 来决定新的宽高
    • LinearLayout 布局器默认已经开启了 measureChildren 。如果会造成困扰,可以设置 enabledMeasureChildren=false 来取消这个效果。
  • 46. GView
    • GBox,GHBox,GVBox 已经默认包含了 LinearLayout 对象,因此只需要普通的 addChild 便能自动布局
    • 任何时候都可以重新执行 setLayout 来指定布局器
  • 47. 例:给刚才的例子加上布局功能 ( 效果 )
    • public var tab1:ZoomButton;
    • public var tab2:ZoomButton;
    • public var tab3:ZoomButton;
    • public var tabBar:GHBox;// 将 tabBar 的类型更改为 GHBox
    • public var render1:Panel;
    • public var render2:Panel2;
    • public var render3:Panel3;
    • public var viewStack:GViewState;
    • public var selectGroup:SelectGroup;
    • public function Panel(skin:*= null , replace:Boolean= true )
    • {
    • super ( &quot;asset.SelectGroup&quot; );
    • tabBar.linearLayout.delayCall = false ; // 取消延迟布局可减少运动时的抖动现象
    • tabBar.linearLayout.horizontalGap = 5; // 设置间距