MUI是dcloud(數字天堂)公司旗下的一款跨平台開發移動APP的框架產品,在學習MUI框架之前,最先接觸了Hbuilder代碼編輯器,它帶給我的第一感覺是快,這是HBuilder的最大優勢,通過完整的語法提示和代碼輸入法、代碼塊等,大幅提升HTML 、JS、css的開發效率。
01-初識MUI
MUI 有以下兩大亮點:
一、輕量
追求性能體驗,是我們開始啟動MUI項目的首要目標,輕量必然是重要特征;
MUI不依賴任何第三方JS庫,壓縮后的JS和CSS文件僅有100+K和60+K
二、原生UI
鑒於之前的很多前端框架(特別是響應式布局的框架),UI控件看起來太像網頁,沒有原生感覺,因此追求原生UI感覺也是我們的重要目標
MUI以iOS平台UI為基礎,補充部分Android平台特有的UI控件
在學習MUI之前,推薦一些適合新手的網站:
- Dcloud官網:http://dcloud.io/
- Dcloud問答社區:http://ask.dcloud.net.cn/
- Dcloud文檔匯總地址:http://ask.dcloud.net.cn/docs/
- Hello mui線上演示地址:http://www.dcloud.io/hellomui...
- Hello mui演示APP下載地址:
DOM結構
關於mui頁面的dom,你需要知道如下規則。
固定欄靠前
所謂的固定欄,也就是帶有.mui-bar屬性的節點,都是基於fixed定位的元素;常見組件包括:
頂部導航欄(.mui-bar-nav)、底部工具條(.mui-bar-footer)、底部選項卡(.mui-bar-tab);這些元素使用時需遵循一個規則:放在.mui-content元素之前,即使是底部工具條和底部選項卡,也要放在.mui-content之前,否則固定欄會遮住部分主內容;
一切內容都要包裹在mui-content中
除了固定欄之外,其它內容都要包裹在.mui-content中,否則就有可能被固定欄遮罩,原因:固定欄基於Fixed定位,不受流式布局限制,普通內容依然會從top:0的位置開始布局,這樣就會被固定欄遮罩,mui為了解決這個問題,定義了如下css代碼:
.mui-bar-nav ~ .mui-content { padding-top: 44px; } .mui-bar-footer ~ .mui-content { padding-bottom: 44px; } .mui-bar-tab ~ .mui-content { padding-bottom: 50px; }
你當然可以通過自定義CSS的方式實現如上類似效果,但為了使用簡便,建議將除固定欄之外的所有內容,全部放在.mui-content中。
始終為button按鈕添加type屬性
若button按鈕沒有type屬性,瀏覽器默認按照
type=submit邏輯處理,這樣若將沒有type的button放在form表單中,點擊按鈕就會執行form表單提交,頁面就會刷新,用戶體驗極差。
窗口管理
頁面初始化:必須執行mui.init方法
mui在頁面初始化時,初始化了很多參數配置,比如:按鍵監聽、手勢監聽等,
因此mui頁面都必須調用一次mui.init()方法;
頁面跳轉:拋棄href跳轉
當瀏覽器加載一個新頁面時,若頁面DOM尚未渲染完畢,頁面會先顯示空白,然后等DOM渲染完畢后,再顯示具體內容,這是WEB瀏覽器技術無法逾越的體驗障礙;為解決這個問題,建議使用
mui.openWindow方法打開一個新的webview,mui會自動監聽新頁面的loaded事件,若加載完畢,再自動顯示新頁面;
頁面關閉:勿重復監聽backbutton
mui框架自動封裝了
頁面關閉邏輯,若希望自定義返回邏輯(例如編輯頁面的返回,需用戶確認放棄草稿后再執行返回邏輯),則需要重寫mui.back方法,切勿簡單通過addEventListener添加backbutton監聽,因為addEventListener只會增加新的執行程序,mui默認封裝的監聽執行邏輯依然會繼續執行,因此若僅addEventListener添加用戶確認框,則用戶即使選擇了取消,也會繼續關閉窗口。
手勢操作
點擊:忘記click
快速響應是mobile App實現的重中之重,研究表明,當延遲超過100毫秒,用戶就能感受到界面的卡頓,然而手機瀏覽器的click點擊存在300毫秒延遲(至於為何會延遲,及300毫秒的來龍去脈,請自行谷百),mui為了解決這個問題,封裝了tap事件,因此在任何點擊的時候,請忘記click及onclick操作,統統使用如下代碼:
element.addEventListener('tap',function(){ //點擊響應邏輯 });
常見錯誤
Uncaught ReferenceError: plus is not defined
在app開發中,若要使用HTML5+擴展api,必須等plusready事件發生后才能正常使用,否則可能會報“plus is not defined”的錯誤;
mui為簡化開發,
將plusReady事件封裝成了mui.plusReady()方法,凡涉及到HTML5+的api,建議都寫在mui.plusReady方法中;
02-webview基本知識
什么是webview?
在學習MUI的webview相關知識之前,我們先來了解下什么是窗口?什么是webview?
舉個相似的例子,我們在使用瀏覽器的時候,瀏覽器的頂部和底部的選項卡欄是固定不變的,當我們打開不同的網頁時,網頁內容會隨之改變。這時,瀏覽器就相當於一個窗口,不同的網頁就相當於不同的webview。同理,在應用MUI制作手機APP頁面時,每一個HTML頁面就是一個窗口,我們可以在一個頁面中創建多個webview。
創建新的Webview窗口
WebviewObject plus.webview.create( url, id, styles, extras );
說明:
創建Webview窗口,用於加載新的HTML頁面,可通過styles設置Webview窗口的樣式,創建完成后需要調用show方法才能將Webview窗口顯示出來。
顯示Webview窗口
void plus.webview.show( id_wvobj, aniShow, duration, showedCB, extras );
說明:
顯示已創建或隱藏的Webview窗口,需先獲取窗口對象或窗口id,並可指定顯示窗口的動畫及動畫持續時間。
隱藏Webview窗口
void plus.webview.hide( id_wvobj, aniHide, duration, extras );
說明:
根據指定的WebviewObject對象或id隱藏Webview窗口,使得窗口不可見。
獲取當前窗口的WebviewObject對象
WebviewObject plus.webview.currentWebview();
說明:
獲取當前頁面所屬的Webview窗口對象。
查找指定標識的WebviewObject窗口
WebviewObject plus.webview.getWebviewById( id );
說明:
在已創建的窗口列表中查找指定標識的Webview窗口並返回。 若沒有查找到指定標識的窗口則返回null,若存在多個相同標識的Webview窗口,則返回第一個創建的Webview窗口。 如果要獲取應用入口頁面所屬的Webview窗口,其標識為應用的%APPID%,可通過plus.runtime.appid獲取。
創建並打開Webview窗口
WebviewObject plus.webview.open( url, id, styles, aniShow, duration, showedCB );
說明:
創建並顯示Webview窗口,用於加載新的HTML頁面,可通過styles設置Webview窗口的樣式,創建完成后自動將Webview窗口顯示出來。
mui雙webview模式
首先我們要了解mui為了解決窗體切換白屏和區域滾動提出的雙webview模式。
頁面初始化
mui框架將很多功能配置都集中在 mui.init方法中,要使用某項功能,只需要在mui.init方法中完成對應參數配置即可,目前支持在mui.init方法中配置的功能包括:創建子頁面、關閉頁面、手勢事件配置、預加載、下拉刷新、上拉加載、設置系統狀態欄背景顏色。mui需要在頁面加載時初始化很多基礎控件,如監聽返回鍵,因此務必在每個頁面中調用.
以下是可以配置的參數:
mui.init({ //子頁面 subpages: [{ //... }], //預加載 preloadPages:[ //... ], //下拉刷新、上拉加載 pullRefresh : { //... }, //手勢配置 gestureConfig:{ //... }, //側滑關閉 swipeBack:true, //Boolean(默認false)啟用右滑關閉功能 //監聽Android手機的back、menu按鍵 keyEventBind: { backbutton: false, //Boolean(默認truee)關閉back按鍵監聽 menubutton: false //Boolean(默認true)關閉menu按鍵監聽 }, //處理窗口關閉前的業務 beforeback: function() { //... //窗口關閉前處理其他業務詳情點擊 ↑ "關閉頁面"鏈接查看 }, //設置狀態欄顏色 statusBarBackground: '#9defbcg', //設置狀態欄顏色,僅iOS可用 preloadLimit:5//預加載窗口數量限制(一旦超出,先進先出)默認不限制 })
如下為打印當前頁面URL的示例:
mui.plusReady(function(){ console.log("當前頁面URL:"+plus.webview.currentWebview().getURL()); });
創建子頁面
在mobile app開發過程中,經常遇到卡頭卡尾的頁面,此時若使用局部滾動,在android手機上會出現滾動不流暢的問題; mui的解決思路是:將需要滾動的區域通過單獨的webview實現,完全使用原生滾動。具體做法則是:將目標頁面分解為主頁面和內容頁面,主頁面顯示卡頭卡尾區域,比如頂部導航、底部選項卡等;內容頁面顯示具體需要滾動的內容,然后在主頁面中調用mui.init方法初始化內容頁面。
參數說明:
styles:表示窗口屬性,參考5+規范中的WebviewStyle;特別注意,height和width兩個屬性,即使不設置,也默認按100%計算;因此若設置了top值為非"0px"的情況,建議同時設置bottom值,否則5+ runtime根據高度100%計算,可能會造成頁面真實底部位置超出屏幕范圍的情況;left、right同理。
示例:Hello mui的首頁其實就是index.html加list.html合並而成的,如下:
index.html的作用就是顯示固定導航,list.html顯示具體列表內容,列表項的滾動是在list.html所在webview中使用原生滾動,既保證了滾動條不會穿透頂部導航,符合app的體驗,也保證了列表流暢滾動,解決了區域滾動卡頓的問題。 list.html就是index.html的子頁面,創建代碼比較簡單,如下:
mui.init({ subpages:[{ url:'list.html', id:'list.html', styles:{ top:'45px',//mui標題欄默認高度為45px; bottom:'0px'//默認為0px,可不定義; } }] });
打開新頁面
做
web app,一個無法避開的問題就是轉場動畫;web是基於鏈接構建的,從一個頁面點擊鏈接跳轉到另一個頁面,如果通過有刷新的打開方式,用戶要面對一個空白的頁面等待;如果通過無刷新的方式,用Javascript移入DOM節點(常見的SPA解決方案),會碰到很高的性能挑戰:DOM節點繁多,頁面太大,轉場動畫不流暢甚至導致瀏覽器崩潰; mui的解決思路是:單webview只承載單個頁面的dom,減少dom層級及頁面大小;頁面切換使用原生動畫,將最耗性能的部分交給原生實現。
mui.openWindow({ url:new-page-url, id:new-page-id, styles:{ top:newpage-top-position,//新頁面頂部位置 bottom:newage-bottom-position,//新頁面底部位置 width:newpage-width,//新頁面寬度,默認為100% height:newpage-height,//新頁面高度,默認為100% ...... }, extras:{ .....//自定義擴展參數,可以用來處理頁面間傳值 }, createNew:false,//是否重復創建同樣id的webview,默認為false:不重復創建,直接顯示 show:{ autoShow:true,//頁面loaded事件發生后自動顯示,默認為true aniShow:animationType,//頁面顯示動畫,默認為”slide-in-right“; duration:animationTime//頁面動畫持續時間,Android平台默認100毫秒,iOS平台默認200毫秒; }, waiting:{ autoShow:true,//自動顯示等待框,默認為true title:'正在加載...',//等待對話框上顯示的提示內容 options:{ width:waiting-dialog-widht,//等待框背景區域寬度,默認根據內容自動計算合適寬度 height:waiting-dialog-height,//等待框背景區域高度,默認根據內容自動計算合適高度 ...... } } })
參數說明:
- styles:表示窗口參數,參考5+規范中的WebviewStyle;特別注意,height和width兩個屬性,即使不設置,也默認按100%計算;因此若設置了top值為非"0px"的情況,建議同時設置bottom值,否則5+ runtime根據高度100%計算,可能會造成頁面真實底部位置超出屏幕范圍的情況,left、right同理。
- extras:新窗口的額外擴展參數,可用來處理頁面間傳值;例如:
var webview = mui.openWindow({ url:'info.html', extras:{ name:'mui' } }); console.log(webview.name);
控制台會輸出"mui"字符串;
注意:擴展參數僅在打開新窗口時有效,若目標窗口為預加載頁面,則通過mui.openWindow方法打開時傳遞的extras參數無效。
- createNew:是否重復創建相同id的webview;為優化性能、避免app中重復創建webview,mui v1.7.0開始增加createNew參數,默認為false;判斷邏輯如下:若createNew為true,則不判斷重復,每次都新建webview;若為fasle,則先計算當前App中是否已存在同樣id的webview,若存在則直接顯示;否則新創建並根據show參數執行顯示邏輯;該參數可能導致的影響:若業務寫在plusReady事件中,而plusReady事件僅首次創建時會觸發,則下次再次通過mui.openWindow方法打開同樣webview時,是不會再次觸發plusReady事件的,此時可通過自定義事件觸發;
- show:表示窗口顯示控制。autoShow:目標窗口loaded事件發生后,是否自動顯示;若目標頁面為預加載頁面,則該參數無效;aniShow表示頁面顯示動畫,比如從右側划入、從下側划入等
- waiting:表示系統等待框;mui框架在打開新頁面時等待框的處理邏輯為:顯示等待框-->創建目標頁面webview-->目標頁面loaded事件發生-->關閉等待框;因此,只有當新頁面為新創建頁面(webview)時,會顯示等待框,否則若為預加載好的頁面,則直接顯示目標頁面,不會顯示等待框。waiting中的參數:autoShow表示自動顯示等待框,默認為true,若為false,則不顯示等待框;注意:若顯示了等待框,但目標頁面不自動顯示,則需在目標頁面中通過如下代碼關閉等待框plus.nativeUI.closeWaiting();。title表示等待框上的提示文字,options表示等待框顯示參數,比如寬高、背景色、提示文字顏色等
示例1:Hello mui中,點擊首頁右上角的圖標,會打開關於頁面,實現代碼如下:
//tap為mui封裝的單擊事件,可參考手勢事件章節 document.getElementById('info').addEventListener('tap', function() { //打開關於頁面 mui.openWindow({ url: 'examples/info.html', id:'info' }); });
因沒有傳入styles參數,故默認全屏顯示;也沒有傳入show參數,故使用slide-in-right動畫,新頁面從右側滑入。
示例2:從A頁面打開B頁面,B頁面為一個需要從服務端加載的列表頁面,若在B頁面loaded事件發生時就將其顯示出來,因服務器數據尚未加載完畢,列表頁面為空,用戶體驗不好;可通過如下方式改善用戶體驗(最好的用戶體驗應該是通過預加載的方式)
第一步,B頁面loaded事件發生后,不自動顯示
//A頁面中打開B頁面,設置show的autoShow為false,則B頁面在其loaded事件發生后,不會自動顯示; mui.openWindow({ url: 'B.html', show:{ autoShow:false } });
第二步,在B頁面獲取列表數據后,再關閉等待框、顯示B頁面
//B頁面onload從服務器獲取列表數據; window.onload = function(){ //從服務器獲取數據 .... //業務數據獲取完畢,並已插入當前頁面DOM; //注意:若為ajax請求,則需將如下代碼放在處理完ajax響應數據之后; mui.plusReady(function(){ //關閉等待框 plus.nativeUI.closeWaiting(); //顯示當前頁面 mui.currentWebview.show(); });
關閉頁面
mui框架將窗口關閉功能封裝在mui.back方法中,具體執行邏輯是:
若當前webview為預加載頁面,則hide當前webview;否則,close當前webview。
在mui框架中,有三種操作會觸發頁面關閉(執行mui.back方法)。
- 點擊包含.mui-action-back類的控件
- 在頁面上,向右快速滑動
- Android手機按下back按鍵
hbuilder中敲mheader生成的代碼塊,會自動生成帶有返回導航箭頭的標題欄,點擊返回箭頭可關閉當前頁面,原因就是因為該返回箭頭包含.mui-action-back類,代碼如下:
<header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">標題</h1> </header>
若希望在頂部導航欄之外的其它區域添加關閉頁面的控件,只需要在對應控件上添加.mui-action-back類即可,如下為一個關閉按鈕示例:
<button type="button" class='mui-btn mui-btn-danger mui-action-back'>關閉</button>
mui框架封裝的頁面右滑關閉功能,默認未啟用,若要使用右滑關閉功能,需要在
mui.init();方法中設置swipeBack參數,如下:
mui.init({ swipeBack:true //啟用右滑關閉功能 });
mui框架默認會監聽Android手機的back按鍵,然后執行頁面關閉邏輯; 若不希望mui自動處理back按鍵,可通過如下方式關閉mui的back按鍵監聽;
mui.init({ keyEventBind: { backbutton: false //關閉back按鍵監聽 } });
除了如上三種操作外,也可以直接調用
mui.back()方法,執行窗口關閉邏輯;mui.back()僅處理窗口邏輯,若希望在窗口關閉之前再處理一些其它業務邏輯,則可將業務邏輯抽象成一個具體函數,然后注冊為mui.init方法的beforeback參數;beforeback的執行邏輯為:
執行
beforeback參數對應的函數若返回false,則不再執行mui.back()方法;否則(返回true或無返回值),繼續執行mui.back()方法;
示例:從列表打開詳情頁面,從詳情頁面再返回后希望刷新列表界面,此時可注冊beforeback參數,然后通過自定義事件通知列表頁面刷新數據,示例代碼如下:
mui.init({ beforeback: function(){ //獲得列表界面的webview var list = plus.webview.getWebviewById('list'); //觸發列表界面的自定義事件(refresh),從而進行數據刷新 mui.fire(list,'refresh'); //返回true,繼續頁面關閉邏輯 return true; } });
注意:
beforeback的執行返回必須是同步的(阻塞模式),若使用nativeUI這種異步js(非阻塞模式),則可能會出現意想不到的結果;比如:通過plus.nativeUI.confirm()彈出確認框,可能用戶尚未選擇,頁面已經返回了(beforeback同步執行完畢,無返回值,繼續執行mui.back()方法,nativeUI不會阻塞js進程):在這種情況下,若要自定義業務邏輯,就需要復寫mui.back方法了;如下為一個自定義示例,每次都需要用戶確認后,才會關閉當前頁面。
//備份mui.back,mui.back已將窗口關閉邏輯封裝的比較完善(預加載及父子窗口),因此最好復用mui.back var old_back = mui.back; mui.back = function(){ var btn = ["確定","取消"]; mui.confirm('確認關閉當前窗口?','Hello MUI',btn,function(e){ if(e.index==0){ //執行mui封裝好的窗口關閉邏輯; old_back(); } }); }
注意:自定義關閉邏輯時,一定要重寫
mui.back,不能簡單通過addEventListener增加back按鍵監聽, 因為addEventListener只會增加新的執行邏輯,老的監聽邏輯依然會執行。
