首頁  >  文章  >  web前端  >  JavaScript使用DeviceOne開發實戰(三)仿微信應用_javascript技巧

JavaScript使用DeviceOne開發實戰(三)仿微信應用_javascript技巧

WBOY
WBOY原創
2016-05-16 15:28:201064瀏覽

這是一個系列的文檔,長期目標是利用DeviceOne開發一些目前使用廣泛的優質手機應用,我們會最大化的實現這些應用的每一個功能和細節,不只停留在簡單的UI模仿和Demo階段,而是一個基本上可以使用的實際App。 

在實現的過程中,會有很多困難,還會發現有一些功能目前缺乏組件支援而無法實現,也會碰見各種行動開發中都會碰到的常見技術問題。一步一步的操作和問題的解決可以讓開發者直覺的了解透過DeviceOne如何開發一個實際App,也可以了解行動開發本身的許多技術細節,可以讓App開發者少走很多彎路。

這篇文件主要介紹微信的模仿。

第一部分是框架的建造

UE和UI設計,通常App的開發需要產品人員的UE設計和美工人員的UI設計,這二步驟完成後才開始功能實作。我們現在是模仿已有的微信,這些步驟都可以省去,看下圖是美工提供的主介面UI設計圖,裡面的元素的尺寸都標示了。
 


1.新專案:我們選擇了Simple template,選擇空模板是因為我們可以更細緻的講解開發的過程。實際上這個專案是可以選擇Multi View with ViewShower範本比較合適。

 2.簡單分析一下主介面,整個主介面的尺寸是iphone6的尺寸750*1334. 分上下二個部分,底部是一個Bottom Bar導航欄,上面是4個獨立的介面,這4個介面永遠只有一個介面顯示,其他三個介面在記憶體中,靠底部的導航來切換,這個適合用do_ViewShower為框架的主體如下圖:

對應這個設計,我們在新建的專案上刪除自動產生的button,加入一個do_ALayout元件和do_ViewShower元件,設定它們的高寬和x,y座標:


3.接下來我們為ViewShower增加4個分頁,在這裡我還強調一下,實際App會多人參與,我們的程式碼結構要清晰易讀,命名盡量使用英文,實在不行使用中文全拼,包括要建立多層子目錄,不要都混在一起。這裡我們增加4個子目錄:chats, contacts, discover, me. 右鍵每個子目錄,選擇New--Other--DeviceOne--UI File,建立4個index.ui,對應的index.ui.js會自動生成。

這裡注意把這4個ui檔案對應的根節點ALayout的高度設定為1220,因為這4個ui檔案都是主介面的ViewShower的子view,不應該超過ViewShower的大小。

另外為每個ui裡都加上一個label,標記中文名,只是一會兒debug看真實效果更清楚。

4.Bottom Bar內簡單加入4個按鈕,然後加入對應的js程式碼實作ViewShower的頁面切換功能。這裡提一個技巧,選取2個或以上組件,可以使用各種對齊方式的功能,如下圖:


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個按鈕,切換效果都是好的:

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. 我們先來分析一下介面效果圖和設計圖

 

整個底部導覽分4個重複的部分,每個部分由一個圖片ImageView和底部標題Label,以及右上角標示,這個標示可以用圓角Label來實現,這個標示缺省的時候應該是隱藏的。

1. 第一步我們得找到對應的圖片資源,通常開發這些資源由美工提供,我們現在模仿微信,最好的方式是從微信原生安裝包裡獲取,不能直接靠截圖,而是打開微信ios,android安裝包,ios的安裝包是ipa和android的安裝包apk都是壓縮文件,解開可以獲取到一些圖片資源。目前我只需要底部8個圖標,包括未點中以及點中的高亮圖標,外面把這些圖標放在image目錄下

2. 先刪除先前增加的4個臨時按鈕,然後按照美工提供的尺寸資料佈局好新的組件,包括4個do_ImageView組件和4個Label組件以及4個右上角的Label
簡單計算可以得到ImageView的大小是60*60。這裡有一個小技巧,設定好一組ImageView和Label後選中2個組件,然後右鍵“Copy”,然後再"Paste"三次,還可以選中多個組件各種對齊。

再細調一下,把圖片和文字設定好,圖片設定就是設定ImageView的source屬性,Label需要設定文字居中,設定textAlign屬性為center,設定字體,設定背景色,前景色等等,設定右上角三個Label的visible為false。中間加上一個ALayout設定背景為灰色,作為ViewShower和Bottom Bar上下的分割線. 這裡要注意,右上角的正圓形Label的實作方式就是設定border屬性,border設定為FF0000FF,1,15 表示界線的顏色是紅色,寬度1,圓角半徑15(Label的寬高都是30),因此實現正圓。

 

在真機上測試一下效果,iPhone和Android手機真機的效果圖如下:

3. 這時候會有2個問題,如果給ImageView上新增點擊事件的話,使用者必須點中這個圖片才能觸發點擊,這樣體驗不好。第二個問題是圖片在Android上稍微有點變形,如果在例如iPhone4上,可能圓形會變成橢圓,這個問題是因為不同手機寬高比的差異。

要解決的方法是:

* 把Bottom Bar所在的ALayout增加4個一樣大小的子ALayout,然後把imageview,label都放在對應的子ALayout上,然後給子ALayout增加點擊事件,這樣用戶的手指只要接觸到差不多位置都能觸發事件
* 把上面的4個子ALayout的isStretch屬性改成false,這個原理可以參考文檔 ALayout的範例demo

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,由三个大部分组成,系统状态栏,工具栏和微信聊天记录列表。

 

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

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


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

要解决的方法是修改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"
    });
});

再来看看真机效果图:

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


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

 

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


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

对应的映射关系的代码在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

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
});

我们在真机上看看效果


在运行中有几个细节:

* 上下滑动的时候,图片不断的刷新,原因是我们的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加载一个三角形图标来实现。

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

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

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


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

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn