zoukankan      html  css  js  c++  java
  • 关于sencha touch在华为、红米等部分手机下hide事件失效,msgbox无法关闭的解决方案(已更新最新解决方案)

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

     问题描述

      因为前段时间抢到了华为荣耀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这个方法触发的

     1 refreshRunningAnimationsData: function(element, propertyNames, interrupt, replace) {
     2         var id = element.getId(),
     3             runningAnimationsData = this.runningAnimationsData,
     4             runningData = runningAnimationsData[id];
     5 
     6         if (!runningData) {
     7             return;
     8         }
     9 
    10         var nameMap = runningData.nameMap,
    11             nameList = runningData.nameList,
    12             sessions = runningData.sessions,
    13             ln, j, subLn, name,
    14             i, session, map, list,
    15             hasCompletedSession = false;
    16 
    17         interrupt = Boolean(interrupt);
    18         replace = Boolean(replace);
    19 
    20         if (!sessions) {
    21             return this;
    22         }
    23 
    24         ln = sessions.length;
    25 
    26         if (ln === 0) {
    27             return this;
    28         }
    29 
    30         if (replace) {
    31             runningData.nameMap = {};
    32             nameList.length = 0;
    33 
    34             for (i = 0; i < ln; i++) {
    35                 session = sessions[i];
    36                 this.onAnimationEnd(element, session.data, session.animation, interrupt, replace);
    37             }
    38 
    39             sessions.length = 0;
    40         }
    41         else {
    42             for (i = 0; i < ln; i++) {
    43                 session = sessions[i];
    44                 map = session.map;
    45                 list = session.list;
    46 
    47                 for (j = 0,subLn = propertyNames.length; j < subLn; j++) {
    48                     name = propertyNames[j];
    49 
    50                     if (map[name]) {//当执行transform的时候这里传过来的name是-webkit-transform,但是map里只有transform属性,问题就出在这里,匹配不一致导致动画不会被移除
    51                         delete map[name];//动画存在移除匹配的动画属性
    52                         Ext.Array.remove(list, name);
    53                         session.length--;//因为map不匹配,导致少执行一次session.length--,session.length永远不为0
    54                         if (--nameMap[name] == 0) {
    55                             delete nameMap[name];
    56                             Ext.Array.remove(nameList, name);
    57                         }
    58                     }
    59                 }
    60 
    61                 if (session.length == 0) {//当动画移除完毕时执行
    62                     sessions.splice(i, 1);
    63                     i--;
    64                     ln--;
    65 
    66                     hasCompletedSession = true;
    67                     this.onAnimationEnd(element, session.data, session.animation, interrupt);//触发动画结束事件,最终组件被隐藏,hide事件被触发
    68                 }
    69             }
    70         }
    71 
    72         if (!replace && !interrupt && sessions.length == 0 && hasCompletedSession) {
    73             this.onAllAnimationsEnd(element);
    74         }
    75     }

    问题就出在上面代码第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方法里导致的

      1 run: function(animations) {
      2         var me = this,
      3             isLengthPropertyMap = this.lengthProperties,
      4             fromData = {},
      5             toData = {},
      6             data = {},
      7             element, elementId, from, to, before,
      8             fromPropertyNames, toPropertyNames,
      9             doApplyTo, message,
     10             runningData, elementData,
     11             i, j, ln, animation, propertiesLength, sessionNameMap,
     12             computedStyle, formattedName, name, toFormattedValue,
     13             computedValue, fromFormattedValue, isLengthProperty,
     14             runningNameMap, runningNameList, runningSessions, runningSession;
     15 
     16         if (!this.listenersAttached) {
     17             this.attachListeners();
     18         }
     19 
     20         animations = Ext.Array.from(animations);
     21 
     22         for (i = 0,ln = animations.length; i < ln; i++) {
     23             animation = animations[i];
     24             animation = Ext.factory(animation, Ext.fx.Animation);
     25             element = animation.getElement();
     26 
     27             // Empty function to prevent idleTasks from running while we animate.
     28             Ext.AnimationQueue.start(Ext.emptyFn, animation);
     29 
     30             computedStyle = window.getComputedStyle(element.dom);
     31 
     32             elementId = element.getId();
     33 
     34             data = Ext.merge({}, animation.getData());
     35 
     36             if (animation.onBeforeStart) {
     37                 animation.onBeforeStart.call(animation.scope || this, element);
     38             }
     39             animation.fireEvent('animationstart', animation);
     40             this.fireEvent('animationstart', this, animation);
     41 
     42             data[elementId] = data;
     43 
     44             before = data.before;
     45             from = data.from;
     46             to = data.to;
     47 
     48             data.fromPropertyNames = fromPropertyNames = [];
     49             data.toPropertyNames = toPropertyNames = [];
     50 
     51             for (name in to) {
     52                 if (to.hasOwnProperty(name)) {
     53                     to[name] = toFormattedValue = this.formatValue(to[name], name);
     54                     formattedName = this.formatName(name);//这里就是出问题的地方,传进去的name是transform,这个formatName就是判断你的浏览器属性然后对这个那么进行前缀添加
     55                     isLengthProperty = isLengthPropertyMap.hasOwnProperty(name);
     56 
     57                     if (!isLengthProperty) {
     58                         toFormattedValue = this.getCssStyleValue(formattedName, toFormattedValue);
     59                     }
     60 
     61                     if (from.hasOwnProperty(name)) {
     62                         from[name] = fromFormattedValue = this.formatValue(from[name], name);
     63 
     64                         if (!isLengthProperty) {
     65                             fromFormattedValue = this.getCssStyleValue(formattedName, fromFormattedValue);
     66                         }
     67 
     68                         if (toFormattedValue !== fromFormattedValue) {
     69                             fromPropertyNames.push(formattedName);
     70                             toPropertyNames.push(formattedName);
     71                         }
     72                     }
     73                     else {
     74                         computedValue = computedStyle.getPropertyValue(formattedName);
     75 
     76                         if (toFormattedValue !== computedValue) {
     77                             toPropertyNames.push(formattedName);
     78                         }
     79                     }
     80                 }
     81             }
     82 
     83             propertiesLength = toPropertyNames.length;
     84 
     85             if (propertiesLength === 0) {
     86                 this.onAnimationEnd(element, data, animation);
     87                 continue;
     88             }
     89 
     90             runningData = this.getRunningData(elementId);
     91             runningSessions = runningData.sessions;
     92 
     93             if (runningSessions.length > 0) {
     94                 this.refreshRunningAnimationsData(
     95                     element, Ext.Array.merge(fromPropertyNames, toPropertyNames), true, data.replacePrevious
     96                 );
     97             }
     98 
     99             runningNameMap = runningData.nameMap;
    100             runningNameList = runningData.nameList;
    101 
    102             sessionNameMap = {};
    103             for (j = 0; j < propertiesLength; j++) {
    104                 name = toPropertyNames[j];
    105                 sessionNameMap[name] = true;
    106 
    107                 if (!runningNameMap.hasOwnProperty(name)) {
    108                     runningNameMap[name] = 1;
    109                     runningNameList.push(name);
    110                 }
    111                 else {
    112                     runningNameMap[name]++;
    113                 }
    114             }
    115 
    116             runningSession = {
    117                 element: element,
    118                 map: sessionNameMap,
    119                 list: toPropertyNames.slice(),
    120                 length: propertiesLength,
    121                 data: data,
    122                 animation: animation
    123             };
    124             runningSessions.push(runningSession);
    125 
    126             animation.on('stop', 'onAnimationStop', this);
    127 
    128             elementData = Ext.apply({}, before);
    129             Ext.apply(elementData, from);
    130 
    131             if (runningNameList.length > 0) {
    132                 fromPropertyNames = Ext.Array.difference(runningNameList, fromPropertyNames);
    133                 toPropertyNames = Ext.Array.merge(fromPropertyNames, toPropertyNames);
    134                 elementData['transition-property'] = fromPropertyNames;
    135             }
    136 
    137             fromData[elementId] = elementData;
    138             toData[elementId] = Ext.apply({}, to);
    139 
    140             toData[elementId]['transition-property'] = toPropertyNames;
    141             toData[elementId]['transition-duration'] = data.duration;
    142             toData[elementId]['transition-timing-function'] = data.easing;
    143             toData[elementId]['transition-delay'] = data.delay;
    144 
    145             animation.startTime = Date.now();
    146         }
    147 
    148         message = this.$className;
    149 
    150         this.applyStyles(fromData);
    151 
    152         doApplyTo = function(e) {
    153             if (e.data === message && e.source === window) {
    154                 window.removeEventListener('message', doApplyTo, false);
    155                 me.applyStyles(toData);
    156             }
    157         };
    158 
    159         if(Ext.browser.is.IE) {
    160             window.requestAnimationFrame(function() {
    161                 window.addEventListener('message', doApplyTo, false);
    162                 window.postMessage(message, '*');
    163             });
    164         }else{
    165             window.addEventListener('message', doApplyTo, false);
    166             window.postMessage(message, '*');
    167         }
    168     }

    54行的formatName这个方法是对浏览器进行css判断然后给传进去的name参数加上浏览器前缀,

    最终回传给formattedName,而这个formattedName最终会对应到map里的属性,

    但是这个formatName在emotion Ui上的判断跟预期不一样

    我们看一下formatName的方法

     1 formatName: function(name) {
     2         var cache = this.formattedNameCache,
     3             formattedName = cache[name];
     4 
     5         if (!formattedName) {
     6             if ((Ext.os.is.Tizen || !Ext.feature.has.CssTransformNoPrefix) && this.prefixedProperties[name]) {//Ext.feature.has.CssTransformNoPrefix判断
     7                 formattedName = this.vendorPrefix + name;                               //结果相反了,导致执行了else里的代码,将transform
     8             }                                                         //在没有加前缀的情况下返回了回去
     9             else {
    10                 formattedName = name;
    11             }
    12 
    13             cache[name] = formattedName;
    14         }
    15 
    16         return formattedName;
    17     }

    如上所示,在判断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]);
            }
        }
    

      

  • 相关阅读:
    Get-CrmSetting返回Unable to connect to the remote server的解决办法
    Dynamics 365中的常用Associate和Disassociate消息汇总
    Dynamics 365 Customer Engagement V9 活动源功能报错的解决方法
    Dynamics Customer Engagement V9版本配置面向Internet的部署时候下一步按钮不可点击的解决办法
    Dynamics 365检查工作流、SDK插件步骤是否选中运行成功后自动删除系统作业记录
    注意,更改团队所属业务部门用Update消息无效!
    Dynamics 365的审核日志分区删除超时报错怎么办?
    Dynamics 365使用Execute Multiple Request删除系统作业实体记录
    Dynamics 365的系统作业实体记录增长太快怎么回事?
    Dynamics CRM日期字段查询使用时分秒的方法
  • 原文地址:https://www.cnblogs.com/cjpx00008/p/3535557.html
Copyright © 2011-2022 走看看