apicloud中提供下拉刷新監聽事件api,也提供滾動到底部事件的監聽,能夠實現下拉刷新和滾動到底部加載更多功能,但是我們真的就滿足實現功能了嗎?將兩個代碼拼湊起來運行看看發現了什么?是的,在滾動到底部加載更多的時候底部會彈動,有人可能會說觸發加載更多的時候直接放一個遮罩view,也就是progress,用來禁止用戶繼續對當前view產生觸摸事件就行,但是如果你很快滑動到底部呢,彈動現象仍然不能禁止。我曾向技術多次提過在下拉刷新api中提供一個參數用來控制是否禁用底部彈動的,但是前幾次技術都是不了了之,最近又問了一次,直接回應“目前市面上有APP是這種效果嗎?底部彈動,主要是擬物,跟手,你不喜歡這種效果,不代表每個人都不喜歡。”,我只想說,我又沒叫你去掉彈動,只是加一個屬性控制是否彈動,這和別人是否喜歡有什么關系,而且盡然還說現在市場的app有這種效果嗎?可能我已經脫離了市場軌道。好了,發發牢騷而已,這里不談技術人員的態度問題,下面分享下我如何實現優雅控制的:
對於這個問題我想過很多種辦法,原本想是否改寫官方底層來實現,但是嘗試了一下放棄了(好吧,我承認是我太水,沒改的了),因為就算我把Android改了,那IOS呢,apicloud引擎又沒有開源,IOS同樣無法實現,那又有什么意義,我想應該有很多人都糾結這個問題,那么到底怎么解決呢?
首先apicloud提供了設置頁面bounces的方法,能否從這方面下手,動態改變其bounces狀態,在具有下拉意圖的時候開啟bounces,在具有上拉加載更多意圖的時候禁用bounces。但是如何判斷意圖呢,最初的設想是監聽scrolltobottom方法來控制,放滾動到距離底部還有50dp的時候來禁用bounces,在距離頂部50dp(通過計算成距離底部距離)的時候開啟bounces,但是發現scrolltobottom有些問題:1、只能監聽一個,不能任性的監聽,后面的會覆蓋前面的。2、滾動到距離底部50dp的時候觸發事件,但是滾動到距底部0dp的時候再向下滾任然會觸發該事件,這應該是一個bug。原生api使用不了,那js是否提供方法呢,都知道我們的開始使用的是webview,雖然Java可以直接調用webview的原生方法,但是js也可以操作webkit的事件,其中就有onscroll事件,這個是網頁滾動的事件。能否通過這個方法來改變bounces呢,試驗了一下,還真可以,總體方法就是在使用原生下拉刷新與scrolltobottom監聽滾動到底部的同時,使用js監聽頁面滾動事件,在滾動條距離頂部30像素位置上下分別改變bounces狀態。
具體代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="target-densitydpi=device-dpi,maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0"/> <meta name="format-detection" content="telephone=no,email=no,date=no,address=no"> <title>內容頁</title> <link rel="stylesheet" type="text/css" href="css/ui-base.css" /> <link rel="stylesheet" type="text/css" href="css/ui-box.css" /> <link rel="stylesheet" type="text/css" href="css/ui-color.css" /> <link rel="stylesheet" type="text/css" href="css/ui.control.css" /> <link rel="stylesheet" type="text/css" href="css/load_more.css" /> <style> html,body{ width:100%; height:100%; } </style> </head> <body class="um-vp"> <div id="page_0" class="up ub ub-ver"> <div id="content" class="ub ub-ver" style="min-height: 300px;"> </div> <div id="loading" class="ub ub-ver" style="background:rgba(0,0,0,0.2);margin-top:5px"></div> </div> </body> <script type="text/javascript" src="./script/zepto.min.js"></script> <script src="script/api.control.js"></script> <script src="script/templete/template.js"></script> <script src="script/ui.widget/ui.listview.js"></script> <script src="script/zepto.widget/zepto.nodata.js"></script> <script> ;(function($){ $(function () { var con={ listview: $.listview({ selector: "#content", type: "thickLine", hasAngle: false, hasIcon: false, hasSmallIcon:false, multiLine: 1, }), pageindex:0, items:20, hasmore:false, times:0, isloading:false, getData:function(){ var self=this; $("#content").empty(); $("#loading").empty(); var data=[]; switch (self.times){ case 0://無數據 break; case 1://5條數據,不足一個屏幕 for(var i=0;i<5;i++){ data.push({ title:"測試數據1" }); } break; case 2://15條數據,超過一個屏幕,但是不足20條,沒有加載更多 for(var i=0;i<15;i++){ data.push({ title:"測試數據2" }); } break; case 3://20條,表示可能有更多數據 for(var i=0;i<20;i++){ data.push({ title:"測試數據3" }); } break; } if(data.length>0){ self.listview.add(data,1); } self.logic(data); if(self.times>=3){ self.times=0; }else{ self.times++; } }, logic: function(data) { var self = this; var boxdom=$("#content"); if (boxdom.children().length == 0&&data.length==0) { //從無數據開始 boxdom.nodata(); }else{ if (data.length == 0) { self["hasmore"]= false; if($("#content").find(".ui-nodata")){ //如果已經顯示無數據則不進行append操作 }else{ $("#loading").empty().append('<div class="loading_more pause"><div id="loadingglobe" class="sk-spinner sk-spinner-wordpress uhide"><span class="sk-inner-circle"></span></div></div>'); } return; } else { if (boxdom.offset().height > document.body.clientHeight) { if(data.length<self.items){ self["hasmore"]= false; $("#loading").empty().append('<div class="loading_more pause"><div id="loadingglobe" class="sk-spinner sk-spinner-wordpress uhide"><span class="sk-inner-circle"></span></div></div>'); }else{ self["hasmore"]= true; $("#loading").empty().append('<div class="loading_more"><div id="loadingglobe" class="sk-spinner sk-spinner-wordpress uhide"><span class="sk-inner-circle"></span></div></div>'); } } } } }, loadData: function () { var self=this; var data=[{ title:"測試數據4", },{ title:"測試數據4", }]; self.listview.add(data,1); self.logic(data); self.isloading=false; }, init: function () { var bounce=true,self=this; $.apiready(function () { api.setRefreshHeaderInfo({ visible: true, bgColor: '#ccc', textColor: '#fff', textDown: '下拉刷新...', textUp: '松開刷新...', showTime: true }, function(ret, err){ setTimeout(function () { self.getData(); api.refreshHeaderLoadDone(); },2000) }); api.addEventListener({ name:'scrolltobottom', extra:{ threshold:0 } },function(ret,err){ if(self.hasmore&&!self.isloading){ $("#loadingglobe").removeClass("uhide"); $("#loading .loading_more").addClass("active"); self.isloading=true; setTimeout(function () { self.loadData(); },3000) } }); $.showProgress(); setTimeout(function () { self.getData(); $.closeProgress(); },3000) }); window.addEventListener("scroll",function(){ var t = document.documentElement.scrollTop || document.body.scrollTop; if(t<=30){ if(!bounce){ api.setFrameAttr({ name: 'con', bounces: true, }); bounce=true; } }else{ if(bounce){ api.setFrameAttr({ name: 'con', bounces: false, }); bounce=false; } } }); } }; con.init(); }) })(Zepto) </script> </html>
具體體驗效果可以下載使用:
在Appcan論壇中也有人曾經問過如何實現的,希望能夠參考一下實現,或許有人說使用js監聽滾動事件是不是會影響webiew效率,在這里加入了onscroll稀釋條件,對於Android的效率影響不是很大,可以放心使用。
有人說在Android中可以這樣用,但是IOS中因為IOS引擎在滾動的時候阻塞其內部所有js的執行,只有當滾動停止后才會執行onscroll內的js,這樣不就行不通了嗎。。那只有Android能實現又有什么用?是的,IOS這樣是實現不了的,但是我們都知道IOS的觸摸事件響應效率是非常高的,我們是否可以用觸摸來代替scroll事件,使用touch事件來模擬scroll事件呢,也就是識別用戶滑動的手勢來動態判斷是上還是下,如果是向上,那么就禁止回彈,如果是向下就開啟回彈,這種想法很好,可能有很多人會想到,但是這種會有一個問題,例如用戶先向上再突然向下,或者先向下再突然向上這種那就不能在touchend中判斷手勢了,需要在touchmove中動態判斷手勢,這種方式也不錯,也很容易實現,代碼如下:
var sY=0; var flag=0; var cY=0; var sTop=0; var tY=0; var down=false; //控制在100處執行檢查,實現稀釋 $(window).on("touchstart", function (e) { //記錄y坐標 sY=e.touches[0].clientY; cY=sY; sTop=document.getElementById('content').getBoundingClientRect().top; tY=sY; }) $(window).on("touchmove", function (e) { var Y=e.touches[0].clientY; if(Y-tY>=-sTop){//處於下拉中 down=true; }else{ down=false; } //記錄y坐標 if(Y-cY>0){//flag: 1 if(flag==-1){ sY=Y; flag=1; return; }else if(flag==0){ flag=1; } }else{ if(flag==1){ sY=Y; flag=-1; return; }else if(flag==0){ flag=-1; } } if(Y-sY>=10&&flag==1){//上滑 if(!bounce){ api.setFrameAttr({ name: 'con', bounces: true, }); bounce=true; } } if(sY-Y>=10&&flag==-1){//下滑 if(bounce&&!down){ api.setFrameAttr({ name: 'con', bounces: false, }); bounce=false; } } cY=Y; }) $(window).on("touchend", function (e) { down=false; sY=0; flag=0; cY=0; sTop=0; tY=0; }) $(window).on("touchcancel", function (e) { down=false; sY=0; flag=0; cY=0; sTop=0; tY=0; })
但是測試會發現一個問題,在底部的時候向下滑動一段距離再向上快速滑動,到底部無法實現鎖定,仍然會彈動,這是因為事件響應次序造成的問題,這個問題通過這種方法就不可避免,后來又想是否同Android一樣,將手勢也固定在一定范圍內,例如在content距離webview邊框-100~0范圍內監聽手勢來改變,但是最后發現貌似不行,因為當處於最低端快速向上滑動的時候會存在慣性滾動,如果在這段范圍內完全由慣性滾動完成,不存在touch事件,那么就無法判斷。后來想想完全是自己想的太復雜了,直接監聽觸摸事件,在觸摸事件中監聽不就行了,上代碼:
//for IOS
;(function () {
;['touchstart', 'touchmove', 'touchend', 'touchcancel',"scroll"].forEach(function(eventName){
$(window).on(eventName, function (e) {
//記錄y坐標
var sTop=document.getElementById('content').getBoundingClientRect().top;
if(sTop>=-100){
if(!bounce){
api.setFrameAttr({
name: 'con',
bounces: true,
});
bounce=true;
}
}else{
if(bounce){
api.setFrameAttr({
name: 'con',
bounces: false,
});
bounce=false;
}
}
})
})
})()
這樣基本就能解決一下問題了,但是如果暴力測試的話還是有可能有問題的,因為這里的100是要求內容部分總高度大於frame高度100px才行,可以適當調整此大小
IOS版