【翻译】在Ext JS和Sencha Touch中创建自己定义布局-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

【翻译】在Ext JS和Sencha Touch中创建自己定义布局

简介:

原文:Creating Custom Layouts in Ext JS and 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 应用程序中创建有创意的布局,而这实际上非常easy。关键的地方是丫了解怎样去初始化布局,以及在这期间发生了什么——其实底层的框架代码并没有你所想象的那样复杂。Sencha Touch和Ext JS的布局系统。尽管在底层会有些许的不同,但实现方法是实际上是一样的。

请注意:这仅仅是一个技术演示。不能保证代码能执行在全部浏览器上。

而其实,所使用的CSS3转换已经意味着排查了一些浏览器,因此请不要将这个应用到生产中。


Other interesting examples of customized layouts include this Sencha Fiddle by Sencha Support engineer Seth Lemmons involving a circle menu, and this video of a custom navigation component by Andrea Cammarata, Sencha Professional Services engineer.






本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5134089.html,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: