場(chǎng)景描述
最近在接觸h5與android混合開(kāi)發(fā)時(shí)遇到一個(gè)問(wèn)題,在一個(gè)activity使用ViewPager+Fragment結(jié)構(gòu),某個(gè)Fragment包含了一個(gè)webview。而在這個(gè)webview展示的h5里有一個(gè)橫屏輪播的元素,此時(shí)當(dāng)我們橫向滑動(dòng)的時(shí)候,大多數(shù)情況下是ViewPager在滑動(dòng)(這里說(shuō)是大多數(shù)情況是不考慮網(wǎng)頁(yè)可以橫向滑動(dòng)的特殊情況)。因此我們需要判斷并處理事件。
如下圖所示,藍(lán)色線條中間的部分是一個(gè)輪播。而整個(gè)首頁(yè)的結(jié)構(gòu)是一個(gè)Viewpager。
問(wèn)題分析
其實(shí)h5的輪播基本都會(huì)支持手滑動(dòng)事件(指JS控制輪播的滑動(dòng)),我們要做的就是判斷什么時(shí)候,touch事件由h5來(lái)處理,什么時(shí)候由ViewPager來(lái)處理,這里就不再講述android的事件機(jī)制,有興趣的同學(xué)可以去老衲之前發(fā)布的文章中去找。這里只用到了其中一個(gè)知識(shí)點(diǎn),即child如何影響parent的事件處理。所涉及到的方法就是
//當(dāng)result為true的時(shí)候,child會(huì)阻止parent獲取touch事件,反之則不會(huì)影響。
requestDisallowInterceptTouchEvent(result);
而這個(gè)方法參數(shù)result的值是true還是false,就是今天要踩得坑之一。
主要思路
首先我們需要確定的是,需要重寫誰(shuí)的onTouch方法,webview還是viewpager,當(dāng)然是webview,通過(guò)webview來(lái)主動(dòng)控制viewpager的事件獲取權(quán)限。
接下來(lái),為了判斷,我們還需要輪播控件的坐標(biāo)和范圍,這個(gè)可以有h5和JS來(lái)實(shí)現(xiàn)
范圍有了。接下來(lái)就是要真正進(jìn)行touch的坐標(biāo)判斷了。
踩坑1之Java與JS的之間相互調(diào)用的順序
關(guān)于android內(nèi)的java方法與html內(nèi)的js方法相互調(diào)用請(qǐng)大家自行百度。這里要提醒大家的是,當(dāng)JS與java在進(jìn)行交互的時(shí)候,他們并不是同步執(zhí)行的。舉個(gè)栗子
public void doCheck() {
String call = "javascript:getViewPagerInfo()";
webview.loadUrl(call);
}
當(dāng)通過(guò)上述方法調(diào)用JS的getViewPagerInfo方法時(shí),而JS的getViewPagerInfo方法內(nèi)部又調(diào)用了java的下列方法時(shí)。
@JavascriptInterface
public void getH5ViewPagerInfo(int x ,int y , int width , int height){
mPagerDesc = new PagerDesc(y,x,x+width,y+height , 0);
}
假如我們需要按順序執(zhí)行如下兩個(gè)方法
doCheck();
showToast();
當(dāng)執(zhí)行完doCheck的loadurl方法之后,他會(huì)去執(zhí)行showToast,不會(huì)等JS回調(diào)java的getH5ViewPagerInfo方法執(zhí)行完再執(zhí)行。
踩坑2之JS滑動(dòng)
剛開(kāi)始接到這個(gè)需求的時(shí)候,會(huì)想的太多,導(dǎo)致剛開(kāi)始考慮h5的時(shí)候順帶把網(wǎng)頁(yè)的滑動(dòng)也計(jì)算進(jìn)去了。這個(gè)是沒(méi)有必要的。見(jiàn)踩坑3內(nèi)的代碼,可以兼容滑動(dòng)的情況。
踩坑3之h5獲取輪播控件的坐標(biāo)與寬高
沒(méi)啥技術(shù)亮點(diǎn),直接看代碼
//獲取輪播控件的寬高以及相對(duì)于原點(diǎn)的位置
function getViewPagerInfo() {
var width = img.clientWidth;
var height = img.clientHeight;
var elem = getElementRect(img);
//調(diào)用android代碼
window.controller.getH5ViewPagerInfo(elem.x,elem.y,width,height);
}
//獲取元素的坐標(biāo)
function getElementRect(e){
var box = e.getBoundingClientRect();
var x = box.left;
var y = box.top;
console.log("x::" + x);
console.log("y::" + y);
return {x:x , y: y};
}
踩坑4之輪播寬高坐標(biāo)的獲取時(shí)機(jī)
因?yàn)閔5頁(yè)面的高度是不確定的。很有可能是可以上下滑動(dòng)的。所以我們輪播的區(qū)域也是會(huì)變化的,而且!?。≥啿サ膮^(qū)域可能不止一個(gè),這個(gè)需要注意。輪播區(qū)域的獲取時(shí)機(jī)有兩個(gè),一個(gè)是剛加載h5頁(yè)面的時(shí)候,另外一個(gè)就是滑動(dòng)的時(shí)候,在js代碼里寫
window.onload = function(){
...
}
window.onscroll = function(){
...
}
踩坑6之h5與android坐標(biāo)系的轉(zhuǎn)換
該需求最大的坑就在于h5與android坐標(biāo)系的換算,h5的坐標(biāo)系與android的坐標(biāo)系的不同在于
- h5的坐標(biāo)系以webview左上角的點(diǎn)為準(zhǔn),而android得坐標(biāo)系以屏幕左上角的點(diǎn)為準(zhǔn)。因此,這里要將通知欄的高度計(jì)算進(jìn)去。
- h5的坐標(biāo)系采用的是css的像素,而android是采用的設(shè)備的像素值。這兩個(gè)像素需要進(jìn)行換算,換算的規(guī)則也很簡(jiǎn)單,與設(shè)備的像素密度相關(guān)。
假設(shè)我們現(xiàn)在拿到了輪播的坐標(biāo),以及寬高,并且通過(guò)js回傳給了java,,此時(shí),我們就可以進(jìn)行最重要的touch事件的判斷了。
首先我們定義一個(gè)內(nèi)部類用來(lái)封裝輪播的寬高和坐標(biāo)
class PagerDesc {
private int top;
private int left;
private int right;
private int bottom;
public PagerDesc(int top, int left , int right ,int bottom ) {
this.top = top;
this.bottom = bottom;
}
}
考慮到目前多數(shù)的輪播都是橫向充滿全屏,因此這里我們只考慮touch事件在y軸上的坐標(biāo)。
接下來(lái),需要通過(guò)js將上述類創(chuàng)建所需要的數(shù)據(jù)回傳給java用來(lái)創(chuàng)建對(duì)象。
private PagerDesc mPagerDesc;
@JavascriptInterface
public void getH5ViewPagerInfo(int x ,int y , int width , int height){
mPagerDesc = new PagerDesc(y,x,x+width,y+height);
}
最關(guān)鍵的一步,我們要在webview的onTouchListener進(jìn)行如下處理。
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
//獲取y軸坐標(biāo)
float y = motionEvent.getRawY();
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
getHTMLPosition();
if (null != mPagerDesc) {
int top = mPagerDesc.top;
int bottom = top + (mPagerDesc.bottom - mPagerDesc.top);
//將css像素轉(zhuǎn)換為android設(shè)備像素并考慮通知欄高度
top = (int) (top * metric.density) + height
bottom = (int) (bottom * metric.density) + height
//如果觸摸點(diǎn)的坐標(biāo)在輪播區(qū)域內(nèi),則由webview來(lái)處理事件,否則由viewpager來(lái)處理
if (y > top && y < bottom) {
webview.requestDisallowInterceptTouchEvent(true);
} else {
webview.requestDisallowInterceptTouchEvent(false);
}
}
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_MOVE:
break;
}
至此,輪播和ViewPager的滑動(dòng)沖突及解決方案已經(jīng)介紹完了。這個(gè)問(wèn)題考察的點(diǎn)還是挺多的,需要開(kāi)發(fā)者有一定的JS基礎(chǔ),需要懂得JS與java的相互調(diào)用,以及深入理解touch事件的傳遞及攔截機(jī)制。當(dāng)然解決的過(guò)程就是踩坑與提高的過(guò)程,希望本文能給遇到該問(wèn)題的小伙伴一個(gè)思路。