投放本站广告请联系:
extjscn#126.com
Sencha Touch华为emotion ui下hide事件失效,msgbox无法关闭的解决方案
因为前段时间抢到了华为荣耀3c,所以做项目的时候就用荣耀3c测试了一下项目,
结果发现在华为的emotion ui上sencha touch的messagebox的弹窗,弹出后点击确认按钮时无法隐藏,
有的圆角框还有会缺边,不过不仔细看倒是不看得出来,
这是我的项目在手机上的截图,
当我点击确定按钮的时候,messagebox的模态背景消失了,但是弹窗并不会消失,仔细看登陆框的圆角,有点缺边,我想华为应该是改过系统的浏览器内核了,至于做了哪些变动,这还真说不清
对于圆角缺边,我倒可以暂时无视,但是弹窗不能消失的状况就坑爹了,由于项目时间比较紧,加之其他手机暂无此bug,所以暂时没有处理
在后来的测试中,发现更严重得bug,项目中所有组件的hide事件都不会触发,
导致我在hide事件中手动控制的组件销毁全部失效,官方的例子运行起来也存在很多问题
项目经理让我把这个手机上的问题解决了,这下头疼了,
于是下了别人已经发布的sencha touch的apk进行了下测试,
发现都是这个问题,无意中发现魔狼很久之前做的 迷尚豆捞 竟然没问题,经魔狼本人确认是2.0版本的sencha touch,
所以接下来我下载了从2.0版本到2.3.1版本的sencha touch的sdk进行了测试,最终发现从2.2.1版本开始都存在这个问题,
我想应该可以通过代码解决这个问题,于是花了两天时间开始调试查看源码,因为在pc上完全没有问题,只有在手机自带的浏览器上才会出现这个问题,自带的浏览器无法进行远程调试,
所以一致通过在android上用logcat查看console输出来和pc端的调试结果进行比对,找出差别,我想做过sencha touch的一定知道这有多蛋疼了,
功夫不负有心人,最终被我找到了问题,并且发现上面的所有bug都是这个问题产生的,
原来,当组件执行隐藏的时候会触发Component.js里的hide方法
里面有这么一段代码
hide: function(animation) { this.setCurrentAlignmentInfo(null); if(this.activeAnimation) {//激活的动画对象,相当于正在运行中的动画 this.activeAnimation.on({ animationend: function(){ this.hide(animation); }, scope: this, single: true }); return this; } //判断组件是否被隐藏,如果没有被隐藏通过setHidden(true)进行隐藏操作 if (!this.getHidden()) { if (animation === undefined || (animation && animation.isComponent)) { animation = this.getHideAnimation(); } if (animation) { if (animation === true) { animation = 'fadeOut'; } this.onBefore({ hiddenchange: 'animateFn', scope: this, single: true, args: [animation] }); } this.setHidden(true);//进行隐藏操作,正常情况下,操作执行完,激活的动画运行完会被重置为null } return this; }
当执行setHidden时会触发Evented.js里的设置方法最终触发Componet.js里的animateFn方法,并且将activateAnimation重置为null,但是在华为的手机上并没有被重置,
继续查看animateFn函数
animateFn: function(animation, component, newState, oldState, options, controller) {
var me = this;
if (animation && (!newState || (newState && this.isPainted()))) {
this.activeAnimation = new Ext.fx.Animation(animation);//给激活动画对象设置一个动画对象
this.activeAnimation.setElement(component.element);
if (!Ext.isEmpty(newState)) {
this.activeAnimation.setOnEnd(function() {
me.activeAnimation = null;//当动画结束的时候重置activateAnimation为null
controller.resume();
});
controller.pause();
}
Ext.Animator.run(me.activeAnimation);//运行动画
}
}
发现这段代码给activeAnimation绑定了end事件,在setOnEnd里将activateAnimation进行了重置,但是在emotion ui上却没有触发这段代码,
于是继续往下查找,通过
Ext.Animator.run(me.activeAnimation)
我们进入动画执行阶段
这里会调用到这下面的CssTransition.js里的run方法
run的执行过程没有任何问题,关键问题就是这个js里有个onAnimationEnd方法,它在emotion ui上没有被触发,
而这个方法是通过refreshRunningAnimationsData这个方法触发的
refreshRunningAnimationsData: function(element, propertyNames, interrupt, replace) { var id = element.getId(), runningAnimationsData = this.runningAnimationsData, runningData = runningAnimationsData[id]; if (!runningData) { return; } var nameMap = runningData.nameMap, nameList = runningData.nameList, sessions = runningData.sessions, ln, j, subLn, name, i, session, map, list, hasCompletedSession = false; interrupt = Boolean(interrupt); replace = Boolean(replace); if (!sessions) { return this; } ln = sessions.length; if (ln === 0) { return this; } if (replace) { runningData.nameMap = {}; nameList.length = 0; for (i = 0; i < ln; i++) { session = sessions[i]; this.onAnimationEnd(element, session.data, session.animation, interrupt, replace); } sessions.length = 0; } else { for (i = 0; i < ln; i++) { session = sessions[i]; map = session.map; list = session.list; for (j = 0,subLn = propertyNames.length; j < subLn; j++) { name = propertyNames[j]; if (map[name]) {//当执行transform的时候这里传过来的name是-webkit-transform,但是map里只有transform属性,问题就出在这里,匹配不一致导致动画不会被移除 delete map[name];//动画存在移除匹配的动画属性 Ext.Array.remove(list, name); session.length--;//因为map不匹配,导致少执行一次session.length--,session.length永远不为0 if (--nameMap[name] == 0) { delete nameMap[name]; Ext.Array.remove(nameList, name); } } } if (session.length == 0) {//当动画移除完毕时执行 sessions.splice(i, 1); i--; ln--; hasCompletedSession = true; this.onAnimationEnd(element, session.data, session.animation, interrupt);//触发动画结束事件,最终组件被隐藏,hide事件被触发 } } } if (!replace && !interrupt && sessions.length == 0 && hasCompletedSession) { this.onAllAnimationsEnd(element); } }
问题就出在上面代码第50行的判断那里,
propertyNames对应的是从onTransitionEnd方法里传过来的e.browserEvent.propertyName参数
sencha touch里的这个browserEvent封装的是浏览器的原生对象,当执行到css的transform时候,这个propertyName对应的是"-webkit-transform",
而map对象里保存的是run方法里传的目标动画的相关内容,map里却是transform属性,因为匹配不对,导致session.length--少执行一次,session.length永远不为0,
所以后面的onAnimationEnd即动画结束的方法永远不被触发,
然后Msgbox也就不会隐藏了,同时,所有的hide事件也没有被触发,
为什么会不匹配呢,
我们往上查找,
原来最终问题是在run方法里导致的
run: function(animations) { var me = this, isLengthPropertyMap = this.lengthProperties, fromData = {}, toData = {}, data = {}, element, elementId, from, to, before, fromPropertyNames, toPropertyNames, doApplyTo, message, runningData, elementData, i, j, ln, animation, propertiesLength, sessionNameMap, computedStyle, formattedName, name, toFormattedValue, computedValue, fromFormattedValue, isLengthProperty, runningNameMap, runningNameList, runningSessions, runningSession; if (!this.listenersAttached) { this.attachListeners(); } animations = Ext.Array.from(animations); for (i = 0,ln = animations.length; i < ln; i++) { animation = animations[i]; animation = Ext.factory(animation, Ext.fx.Animation); element = animation.getElement(); // Empty function to prevent idleTasks from running while we animate. Ext.AnimationQueue.start(Ext.emptyFn, animation); computedStyle = window.getComputedStyle(element.dom); elementId = element.getId(); data = Ext.merge({}, animation.getData()); if (animation.onBeforeStart) { animation.onBeforeStart.call(animation.scope || this, element); } animation.fireEvent('animationstart', animation); this.fireEvent('animationstart', this, animation); data[elementId] = data; before = data.before; from = data.from; to = data.to; data.fromPropertyNames = fromPropertyNames = []; data.toPropertyNames = toPropertyNames = []; for (name in to) { if (to.hasOwnProperty(name)) { to[name] = toFormattedValue = this.formatValue(to[name], name); formattedName = this.formatName(name);//这里就是出问题的地方,传进去的name是transform,这个formatName就是判断你的浏览器属性然后对这个那么进行前缀添加 isLengthProperty = isLengthPropertyMap.hasOwnProperty(name); if (!isLengthProperty) { toFormattedValue = this.getCssStyleValue(formattedName, toFormattedValue); } if (from.hasOwnProperty(name)) { from[name] = fromFormattedValue = this.formatValue(from[name], name); if (!isLengthProperty) { fromFormattedValue = this.getCssStyleValue(formattedName, fromFormattedValue); } if (toFormattedValue !== fromFormattedValue) { fromPropertyNames.push(formattedName); toPropertyNames.push(formattedName); } } else { computedValue = computedStyle.getPropertyValue(formattedName); if (toFormattedValue !== computedValue) { toPropertyNames.push(formattedName); } } } } propertiesLength = toPropertyNames.length; if (propertiesLength === 0) { this.onAnimationEnd(element, data, animation); continue; } runningData = this.getRunningData(elementId); runningSessions = runningData.sessions; if (runningSessions.length > 0) { this.refreshRunningAnimationsData( element, Ext.Array.merge(fromPropertyNames, toPropertyNames), true, data.replacePrevious ); } runningNameMap = runningData.nameMap; runningNameList = runningData.nameList; sessionNameMap = {}; for (j = 0; j < propertiesLength; j++) { name = toPropertyNames[j]; sessionNameMap[name] = true; if (!runningNameMap.hasOwnProperty(name)) { runningNameMap[name] = 1; runningNameList.push(name); } else { runningNameMap[name]++; } } runningSession = { element: element, map: sessionNameMap, list: toPropertyNames.slice(), length: propertiesLength, data: data, animation: animation }; runningSessions.push(runningSession); animation.on('stop', 'onAnimationStop', this); elementData = Ext.apply({}, before); Ext.apply(elementData, from); if (runningNameList.length > 0) { fromPropertyNames = Ext.Array.difference(runningNameList, fromPropertyNames); toPropertyNames = Ext.Array.merge(fromPropertyNames, toPropertyNames); elementData['transition-property'] = fromPropertyNames; } fromData[elementId] = elementData; toData[elementId] = Ext.apply({}, to); toData[elementId]['transition-property'] = toPropertyNames; toData[elementId]['transition-duration'] = data.duration; toData[elementId]['transition-timing-function'] = data.easing; toData[elementId]['transition-delay'] = data.delay; animation.startTime = Date.now(); } message = this.$className; this.applyStyles(fromData); doApplyTo = function(e) { if (e.data === message && e.source === window) { window.removeEventListener('message', doApplyTo, false); me.applyStyles(toData); } }; if(Ext.browser.is.IE) { window.requestAnimationFrame(function() { window.addEventListener('message', doApplyTo, false); window.postMessage(message, '*'); }); }else{ window.addEventListener('message', doApplyTo, false); window.postMessage(message, '*'); } }
54行的formatName这个方法是对浏览器进行css判断然后给传进去的name参数加上浏览器前缀,
最终回传给formattedName,而这个formattedName最终会对应到map里的属性,
但是这个formatName在emotion Ui上的判断跟预期不一样
我们看一下formatName的方法
formatName: function(name) { var cache = this.formattedNameCache, formattedName = cache[name]; if (!formattedName) { if ((Ext.os.is.Tizen || !Ext.feature.has.CssTransformNoPrefix) && this.prefixedProperties[name]) {//Ext.feature.has.CssTransformNoPrefix判断 formattedName = this.vendorPrefix + name; //结果相反了,导致执行了else里的代码,将transform } //在没有加前缀的情况下返回了回去 else { formattedName = name; } cache[name] = formattedName; } return formattedName; }
如上所示,在判断Ext.feature.has.CssTransformNoPrefix的时候预期结果跟实际相反了,
emotion ui自带浏览器判断的结果是true,但实际上应该为false,
于是导致执行了下面else里的代码,
name参数transform传了进来,没有加上前缀又以transform传了回去,本应该传-webkit-transform的
但是在后来的判断中原生event对象里的propertyName又是加前缀的,
于是导致了refreshRunningAnimationsData里map["-webkit-transform"]匹配不一致,
代码判断不对,于是session.length--少执行一次,
session.length不会为0就不会触发后面的onAnimationEnd方法了,最终,
组件没有被隐藏,hide事件没有被触发,
那老版本的sencha touch为什么没这个问题,
因为老版本没有对
(Ext.os.is.Tizen || !Ext.feature.has.CssTransformNoPrefix)
所以老版本没有出现这个问题,
在其他的android系统上,Ext.feature.has.CssTransformNoPrefix这个值都是false,即不支持没有前缀,
包括最新版本的chrome,但是在华为emotion ui上这个判断不对了,原因是什么,我也不清楚,
由于之前的解决方案造成htc sense的判断不正确,我重新修改了CssTransition.js的源码,
由于前缀不匹配,所以我将浏览器自带事件的propertyName也做了处理,以保证前缀一致,
修改onTransitionEnd方法如下:
onTransitionEnd: function (e) { var target = e.target, id = target.id, propertyName = e.browserEvent.propertyName, styleDashPrefix = Ext.browser.getStyleDashPrefix(); if (id && this.runningAnimationsData.hasOwnProperty(id)) { if (Ext.feature.has.CssTransformNoPrefix) { if (propertyName.indexOf(styleDashPrefix) >= 0) { propertyName = propertyName.substring(styleDashPrefix.length); } } this.refreshRunningAnimationsData(Ext.get(target), [propertyName]); } }
作者: 香辣牛肉面
原文: http://www.cnblogs.com/cjpx00008/p/3535557.html
- 关键字:
- 要发表评论,请先登录