博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用HBuilder基于HTML5编写新闻客户端APP的一些实验
阅读量:7228 次
发布时间:2019-06-29

本文共 10171 字,大约阅读时间需要 33 分钟。

hot3.png

一、一些基本概念

1、HBuilder

按照我的理解,HBuilder是一个基于eclipse二次开发的IDE,主要对HTML5的开发做了许多优化,刚发布没多久,目前基本稳定,但是仍然有一些BUG。

用HBuilder开发移动APP的好处是可以连接手机实时调试APP(对于Android其实也就是用adb跟DDMS),连接手机后IDE的界面上会出现你所连接的手机,对项目中的文件修改回实时同步到手机上,下图是ADT和HB的对比:

124523_pLci_1253039.jpg

2、HTML5 Plus

其实就是HBuildeBuild发团队推出的一种框架,尽管官方说这是一个开放性的HTML5的扩展标准,但是从目前其提供的资源以及行为来看,我跟人不太认同官方的说法。目前也只有HBuilder能打包基于HTML5+的项目,同样地,HBuilder开发移动应用用的就是这个框架。

目前市场上可见的、基于此框架做的应用有CSDN的新闻客户端,基于此框架可以调用许多手机的原生服务。以下分别是CSDN的客户端及HBuilder提供的示例程序,HBuilder中有这两个项目的源代码:

125225_HJ86_1253039.jpg

二、实验目标(需求分析)

1、目标

首先,是确定一下用HB和H5+能不能做出原生手机应用的效果,其次是看看开发的复杂度。

在此,我定下一个目标:仿照网易新闻的APP,用HB做一个新闻客户端的雏形,重点是做出几个手机客户端常见的手势操作以及一些动画效果。

2、分析

下图是对网易新闻客户端使用过程的几个截图,也是这次要实现的内容:

130611_qYqX_1253039.jpg

①、首页(主页面),包含一个LOGO、一个菜单按钮、一个用户按钮、一个导航栏、一个新闻标题列,标题列的顶部是一个图片轮播栏;

②、在首页中拖动页面,页面可渐渐缩放以呈现出菜单,点击菜单按钮也是同样的效果;

③、在首页左右拖动可在头条、推荐、娱乐等导航间进行切换;

④、点击新闻列表进入新闻内容页,在内容页中拖动页面可返回。

第一步就是实现上面的内容,其中大部分手势操作在许多原生应用中都是司空见惯的,但是对于H5应用来说还是比较罕见,也没有几个案例可以参考,因此就蛮力开撸了。

三、正文

1、大概思路

既然是基于HTML5的,那么就必须发挥HTML5的优势。其一就是兼容性,一次开发即可在多个平台上使用,这就意味着同样需要在代码中进行设备检测,也要进行屏幕自适应,这些都是移动端网站用得比较多的手段了,也不再详述。官方也说了建议别用各种JS库,那么所有动画效果、页面布局都需要自己来写。

当然,必要的偷懒还是得有的,用个jQuery,在不影响性能的地方使用也无妨。

按照上面的分析,目前要做的是两个页面:第一个页面就是新闻列表页,包含那个缩放菜单;第二个页面是新闻内容的查看页面。

看完HTML5+的文档(虽然内容不多,但是该有的内容都有,还是不错的),大概明白了新闻列表页中的所有效果都得基于HTML5来写,也就是说,写这个页面可以用PC浏览器来进行调试。

进入新闻内容页的时候,可以用H5+提供的窗体接口,此页面中拖动返回之类的操作可以利用修改窗体的位置来实现。

页面中的图标都可以在FontAwesome这个图标字体里边找到,可以省下一大笔时间。

那么,接下来就是开撸了。

2、代码结构

代码列表如下:

132917_yN1v_1253039.jpg

index是新闻列表页,view是新闻内容查看页,pic是图片浏览页(点击图片会进到这个页面,先放置);js目录中,frame.js中放置一些公用代码,main.js是处理index逻辑的代码,main.XXX的几个js文件则是头条、推荐等导航的逻辑分管。

3、DOM操作及模板机制

从图片轮播到新闻列表,都可以先做好一个模板然后给予模板动态生成,所以,首先,我写了几个模板:

    
        
                 
        
         
        
        
         
        
        
         
        
新闻标题新闻标题新闻标题新闻标题新闻标题         
副标题描述副标题描述副标题描述副标题描述        
    

上面的代码中,从上而下分别是图片轮播的轮播页、图片轮播的标题页、新闻标题列表项的模板。HTML代码中有data-node这个属性,这是后面用来获取DOM用的,data-node的相关JS代码如下:

var F = window['F'] = {    getNodes : function(obj){        var i;        if(!obj.childNodes || obj.childNodes.length == 0){            return obj;        }        for(i = 0; i < obj.childNodes.length; i ++){            if(obj.childNodes[i].dataset && obj.childNodes[i].dataset.node){                obj[obj.childNodes[i].dataset.node] = F.getNodes(obj.childNodes[i]);            }        }        return obj;    }};

也就是一个简易的DOM获取的方式,通过这句代码:

_doc = F.getNodes($('body')[0]);

可以分层遍历获取body下所有带了data-node的节点,譬如上面的模板,可以通过_doc.tpl.imghead来取得图片轮播的模板。

4、页面

调界面的过程是痛苦的,持续三四个小时的眼镜干涩的调整过程不提,总而言之最终写好的页面长这样:

134002_WNAo_1253039.jpg

仿真度也足够了吧?在此感谢FontAwesome,不然画图标可能还要花上一两个小时……

(其实做完界面之后我就下班了,那天是4.29,就快五一了,心情激动啊)

最终首页的布局如下:

    
    
    
...    
    
    
    
    
    
    
    
    
    
    
新闻 
订阅
图片
视频
跟帖
电台  
 
 
头条 
推荐
娱乐 
体育
财经
 
 
 
 
   
 
 
 
 
新闻标题新闻标题新闻标题新闻标题新闻标题 
副标题描述副标题描述副标题描述副标题描述

新闻内容页的布局如下:

			
 
 
1245
新闻标题
副标题 02-02 00:00

      

新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容

新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容

新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容

新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容

新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容

新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容

新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容

新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容新闻内容

 
 

5、屏幕自适应

做这个的时候,手头上的测试机有:iPhone 5、Note 3、MI 3、MI 2S

其实移动端的页面自适应,最关键的还是devicePixelRatio这个值,看iPhone4的分辨率比3GS高了四倍,其实对于网页来说还是一样的分辨率,看Note 3的1080P多高,其实在页面上比起肾果来也多不了多少……

因此,我可耻地用了最省事的自适应方法:在页面加载完后一次insertRule来修改样式!

这样做的好处是省事省资源(生下来的资源可以做很多事啊……),坏处是面对屏幕翻转、页面尺寸更改等事件会表现得一塌糊涂。

首先,我用了这么一坨代码来获取页面的尺寸属性以及设定一些属性

G = {};// 屏幕宽度G.WIDTH                = $(window).width(),// 屏幕高度G.HEIGHT               = $(window).height(),// 首页滑到这个位置则判定用户要打开左侧菜单G.LIMIT_SLIDE_PAGESIDE = G.WIDTH / 4;// 菜单打开时,首页滑到这个位置则认为用户要关闭菜单G.LIMIT_SLIDE_PAGEBACK = G.WIDTH / 4 * 3;// 菜单打开时首页页面滑到这个位置G.SITE_X_PAGESIDE      = G.WIDTH / 3 * 2;// 页面缩放最小值G.SCALE_MAX_PAGESIDE   = 0.2;// 菜单打开时页面缩放的比率G.SCALE_PAGESIDE       = 1 - G.SITE_X_PAGESIDE / G.WIDTH * G.SCALE_MAX_PAGESIDE;// 菜单滑动的动画时间G.TIME_SLIDE_ANIMATE   = 200;// 页面导航切换的动画时间G.TIME_SPAGE_ANIMATE   = 200;// 菜单打开时,菜单的顶部对齐缩小后的页面顶部G.SITE_SCALE_BOTTOM    = (G.HEIGHT - G.SCALE_PAGESIDE * G.HEIGHT) / 2;

然后,我又用了这么个方式来让一些页面元素自适应:

style = document.styleSheets[document.styleSheets.length - 1];style.insertRule('.container{height:' + (G.HEIGHT - 80) + 'px;}', 0);style.insertRule('.content{height:'   + (G.HEIGHT - 80) + 'px; width:' + G.WIDTH + 'px;}', 0);style.insertRule('.content-imghead{height:'   + (G.WIDTH / 2 - 1) + 'px; width:' + G.WIDTH + 'px;}', 0);style.insertRule('.content-imghead-img{height:'   + (G.WIDTH / 2 - 1) + 'px; width:' + G.WIDTH + 'px;}', 0);style.insertRule('.content-imghead-container{height:'   + (G.WIDTH / 2 - 1) + 'px;}', 0);style.insertRule('.content-horitem{width:'   + (G.WIDTH - 24) + 'px;}', 0);

CSS的代码很枯燥,在此就不提了。

6、手势动画

这其实是APP最重要的部分,手势的合理与否很大程度上决定了应用的生死(这是一个伏笔)。

左右滑动可以通过设置style.left的值、可以用translateX来实现,缩放效果则只能用transform的scale函数来实现(这也是一个伏笔)。

我定义了一堆变量用来存放动画过程的一些参数:

// 运行时参数R = {};R.isIOS = navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/iPad/i);R.PAGE_S    = 'main',R.container = _doc.page_main.container;R.navIndex  = 0;R.navCount  = 5;R.animating = false;R.scaleRate = 1;// 页面从哪里开始滑R.fromX     = 0;// 页面滑动的步进R.step_t    = 64;// 页面缩放的步进R.step_s    = (1 - G.SCALE_PAGESIDE) / G.SITE_X_PAGESIDE * 64;

其中,isIOS这个是直接抄CSDN的APP的,在写完这个之后我还做了对chrome、ASOP自带浏览器等的判断,最终还是删掉了。

在滑动页面开关菜单的过程中,最重要的是保证动作的合理与流畅,譬如用户滑动到一定程度后松手时,页面是回弹(操作取消)还是自动滑到另一边(操作生效)、自动滑动的过程中滑动速率跟用户手动滑动的部分速率一致、动画效果不卡帧等。

因此,我在新闻列表页中定义了五套touch事件处理函数来分别处理不同的操作,分别是:

①、没有任何操作时(路由)

②、动作可能是滑到左侧列表菜单

③、左侧菜单打开时的响应

④、可能是左侧菜单滑回页面

⑤、可能是 主页横向左侧翻页

⑥、 主页横向右侧翻页

判定逻辑如下:

T.on(_doc, 'start', function(e){    var sx, sy, cx, cy, rt, fx, ft, step_t, step_s, an = false;    switch(e.touches.length){    // 触摸事件    // 单点手势    case 1:        sx = e.touches[0].clientX;        sy = e.touches[0].clientY;        switch(R.PAGE_S){        // 在主屏幕中,判断是否切换到菜单        case 'main':            T.on(_doc.page_main, 'move'  , m0);            T.on(_doc.page_main, 'end'   , e0);            T.on(_doc.page_main, 'calcel', c0);                             break;        // 在左侧菜单中,判断是否切换回主屏幕        case 'side-l':            T.on(_doc.page_main.cover, 'move'  , m2);            T.on(_doc.page_main.cover, 'end'   , e2);            T.on(_doc.page_main.cover, 'calcel', c2);               break;        }        break;    default:        break;    }    /* ... */}

(因为是第一次用H5做APP,对其不熟悉,也许其中有不少多余的部分)

T.on是另外封装的一个兼容函数,相关代码如下:

var T = window['T'] = {    on : function(dom, type, func){        if(("ontouch" + type) in dom){            dom["ontouch" + type] = func;        }else{            dom.addEventListener("touch" + type, func);        }    },    off : function(dom, type, func){        if(("ontouch" + type) in dom){            dom["ontouch" + type] = null;        }else{            dom.removeEventListener("touch" + type, func);        }    },    offNormal : function(dom, func1, func2, func3){        if('ontouchstart' in dom){            dom.ontouchmove   = null;            dom.ontouchcancel = null;            dom.ontouchend    = null;        }else{            dom.removeEventListener('touchmove',   func1);            dom.removeEventListener('touchend',    func2);            dom.removeEventListener('touchcancel', func3);        }    }};

图片轮播则用了另外的一套touch事件,绑定在imghead上。

说起拖动的原理,在我的理解中,关键就是两个坐标:开始拖动时的横坐标SX、当前拖动位置的横坐标CX,拖动距离就是CX - SX,那么只需要把页面的位移设置为CX - SX就完事了。

当触摸事件结束之后,该如何自动完成剩下的动画?最初,我是直接用了CSS3的transition属性,当触摸结束后,给元素加一个transition:linear;之类的样式,动画效果结束后再去掉这个样式,后来,我发现这个样式在android平台下存在一个略微严重的问题:卡。

也不知道ASOP自带的浏览器是怎么回事,也不知道为什么HBuilder在打包的时候要封装Android的自带浏览器,这个浏览器的性能真的很糟糕,无论多好的手机,只要用这个浏览器,其表现跟若干年前的单核手机没什么区别。

其实说到这,这篇文章已经可以结束了,事实上,做到这,我已经没有什么激情再把这个应用原型写下去了,因为我预感目前在Android上H5+是玩不下去了。

最后我用了这么一个方式来兼顾动画效果:

function menuSlideIn(){    if(R.animating){        return;    }    R.PAGE_S =  'side-l';    if(R.isIOS){        _menuSlideIn();    }else{        $(_doc.page_main.cover).show();        _doc.page_main.style.left = G.SITE_X_PAGESIDE + 'px';    }}function _menuSlideIn(){    R.animating                          =  true;    R.scaleRate                          -= R.step_s;    R.fromX                              += R.step_t;    _doc.page_main.style.left            =  R.fromX + 'px';    _doc.page_main.style.webkitTransform =  'scale(' + R.scaleRate + ', ' + R.scaleRate + ')';    if(R.fromX >= G.SITE_X_PAGESIDE){        $(_doc.page_main.cover).show();        R.animating                          = false;        _doc.page_main.style.left            = G.SITE_X_PAGESIDE + 'px';        _doc.page_main.style.webkitTransform = 'scale(' + G.SCALE_PAGESIDE + ', ' + G.SCALE_PAGESIDE + ')';    }else{        requestAnimationFrame(_menuSlideIn);    }}

对于左侧的菜单,在非IOS上禁用缩放效果。甚至在用户拖动完毕后不显示动画效果而直接让菜单闪到那个位置,卡帧的动画还不如不播,因此我干脆用了requestAnimationFrame这个Android上没有的函数。

最后是新闻内容页,用户点击新闻时,调用这么个方法打开新页面:

plus.ui.createWindow("view.html").show('slide-in-right', 200);

万幸,H5+自带了一系列的窗口打开、关闭动画效果。

因为这个页面是在新窗口的,因此要实现拖动返回的话,就不再是设置DOM的style.left属性了,而是调用这么个方法:

W.setOption({left : ox});

来设置窗体的位置。

相应地,设置窗体位置就要换一种算法了,因为窗体移动后,每次检测到的X位置所相对的位置都改变了。

假设页面一开始的偏移是OX = 0,用户触摸屏幕的位置是SX,用户第一次移动时的位置是CX,那么,把OX设置为CX - SX,然后把页面的位置设置为OX;

第二次以及之后的移动事件中,OX = OX + CX - SX,然后把页面的偏移设置为OX;

结束触摸之后,假如当前页面的偏移达到了返回的判定点,那么调用:

plus.ui.closeWindow(W, 'slide-out-right', 200);

关掉这个窗口,否则让页面动画返回左边缘。

在IOS上,以上的动画效果很流畅完美地实现了,但是,不知道是不是我的使用方式不对,在Android下这段代码的表现是悲剧的。

在拖动过程中,页面不断地闪动,拖着拖着,页面就消失了!

所以,这段代码,我是这么写的:

function m1(e){    cx = e.touches[0].clientX;    cy = e.touches[0].clientY;    _doc.page_view.header.tip.innerHTML = cx;    if(R.isIOS){        if(lx !== null){            ox = ox + cx - sx;            W.setOption({                left : ox            });        }else{            ox = cx - sx;            W.setOption({                left : ox            });        }    }}

是的,对于非IOS的系统,我直接取消掉了这个效果。

至此,文章真的要结束了,因为在Android上,这个应用雏形的用户体验已经低到了极致。

四、总结

实验结果是:H5+可以做APP,不太能做APK。

我衷心希望这篇文章里出现的Android下的糟糕表现是我的技术及实现思路太糟糕而造成的,因为我真心渴望可以直接用HTML5写出媲美原生的应用的那一天的到来。

也许代码中还有很多的可以优化的地方,也许进行极致的优化之后应用可以运行得比较流畅,但这却是不是我所想看到的,需要做得这么完美才能实用的技术,是无法推广的。

对这个半成品都算不上的应用感兴趣的话,可以移步看看实际效果

对代码感兴趣的同学可以把svn://

最后的最后,希望大家可以告诉我是不是真的哪里想歪了,总感觉HBuilder不应该那么脆弱……

转载于:https://my.oschina.net/u/1253039/blog/262974

你可能感兴趣的文章
awk 文本处理
查看>>
【JSConf EU 2018】主题总结 (部分主题已有中文文章)
查看>>
JavaScript面向对象名词详解
查看>>
Java设计模式学习 - 责任链模式
查看>>
JVM,DVM,ART
查看>>
webgl滤镜--会呼吸的痛
查看>>
用Go语言实现微信支付SDK
查看>>
oauth2在php实践
查看>>
LeetCode.914 卡牌分组
查看>>
填坑app:compileDebugJavaWithJavac
查看>>
Android 100+行实现本地跳一跳辅助(不需要连接电脑)
查看>>
位状态的使用
查看>>
面试技术题笔记
查看>>
Myth源码解析系列之一-项目简介
查看>>
JS易混淆的方法整理
查看>>
iOS下JS与OC互相调用(八)--Cordova详解+实战
查看>>
七牛实时音视频云视频连线demo(web部分)
查看>>
Netty源码分析(六):SelectedSelectionKeySetSelector
查看>>
forEach,for...of,map与asycn/await
查看>>
springboot 2 Hikari 多数据源配置问题(dataSourceClassName or jdbcUrl is required)
查看>>