投放本站广告请联系:
extjscn#126.com
在Ext JS和Sencha Touch中创建自定义布局
布局系统是Sencha框架中最强大和最独特的一部分。布局会处理应用程序中每个组件的大小和位置,因而,不需要手动去管理那些碎片。Ext JS与Sencha Touch的布局类有许多相似之处,最近在 Ivan Jouikov的这篇博文中对他们进行了详细的分析。
虽然是这样,但很多Ext JS和Sencha Touch开发人员可能永远都不会去了解布局系统的机制原理。Sencha框架已经提供了最常用的应用程序布局,因此很少出现应用程序需要额外功能的需求,因而不大会有人愿意去了解布局系统的内部运作。
试想一下,你的公司需要在应用程序中使用3D Carousel来显示界面元素,但没有任何标准的Sencha布局可以提供这种能力,哪怎么来解决这个问题呢?
选择基类
在开发任何Ext JS或Sencha Touch自定义组件的时候,第一步要考虑的总会是应该选择哪一个基类来扩展。在目前这种情况,就是需要使用哪种布局才能容纳3D空间的项目。由于不需要任何特殊功能,只是管理项目,因而,需要从布局继承链最低这方面着手。在当前情况下,Ext.layout.container.Container (Ext JS)和Ext.layout.Default (Sencha Touch) 可以说是最佳选择。
在Ext JS中,由于需要支持旧版浏览器,而不能采用一些现代的CSS功能,如Flexbox,因而布局系统需要手动管理许多大小和定位的计算。这样的后果就是,3D carousel布局需要重写大多数的Ext.layout.container.Container方法,如calculate、getContainerSize和getPositionOffset,以便对它的子元素进行布局。
另一个需要注意的问题是,Ext JS布局在执行“布局”时,每一个“布局”的执行都要管理多个“周期”,例如,盒子布局配置为stretchmax就需要至少两个周期,布局首先要检测每一个子组件的最大尺寸,并在第二周期将所有布局内的子组件扩展为相同的大小。布局的操作会产生大量的“布局”或“周期”(添加或移除多个条目),为了提供性能,可能会希望先暂停布局,在操作完成后再恢复布局。
相比之下,Sencha Touch的Ext.layout.Default则允许浏览器通过CSS去处理布局中大多数项目的定位和尺寸(因为Sencha Touch只支持现代浏览器,而所有这些都已实现CSS Flexbox)。因此,Ext.layout.Default包含了与添加、移除和重新定位子条目的相关的主要方法。
现在,已经了解将使用那些类来扩展新的3D Carousel布局,下面来逐步去实现它。
CSS3转换和其他魔法
为了创建3D Carousel,需要使用一些高级CSS 3D转换,如转换、过渡、rotateX/rotateY和translateZ。CSS 3D转换可以很复制,但总的来说,对于新的Sencha布局需要实现以下事情:
在父容器应用透视和转换(让它看上去是3D的)
在布局内的子组件应用转换(围绕3D形状从他们的边界开始旋转他们)
在父容器内部的DOM元素应用转换(o physically rotate the 3D shape as the user interacts with it)
正如你所预期的,通过Ext JS和Sencha Touch生成的实际的DOM元素会有些许不同,因此,虽然在两个框架中采取的方法是一样的,但产生的CSS还是会有区别。新的3D Carousel布局所需的附加CSS如下(Sencha Touch):
.x-layout-carousel {
-webkit-perspective : 1500px;
-moz-perspective : 1500px;
-o-perspective : 1500px;
perspective : 1500px;
position : relative !important;
}
.x-layout-carousel .x-inner {
-webkit-transform-style : preserve-3d;
-moz-transform-style : preserve-3d;
-o-transform-style : preserve-3d;
transform-style : preserve-3d;
}
.x-layout-carousel.panels-backface-invisible .x-layout-carousel-item {
-webkit-backface-visibility : hidden;
-moz-backface-visibility : hidden;
-o-backface-visibility : hidden;
backface-visibility : hidden;
}
.x-layout-carousel-item {
display : inline-block;
position : absolute !important;
}
.x-layout-carousel-ready .x-layout-carousel-item {
-webkit-transition : opacity 1s, -webkit-transform 1s;
-moz-transition : opacity 1s, -moz-transform 1s;
-o-transition : opacity 1s, -o-transform 1s;
transition : opacity 1s, transform 1s;
}
.x-layout-carousel-ready .x-inner {
-webkit-transition : -webkit-transform 1s;
-moz-transition : -moz-transform 1s;
-o-transition : -o-transform 1s;
transition : transform 1s;
}
为了让Ext JS布局的CSS看上去基本一样,还需要对CSS选择器做点微调。
为了在Sencha Touch和Ext JS中让3D Carousel能响应用户交互,不得不在运行时对一些附加的CSS的进行修改。下面首先要做的是扩展布局的基类,然后研究如何添加交互功能。
扩展布局基类
首先来扩展Sencha Touch的Ext.layout.Default,主要目标是添加一些针对新的3D Carousel的观感的配置项,以及一些布局内部用来正确定位子组件的功能。
初步的扩展如下:
Ext.define('Ext.ux.layout.Carousel', {
extend : 'Ext.layout.Default',
alias : 'layout.carousel',
config : {
/**
* @cfg {number} portalHeight
* Height of the carousel, in pixels
*/
portalHeight : 0,
/**
* @cfg {number} portalWidth
* Width of the carousel, in pixels
*/
portalWidth : 0,
/**
* @cfg {string} direction
* 'horizontal' or 'vertical'
*/
direction : 'horizontal' //or 'vertical'
},
onItemAdd : function () {
this.callParent(arguments);
this.modifyItems();
},
onItemRemove : function () {
this.callParent(arguments);
this.modifyItems();
},
modifyItems : function () {
//calculate child positions, etc
}
});
代码中除了config对象外,还定义了3个方法:onItemAdd、onItemRemove和modifyItems。前啷个方法只是简单的重写Ext.layout.Default的方法,以便在添加或删除子组件后编辑子组件的位置,而modifyItems是一个新的方法,用来计算所需的CSS 3D转换。
在布局指派给他们的容器时,布局系统内部的行为就会一直处于活动状态:
setContainer: function(container) {
var options = {
delegate: '> component'
};
this.dockedItems = [];
this.callSuper(arguments);
container.on('centeredchange', 'onItemCenteredChange', this, options, 'before')
.on('floatingchange', 'onItemFloatingChange', this, options, 'before')
.on('dockedchange', 'onBeforeItemDockedChange', this, options, 'before')
.on('afterdockedchange', 'onAfterItemDockedChange', this, options);
},
对于我们的布局扩展来说,为了做进一步的初始化,还需要加上以下方法:
Ext.define('Ext.ux.layout.Carousel', {
//...
setContainer : function (container) {
var me = this;
me.callParent(arguments);
me.rotation = 0;
me.theta = 0;
switch (Ext.browser.name) {
case 'IE':
me.transformProp = 'msTransform';
break;
case 'Firefox':
me.transformProp = 'MozTransform';
break;
case 'Safari':
case 'Chrome':
me.transformProp = 'WebkitTransform';
break;
case 'Opera':
me.transformProp = 'OTransform';
break;
default:
me.transformProp = 'WebkitTransform';
break;
}
me.container.addCls('x-layout-carousel');
me.container.on('painted', me.onPaintHandler, me, { single : true });
},
onPaintHandler : function () {
var me = this;
//add the "ready" class to set the CSS transition state
me.container.addCls('x-layout-carousel-ready');
//set the drag handler on the underlying DOM
me.container.element.on({
drag : 'onDrag',
dragstart : 'onDragStart',
dragend : 'onDragEnd',
scope : me
});
me.modifyItems();
}
});
在nebulous指派布局容器后,必须等到容器渲染之后才能将事件处理指定到底层的DOM。接下来,为了能让布局管理子组件,还要填补功能之间的缝隙( fill in the functional gaps):
Ext.define('Ext.ux.layout.Carousel', {
//...
modifyItems : function () {
var me = this,
isHorizontal = (me.getDirection().toLowerCase() === 'horizontal'),
ct = me.container,
panelCount = ct.items.getCount(),
panelSize = ct.element.dom[ isHorizontal ? 'offsetWidth' : 'offsetHeight' ],
i = 0,
panel, angle;
me.theta = 360 / panelCount;
me.rotateFn = isHorizontal ? 'rotateY' : 'rotateX';
me.radius = Math.round(( panelSize / 2) / Math.tan(Math.PI / panelCount));
//for each child item in the layout...
for (i; i < panelCount; i++) {
panel = ct.items.getAt(i);
angle = me.theta * i;
panel.addCls('x-layout-carousel-item');
// rotate panel, then push it out in 3D space
panel.element.dom.style[ me.transformProp ] = me.rotateFn + '(' + angle + 'deg) translateZ(' + me.radius + 'px)';
}
// adjust rotation so panels are always flat
me.rotation = Math.round(me.rotation / me.theta) * me.theta;
me.transform();
},
transform : function () {
var me = this,
el = me.container.element,
h = el.dom.offsetHeight,
style= el.dom.style;
// push the carousel back in 3D space, and rotate it
el.down('.x-inner').dom.style[ me.transformProp ] = 'translateZ(-' + me.radius + 'px) ' + me.rotateFn + '(' + me.rotation + 'deg)';
style.margin = parseInt(h / 2, 10) + 'px auto';
style.height = me.getPortalHeight() + 'px';
style.width = me.getPortalWidth() + 'px';
},
rotate : function (increment) {
var me = this;
me.rotation += me.theta * increment * -1;
me.transform();
}
});
在示例中,还栩雅大量的复杂运算来确定每一个子组件的正确位置,以及需要在容器内手动更新CSS的转换值。
最后,还需要添加用来捕获用户与3D Carousel之间交互的事件处理:
Ext.define('Ext.ux.layout.Carousel', {
//...
onDragStart : function () {
this.container.element.dom.style.webkitTransitionDuration = "0s";
},
onDrag : function (e) {
var me = this,
isHorizontal = (me.getDirection().toLowerCase() === 'horizontal'),
delta;
if (isHorizontal) {
delta = -(e.deltaX - e.previousDeltaX) / me.getPortalWidth();
}
else {
delta = (e.deltaY - e.previousDeltaY) / me.getPortalHeight();
}
me.rotate((delta * 10).toFixed());
},
onDragEnd : function () {
this.container.element.dom.style.webkitTransitionDuration = "0.4s";
}
});
事件处理只是简单的评估用户是否已将鼠标拖动到carousel,然后更新CSS过渡。
该类完整的Sencha Touch代码可以在这里下载。而扩展自Ext.layout.container.Container的Ext JS的代码与这个非常类似,但在API上还是有一些小小的区别。Ext JS代码的示例可以在这里下载。
回顾Ext.ux.layout.Carousel
下面化一点点时间来回顾一下发生了什么事。
由于3D Carousel布局只需要继承布局系统的基本功能,因而选择了Sencha Touch的Ext.layout.Default类进行扩展。接下来,添加了onItemAdd、onItemRemove和setContainer等重写方法来添加布局所需的运行配置。最好,实现了一些功能方法和事件处理,以便布局能够管理子组件的位置。
尽管3D Carousel是一个使用Sencha Touch或Ext JS创建的异想天开的例子,但它的重点在于如何在Sencha 应用程序中创建有创意的布局,而这实际上很简单。关键的地方是丫了解如何去初始化布局,以及在这期间发生了什么——其实底层的框架代码并没有你所想象的那样复杂。Sencha Touch和Ext JS的布局系统,虽然在底层会有些许的不同,但实现方法是实际上是一样的。
请注意:这只是一个技术演示,不能保证代码能运行在所有浏览器上。而事实上,所使用的CSS3转换已经意味着排查了一些浏览器,因此请不要将这个应用到生产中。
- 关键字:
- Ext JS,
- Sencha Touch,
- 自定义布局,
- 布局,
- 中文教程
- 要发表评论,请先登录

