JavaScript使用DeviceOne开发实战(三)仿微信应用


Posted in Javascript onDecember 02, 2015

这是一个系列的文档,长期目标是利用DeviceOne开发一些目前使用广泛的优质手机应用,我们会最大化的实现这些应用的每一个功能和细节,不只停留在简单的UI模仿和Demo阶段,而是一个基本可以使用的实际App。 

在实现的过程中,会有很多困难,还会发现有一些功能目前缺乏组件支持而无法实现,也会碰见各种移动开发中都会碰到的常见技术问题。一步一步的操作和问题的解决可以让开发者直观的了解通过DeviceOne如何开发一个实际App,也可以了解移动开发本身的很多技术细节,可以让App开发者少走很多弯路。

这个文档主要介绍微信的模仿。

第一部分是框架的搭建

UE和UI设计,通常App的开发需要产品人员的UE设计和美工人员的UI设计,这二个步骤完成后才开始功能实现。我们现在是模仿已有的微信,这些步骤都可以省去,看下图是美工提供的主界面UI设计图,里面的元素的尺寸都标记了。
 

JavaScript使用DeviceOne开发实战(三)仿微信应用

1.新建项目:我们选择了Simple template,选择空模板是因为我们可以更细致的讲解开发的过程。实际上这个项目是可以选择Multi View with ViewShower模板更合适。

JavaScript使用DeviceOne开发实战(三)仿微信应用

 2.简单分析一下主界面,整个主界面的尺寸是iphone6的尺寸750*1334. 分上下二个部分,底部是一个Bottom Bar导航栏,上面是4个独立的界面,这4个界面永远只有一个界面显示,其他三个界面在内存中,靠底部的导航来切换,这个适合用do_ViewShower为框架的主体如下图:

JavaScript使用DeviceOne开发实战(三)仿微信应用

对应这个设计,我们在新建的项目上删除自动生成的button,添加一个do_ALayout组件和do_ViewShower组件,设置它们的高宽和x,y坐标:

JavaScript使用DeviceOne开发实战(三)仿微信应用

3.接下来我们为ViewShower增加4个分页,在这里我还强调一下,实际App会多人参与,我们的代码结构要清晰易读,命名尽量使用英文,实在不行使用中文全拼,包括要建立多级子目录,不要都混在一起。在这里我们增加4个子目录:chats, contacts, discover, me. 右键每个子目录,选择New--Other--DeviceOne--UI File,创建4个index.ui,对应的index.ui.js会自动生成。

JavaScript使用DeviceOne开发实战(三)仿微信应用

这里注意把这4个ui文件对应的根节点ALayout的高度设置为1220,因为这4个ui文件都是主界面的ViewShower的子view,不应该超过ViewShower的大小。

另外为每个ui里都添加一个label,标记中文名,只是一会儿debug看真实效果更清楚。

JavaScript使用DeviceOne开发实战(三)仿微信应用

4.Bottom Bar内简单添加4个按钮,然后添加相应的js代码实现ViewShower的页面切换功能。这里提一个技巧,选中2个或以上组件,可以使用各种对齐方式的功能,如下图:

JavaScript使用DeviceOne开发实战(三)仿微信应用

5.在index.ui.js里添加ViewShower初始化的代码和按钮的点击事件。

var viewshower = ui("viewshower");
var page = sm("do_Page");
// 为viewshower增加4个子页面
viewshower.addViews([ {
    id : "chats",// 页面的标示
    path : "source://view/chats/index.ui"// 页面的路径
}, {
    id : "contacts",
    path : "source://view/contacts/index.ui"
}, {
    id : "discover",
    path : "source://view/discover/index.ui"
}, {
    id : "me",
    path : "source://view/me/index.ui"
} ]);
// 初始化先显示第一个页面
viewshower.showView("chats");
var button1 = ui("do_Button_1");
button1.on("touch", function() {
    viewshower.showView("chats");
});
var button2 = ui("do_Button_2");
button2.on("touch", function() {
    viewshower.showView("contacts");
});
var button3 = ui("do_Button_3");
button3.on("touch", function() {
    viewshower.showView("discover");
});
var button4 = ui("do_Button_4");
button4.on("touch", function() {
    viewshower.showView("me");
});

6.我们来真机看看运行效果,启动设计器的调试服务,启动手机端的调试程序,最后我们看到的效果图如下,iOS和Android界面完全一样。点击底部的4个按钮,切换效果都是好的:

JavaScript使用DeviceOne开发实战(三)仿微信应用

7. 这一节的内容就到这里,框架工作完成了吗?只能说框架的第一步工作完成,如果我们有很多同事一起开发这个App的话,我们可以把工作开始分开并行,接下来分5个部分:

* Bottom Bar的完成
* /chats/index.ui的完成
* /contacts/index.ui的完成
* /discover/index.ui的完成
* /me/index.ui的完成
多人并行工作的前提是代码版本管理比如SVN,GIT,我们这里使用GIT,地址是https://github.com/do-project/Fake-Weixin,每一节结束后我们都会提交GIT服务,你可以下载这个节点的代码参考。我们也会在附件附加这一节的项目代码。

下一节我们完成第一个子任务,BottomBar的实现。

-------------------------------------------------------------

这一节主要是完成底部导航栏的实现。

0. 我们首先来分析一下界面效果图和设计图

JavaScript使用DeviceOne开发实战(三)仿微信应用

 JavaScript使用DeviceOne开发实战(三)仿微信应用

整个底部导航分4个重复的部分,每个部分由一个图片ImageView和底部标题Label,以及右上角标示,这个标示可以用圆角Label来实现,这个标示缺省的时候应该是隐藏的。

1. 第一步我们得找到对应的图片资源,通常开发这些资源由美工提供,我们现在模仿微信,最好的方式是从微信原生安装包里获取,不能直接靠截图,而是打开微信ios,android安装包,ios的安装包是ipa和android的安装包apk都是一个压缩文件,解开可以获取到一些图片资源。目前我只需要底部8个图标,包括未点中以及点中的高亮图标,外面把这些图标放在image目录下

JavaScript使用DeviceOne开发实战(三)仿微信应用

2. 先删除先前增加的4个临时按钮,然后按照美工提供的尺寸数据布局好新的组件,包括4个do_ImageView组件和4个Label组件以及4个右上角的Label
简单计算可以得出ImageView的大小是60*60。这里有一个小技巧,设置好一组ImageView和Label后选中2个组件,然后右键“Copy”,然后再"Paste"三次,还可以选中多个组件各种对齐。

JavaScript使用DeviceOne开发实战(三)仿微信应用

再细调一下,把图片和文字设置好,图片设置就是设置ImageView的source属性,Label需要设置文字居中,设置textAlign属性为center,设置字体,设置背景色,前景色等等,设置右上角三个Label的visible为false。中间添加一个ALayout设置背景为灰色,作为ViewShower和Bottom Bar上下的分割线. 这里要注意,右上角的正圆形Label的实现方式就是设置border属性,border设置为FF0000FF,1,15 表示边界线的颜色是红色,宽度1,圆角半径15(Label的宽高都是30),从而实现正圆。

 JavaScript使用DeviceOne开发实战(三)仿微信应用

在真机上测试一下效果,iPhone和Android手机真机的效果图如下:

JavaScript使用DeviceOne开发实战(三)仿微信应用<ignore_js_op>JavaScript使用DeviceOne开发实战(三)仿微信应用

3. 这个时候会有2个问题,如果给ImageView上添加点击事件的话,用户必须点中这个图片才能触发点击,这样体验不好。第二个问题是图片在Android上稍微有点变形,如果在比如iPhone4上,可能圆形会变成椭圆,这个问题是由于不同手机宽高比的差异。

要解决的办法是:

* 把Bottom Bar所在的ALayout增加4个一样大小的子ALayout,然后把imageview,label都放在对应的子ALayout上,然后给子ALayout增加点击事件,这样用户的手指只要接触到差不多位置都能触发事件
* 把上面的4个子ALayout的isStretch属性改成false,这个原理可以参考文档 ALayout的示例demo

JavaScript使用DeviceOne开发实战(三)仿微信应用

4. 修改index.ui.js,添加代码主要是在底部bottom bar切换按钮的时候修改所有图标的颜色和字的前景色。

var button = ui("do_Button_");
var imageview = ui("do_ImageView_");
var label = ui("do_Label_");
button.on("touch", function() {
    showView("chats");
});
var button = ui("do_Button_");
var imageview = ui("do_ImageView_");
var label = ui("do_Label_");
button.on("touch", function() {
    showView("contacts");
});
var button = ui("do_Button_");
var imageview = ui("do_ImageView_");
var label = ui("do_Label_");
button.on("touch", function() {
    showView("discover");
});
var button = ui("do_Button_");
var imageview = ui("do_ImageView_");
var label = ui("do_Label_");
button.on("touch", function() {
    showView("me");
});
function showView(name) {
    viewshower.showView(name);
    if (name == "chats") {
        imageview.source = "source://image/tabbar_mainframeHL.png";
        label.fontColor = "BBFF";
    } else {
        imageview.source = "source://image/tabbar_mainframe.png";
        label.fontColor = "FFFFF";
    }
    if (name == "contacts") {
        imageview.source = "source://image/tabbar_contactsHL.png";
        label.fontColor = "BBFF";
    } else {
        imageview.source = "source://image/tabbar_contacts.png";
        label.fontColor = "FFFFF";
    }
    if (name == "discover") {
        imageview.source = "source://image/tabbar_discoverHL.png";
        label.fontColor = "BBFF";
    } else {
        imageview.source = "source://image/tabbar_discover.png";
        label.fontColor = "FFFFF";
    }
    if (name == "me") {
        imageview.source = "source://image/tabbar_meHL.png";
        label.fontColor = "BBFF";
    } else {
        imageview.source = "source://image/tabbar_me.png";
        label.fontColor = "FFFFF";
    }
}

到此为止,底部导航栏基本实现完成,这一节比较简单,主要是一些细致的ui拖拽调整。我们用调试版本看一下Android,iOS的效果都非常不错。

我们开始实现ViewShower的第一页主体内容.

---------------------------------------------------------------------------------------

接上一节 底部导航  ,我们这一节主要是完成微信4个主页面的第一个页面“微信”页面,这一节内容比较多,我们分多个跟帖来完成

0 老规矩,先分析一下UI,由三个大部分组成,系统状态栏,工具栏和微信聊天记录列表。

 JavaScript使用DeviceOne开发实战(三)仿微信应用

1. 系统状态栏高度40,背景黑色,我们注意到微信的首页4个子页面都有这个系统状态栏,这样我们需要做一个整体框架的调整,没有必要为4个子页面都添加一个状态栏,只需要在ViewShower上添加一个就可以,对应的ViewShower和子页面的高度都变成1180. 

JavaScript使用DeviceOne开发实战(三)仿微信应用

对应设计器里index.ui, chats/index.ui, contacts/index.ui, discover/index.ui, me/index.ui 都要作相应的height,y属性值的变化。效果如下: 

JavaScript使用DeviceOne开发实战(三)仿微信应用

2. 我们回到chats/index.ui ,先增加工具导航栏(高度80)和里面的标题和工具按钮,这个需要增加加号的资源文件,因为这个文件是chats页面专有的,所以存在image/chats/bar_add.png下。
我们看看真机效果,我们注意到顶部多出一块黑色区域,这是设计器里的增加的状态栏,因为这个页面是从系统状态栏开始往下绘制的,所以会把设计器里这一部分多出来,

JavaScript使用DeviceOne开发实战(三)仿微信应用

要解决的方法是修改app.js,openPage增加一个statusBarState参数(API文档),设置为transparent表示页面从屏幕最顶端开始绘制。

var d1 = require("deviceone");
var app = d1.sm("do_App");
app.on("loaded", function() {
    this.openPage({
        source : "source://view/index.ui",
        statusBarState : "transparent"
    });
});

再来看看真机效果图:

JavaScript使用DeviceOne开发实战(三)仿微信应用

3. 主体部分是一个do_ListView,接下来设置ListView的cell和数据。
ListView的cell指列表框的每一行,比如ListView有100行数据,实际上可见的屏幕永远只能看到8,9行左右,所以我们手势上下滑动的时候并没有创建100行,而是重复使用这8,9行,只不过替换里面的数据而已。我们称之为行模板,在DeviceOne里这种模板也是一个ui文件,比如这里我们在chats子目录下新建一个chat_cell.ui,这个ui基本界面如下:

JavaScript使用DeviceOne开发实战(三)仿微信应用

按照美工的设计尺寸我们来拖拽UI

JavaScript使用DeviceOne开发实战(三)仿微信应用 

这里同样需要考虑纯圆变形问题,需要设置好文字大小,前景色等属性,大家可以看到里面有多个do_Label,do_ImageView组件,由于模板ui是靠后期绑定数据的,所以在设计阶段都是空白的。

JavaScript使用DeviceOne开发实战(三)仿微信应用

接下来我们需要设计chat_cell.ui对应的数据,通常为了用户体验,需要尽可能的减少网络交互,页面打开的时候通常先读取本地的数据文件,把界面显示出来,然后再考虑是否要进行网络连接来获取最新数据,所以App开发需要仔细考虑数据的本地化读写和数据的时效性的平衡。
DeviceOne的传递数据基本上都是标准的JSON格式。如下图,chat_cell.ui里的组件属性和JSON数据结构对应的关系

JavaScript使用DeviceOne开发实战(三)仿微信应用

对应的映射关系的代码在chat_cell.ui.js如下,我们可以看到映射关系的左边是组件id.组件属性名,右边是数据JSON的key名称:

//related to chat_cell.ui
var root = ui("$");;//$是这个ui文件根节点组件的通配符,如果指定组件的id,也可以用id来获取对象
root.setMapping({
    "photo_imageview.source" : "photo",
    "name_label.text" : "name",
    "lastmessage_label.text" : "lastmessage.message",
    "lasttime_label.text" : "lastmessage.time",
    "unread_label.visible" : "unread",
    "unread_label.text" : "unread-count",
    "name_label.fontColor" : "isgroup",
});

对应的数据本应该是第一次运行从网络上获取之后再缓存到本地的,我们是模拟,所以先手动生成一个文件到data/chats/chat.json

JavaScript使用DeviceOne开发实战(三)仿微信应用

4. 我们回到chats/index.ui,我们需要给这里ui文件里的listview设置模板,绑定数据。
设置index.ui 里的listview的templates属性为        source://view/chats/chat_cell.ui
注意:chat_cell.ui存储在source/default/view/chats/chat_cell.ui,但是source://的根节点指的是 source/default/目录
在index.ui.js里添加绑定数据的代码

//related to index.ui
var storage = sm("do_Storage");
var listdata = mm("do_ListData");
var listview = ui("listview");
var json_path = "data://chats/chat.json";//本地缓存的数据
if (storage.fileExist(json_path)) {
    storage.readFile(json_path, function(data, e) {
        //deviceone.print(JSON.stringify(data));
        listdata.addData(data);
        listview.bindItems(listdata);
        listview.refreshItems();
    })
}
var page = sm("do_Page");
page.on("loaded",function(){
    //这个页面加载完显示出来后触发这个事件
    //我们可以在这个事件里去获取最新的网络数据,来更新listview和data/chats/chat.json
});

我们在真机上看看效果

JavaScript使用DeviceOne开发实战(三)仿微信应用

在运行中有几个细节:

* 上下滑动的时候,图片不断的刷新,原因是我们的ImageView的source是网络图片,每次显示的时候都是从网络上获取的,所以这里需要把chat_cell.ui里的ImageView的cacheType属性换成"always" 意思是只从网络上读取一次就会缓存到本地,下一次不会再从网络上读取了。关于cacheType属性参考API文档
* ImageView也是圆角的,圆角通常可以使用border属性来设置,但是android只有ImageView不能通过border来设置圆角,ImageView还有一个专有属性radius来设置Android才有效,这个我们以后可以改进

5. 处理右上角的add按钮,点击弹出菜单

先给右上角ImageView的enable属性设置为true,才可以处理点击事件,在chats/index.ui.js里添加代码

var add_button = ui("add_imageview");
add_button.on("touch", function() {
    var menu = ui("menu_id");
    if (menu) {//如果已经add过,就只是让这个view显示,而不是add一个新的
        if (menu.visible == false)
            menu.visible = true;
    } else {
        main.add("menu_id", "source://view/chats/chat_add_menu.ui");
    }
});

其中chat_add_menu.ui 是弹出的菜单对应的ui文件,这个ui文件的根节点大小和chat/index.ui一样,这样确保我点击任何空白处都可以关闭这个菜单(实际上是隐藏这个菜单),我们在这个ui文件里把对应的布局都拖拽好,其中需要添加4个资源png文件。

这里有个小技巧,顶部的三角形标记只能通过一个ImageView加载一个三角形图标来实现。

JavaScript使用DeviceOne开发实战(三)仿微信应用

我们再给chat_add_menu的根节点添加点击事件,点击的时候把自己隐藏,在chat_add_menu.js

var root = ui("$");
root.on("touch",function(){
    root.visible = false;
});

最后我们先看看真机效果,点击加号弹出菜单,点击任何地方都把菜单隐藏。

JavaScript使用DeviceOne开发实战(三)仿微信应用

这一节暂时先到这里,我们先开始拖拽后几个主页面,那几个页面基本完成后再重新回到这一个页面来细琢。

Javascript 相关文章推荐
更优雅的事件触发兼容
Oct 24 Javascript
node.js不得不说的12点内容
Jul 14 Javascript
基于Jquery+div+css实现弹出登录窗口(代码超简单)
Oct 27 Javascript
js clearInterval()方法的定义和用法
Nov 11 Javascript
JS百度地图搜索悬浮窗功能
Jan 12 Javascript
Angular实现跨域(搜索框的下拉列表)
Feb 16 Javascript
jQuery实现可编辑表格并生成json结果(实例代码)
Jul 19 jQuery
原生js FileReader对象实现图片上传本地预览效果
Mar 27 Javascript
详解Vue2.0配置mint-ui踩过的那些坑
Apr 23 Javascript
微信小程序 高德地图路线规划实现过程详解
Aug 05 Javascript
jquery更改元素属性attr()方法操作示例
May 22 jQuery
详解如何在Canvas中添加事件的方法
Apr 17 Javascript
jquery密码强度校验
Dec 02 #Javascript
基于jQuery实现的双11天猫拆红包抽奖效果
Dec 01 #Javascript
jquery实现全选、反选、获得所有选中的checkbox
Sep 13 #Javascript
js实现继承的5种方式
Dec 01 #Javascript
6种javascript显示当前系统时间代码
Dec 01 #Javascript
基于jQuery实现网页打印功能
Dec 01 #Javascript
jQuery-1.9.1源码分析系列(十一)DOM操作续之克隆节点
Dec 01 #Javascript
You might like
PHP编码规范之注释和文件结构说明
2010/07/09 PHP
PHP STRING 陷阱原理说明
2010/07/24 PHP
PHP实现对png图像进行缩放的方法(支持透明背景)
2015/07/15 PHP
php轻松实现文件上传功能
2016/03/03 PHP
jquery 图片截取工具jquery.imagecropper.js
2010/04/09 Javascript
Js中setTimeout()和setInterval() 何时被调用执行的用法
2013/04/12 Javascript
JQuery页面图片切换和新闻列表滚动效果的具体实现
2013/09/26 Javascript
特殊日期提示功能的实现方法
2016/06/16 Javascript
深入理解jQuery3.0的domManip函数
2016/09/01 Javascript
点击页面任何位置隐藏div的实现方法
2016/09/05 Javascript
详解Angular.js的$q.defer()服务异步处理
2016/11/06 Javascript
在vue-cli脚手架中配置一个vue-router前端路由
2017/07/03 Javascript
浅谈JS封闭函数、闭包、内置对象
2017/07/18 Javascript
基于Vue、Vuex、Vue-router实现的购物商城(原生切换动画)效果
2018/01/09 Javascript
Nodejs异步回调之异常处理实例分析
2018/06/22 NodeJs
mongodb初始化并使用node.js实现mongodb操作封装方法
2019/04/02 Javascript
javascript自定义加载loading效果
2020/09/15 Javascript
[45:17]DOTA2-DPC中国联赛定级赛 Phoenix vs DLG BO3第三场 1月9日
2021/03/11 DOTA
python实现dnspod自动更新dns解析的方法
2014/02/14 Python
Python实现大文件排序的方法
2015/07/10 Python
python matplotlib坐标轴设置的方法
2017/12/05 Python
python实现推箱子游戏
2020/03/25 Python
python 多进程并行编程 ProcessPoolExecutor的实现
2019/10/11 Python
Pandas实现DataFrame按行求百分数(比例数)
2019/12/27 Python
深入浅析python的第三方库pandas
2020/02/13 Python
解决python多线程报错:AttributeError: Can't pickle local object问题
2020/04/08 Python
Python使用tkinter实现摇骰子小游戏功能的代码
2020/07/02 Python
Python常用GUI框架原理解析汇总
2020/12/07 Python
如何利用input事件来监听移动端的输入
2016/04/15 HTML / CSS
网站设计师的岗位职责
2013/11/21 职场文书
水果连锁超市创业计划书
2014/01/24 职场文书
政府信息公开实施方案
2014/05/09 职场文书
解除劳动合同通知书范本
2015/04/16 职场文书
地道战观后感
2015/06/04 职场文书
【DOTA2】总决赛血虐~ XTREME GAMING vs MAGMA - OGA DOTA PIT 2022 CN
2022/04/02 DOTA
搭建Yolov5服务器
2022/04/30 Servers