(急着解决问题的同学可以直接跳最底部查看最终的解决方案)

 问题描述

  因为前段时间抢到了华为荣耀3c,所以做项目的时候就用荣耀3c测试了一下项目,

  结果发现在华为的emotion ui上sencha touch的messagebox的弹窗,弹出后点击确认按钮时无法隐藏,

  有的圆角框还有会缺边,不过不仔细看倒是不看得出来,

  这是我的项目在手机上的截图,

  当我点击确定按钮的时候,messagebox的模态背景消失了,但是弹窗并不会消失,仔细看登陆框的圆角,有点缺边,我想华为应该是改过系统的浏览器内核了,至于做了哪些变动,这还真说不清

  

  对于圆角缺边,只能暂时无视了,但是弹窗不能消失的情况严重影响用户使用,

  在后来的测试中,发现了更为严重的bug,项目中所有组件的hide事件都不会触发,

  导致我在hide事件中手动进行销毁的全部失效了,

  而官方的例子运行起来也存在很多问题

问题解析:

  为了找出问题的所在,

  首先,我下载了几款别人已经发布的sencha touch的apk进行了下测试,

  发现在emotion ui 2.0上都存在这些bug,无意中又发现魔狼在世很久之前做的 《迷尚豆捞》 竟然没问题,经魔狼本人确认是使用2.0版本的sencha touch进行开发的项目,

  于是我下载了从2.0版本到2.3.1版本的sencha touch的sdk进行了测试,

  最终发现从2.2.1版本开始都存在这个问题,

  我想应该可以通过代码解决这个问题,于是花费了大量的时间开始调试查看源码,对于这个在pc上完全没有问题,在手机自带的浏览器上才会出现的bug只能通过在android上用logcat查看console输出来和pc端的调试结果进行比对来找出差别了,

  通过大量的调试查找,最终被找到了问题所在,并且发现问题描述中的所有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上这个判断不对了,原因是什么,我也不清楚,

最终解决方案:

由于sencha touch对css前缀判断有些问题,所以最终我修改了touch/src/fx/runner/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]);
}
}

  

关于sencha touch在华为、红米等部分手机下hide事件失效,msgbox无法关闭的解决方案(已更新最新解决方案)的更多相关文章

  1. [Phonegap+Sencha Touch] 移动开发19 某些安卓手机上弹出消息框 点击后不消失的解决的方法

    Ext.Msg.alert等弹出框在某些安卓手机上,点击确定后不消失. 原因是: 消息框点击确定后有一段css3 transform动画,动画完毕后才会隐藏(display:none). 有些奇葩手机 ...

  2. sencha touch在华为emotion ui 2.0自带浏览器中圆角溢出的bug

    在华为emotion ui 2.0自带的浏览器中,给部分组件设置了圆角后会发现背景仍然是方的,内部边框是圆的, 对于这种bug, 只需在对应的设置圆角的css样式中加入 background-clip ...

  3. sencha touch 常见问题解答(26-50)

    26.sencha touch在华为.红米等部分手机下hide事件失效,msgbox无法关闭怎么办 答:请看http://www.cnblogs.com/cjpx00008/p/3535557.htm ...

  4. Android环境配置Sencha Touch

    转自http://www.phonegap100.com/portal.php?mod=view&aid=19 作为你开发的一部分,为安卓设备开发的 Sencha Touch框架应该在安卓虚拟 ...

  5. 创建Sencha touch第一个应用

    最近学习Sencha touch ,是一个菜鸟级别.废话不多说,让我们来创建Sencha touch的第一应用. 首先,我们下载Sencha touch2.0 sdk 和SDK工具.  SDK工具直接 ...

  6. sencha touch 问题汇总

    做sencha touch有一段时间了,目前而言,sencha touch在android上问题比较严重,在此对android中sencha touch的问题做一些汇总: 1.内存问题: 打包成安装程 ...

  7. sencha touch的开源插件和例子

    写了好久的sencha touch,没想到换工作竟然一年多没有搞了.因为项目的缘故收集了好多的组件,由于懒惰,没有整理,现在想想有点后悔了,再加上如果就这样丢弃,感觉有些遗憾,今天整理了一下放在git ...

  8. 亲手使用Sencha Touch + phonepag开发Web APP随笔 -- 第一个APP

    参考博文: [Phonegap+Sencha Touch] 移动开发1.准备工作 [Phonegap+Sencha Touch] 移动开发2.PhoneGap/Cordova初步使用   经过差不多1 ...

  9. 亲手使用Sencha Touch + phonepag开发Web APP随笔 -- 环境安装篇

    最近因为有个项目需要制作APP,考虑到需要兼容Android和IOS,所以想采用WebAPP的方式来开发.现在是从零开始学习之路,走起-   通过网上博客和论坛,开始安装了一堆软件: 1. Sench ...

随机推荐

  1. UI基础:UITextField 分类: iOS学习-UI 2015-07-01 21:07 68人阅读 评论(0) 收藏

    UITextField 继承自UIControl,他是在UILabel基础上,对了文本的编辑.可以允许用户输入和编辑文本 UITextField的使用步骤 1.创建控件 UITextField *te ...

  2. SharePoint 设置Library中文档的默认打开方式

    在SharePoint Library中的文档, 如word, excel等, 文档有两种打开方式, 一种是Viewer in Browser, 一种是Open in Client applicati ...

  3. .Protobuf,GRpc,Maven项目出现UnsatisfiedDependencyException、ClassNotFoundException、BuilderException等异常

    异常如下: Error starting ApplicationContext. To display the auto-configuration report re-run your applic ...

  4. 安卓手机文件管理器简单横向评比 - imsoft.cnblogs

      X-plore文件管理器 个人评价:安卓手机上管理文件的神器,所有文件一览无余,加上自己对软件常用功能的配置,管理文件无比方便.(本人一直使用)   Solid文件管理器 个人评价:用户体验真的很 ...

  5. POI的简单使用

    一:简介 利用POI工具可以导出word,excel,ppt等office文件 二:程序代码示例 package com.wang.test; import java.io.File; import ...

  6. http状态码301和302详解及区别——辛酸的探索之路(文章源自:http://blog.csdn.net/grandPang/article/details/47448395)

    一直对http状态码301和302的理解比较模糊,在遇到实际的问题和翻阅各种资料了解后,算是有了一定的理解.这里记录下,希望能有新的认识.大家也共勉. 官方的比较简洁的说明: 301 redirect ...

  7. CTF-练习平台-WEB之 计算题

    四.计算题 打开连接 输入后发现只能输入一个数字,在火狐浏览器中按F12,打开查看器 ,如图所示修改最大长度 输入答案后验证,当当当~~flag出现

  8. 2017年最新cocoapods安装教程(解决淘宝镜像源无效以及其他源下载慢问题)

    首先,先来说一下一般的方法吧,就是把之前的淘宝源替换成一个可用的的源: 使用终端查看当前的源 gem sources -l gem sources -r https://rubygems.org/ # ...

  9. 【转】RS232、RS485、TTL电平、CMOS电平

    原文网址:http://blog.sina.com.cn/s/blog_63a0638101018grc.html RS232.RS485.TTL电平.CMOS电平 什么是TTL电平.CMOS电平.R ...

  10. C# 监听HTTP请求(遇到的一些问题)

    先把代码放在这里,下面再详细解说: using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Oracle.DataAccess.Client; ...