前言:
WebKit中的JavaScriptCore引擎處理的JS滑動與原生控件滑動沖突尚未完善,這篇文章討論下如何徹底解決H5端的滑動控件與原生控件滑動沖突
更多文章請關(guān)注:http://www.itdecent.cn/u/b1cff340957c
效果圖
1:點(diǎn)擊WebView中的輪播圖區(qū)域左右滑動與上下滑動

2:WebView非輪播區(qū)域左右翻頁ViewPager

一:WebView內(nèi)容的滑動
首先介紹下WebView內(nèi)容的幾種滑動方式
1:WebView在固定寬高時(shí)與自適應(yīng)屏幕時(shí)的滑動
固定寬高時(shí),WebView加載的html頁面內(nèi)容寬度或高度大于webView的寬度或高度時(shí),滑動的都是改變mScrollX, mScrollY的值,這種滑動能在onScrollChanged(int l, int t, int oldl, int oldt)監(jiān)聽到getScrollX, getScrollY值變化.
WebView自適應(yīng)屏幕寬度,高度自適應(yīng)嵌套在ScrollView中滑動時(shí),WebView高度即為加載頁面的實(shí)際高度,此時(shí)頁面中內(nèi)容的滑動實(shí)際就是ScrollView在控制,滑動改變的是ScrollView中第一個子控件的scrollY值
2:H5控件通過JS控制的滑動
這種滑動在WebView的某個固定區(qū)域內(nèi)通過JS控制滑動,無論是水平滑動還是垂直滑動都不會像android原生滑動控件有requestDisallowInterceptTouchEvent請求事件放行,而且又不影響WebView的scrollX,scrollY值,所以即使是(能自動處理水平與垂直滑動區(qū)別的)ViewPager也不能判斷它是否能夠優(yōu)先滑動
第1個問題只要能改變scrollX或scrollY值外層控件就能識別是否能夠優(yōu)先滑動,這里不再講述如何處理滑動優(yōu)先
上層控件如何處理水平與垂直滑動和多層嵌套-此處鏈接
二:解決JS與原生控件滑動沖突
直接舉例子說明, H5中的輪播控件被嵌套到NestedScrollView中,外層再嵌套SwipeRefreshLayout 下拉刷新,外層再嵌套ViewPager,既然外層控件無法判斷JS是否優(yōu)先滑動,那思路只能由JS絕對優(yōu)先滑動,在產(chǎn)生水平或垂直滑動或無法滑動時(shí)再告訴上層控件是否要放行事件

這種沖突網(wǎng)上也有一大堆的方法,有用js交互處理返回滑動區(qū)域,在該區(qū)域內(nèi)都不攔截事件, 也有修改WebView的onTouchEvent方法,在ACTION_DOWN時(shí)請求.requestDisallowInterceptTouchEvent(true),在ACTION_MOVE時(shí)再根據(jù)x,y軸的差值比較來判斷是否為水平還是垂直滑動,這里分析下不完善之處
a:
如果WebView中加載的H5頁面或WebView外面嵌套的控件即有水平又有垂直滑動時(shí),這方法就行不通了
b:
android判斷水平與垂直滑動的標(biāo)準(zhǔn)也不是這樣子定義的,在google提供的所有的滑動控件中都可以看到 yDiff>mTouchSlop 或 xDiff > mTouchSlop的代碼, 不僅是判斷差值還要判斷差值大于最小滑動單位才算是水平或垂直滑動
直接上代碼嘍,先是JS部分
(這里會用到j(luò)s交互,與js的事件監(jiān)聽,作者對這塊水平爛,如有寫錯,多多指點(diǎn))
// 輪播滾動
var mySwiper = $('.slidepics').swiper({
loop : true,
pagination: '.dotted p',
paginationClickable: true,
spaceBetween: 30,
autoplay:3000,
autoplayDisableOnInteraction:false,
onSliderMove:function(swiper) {
//如果光用此方法來申請app原生控件不攔截事件,在快速滑動的時(shí)候可能搶不到事件
//還要配合下面的touch事件來獲取
isBeingDrag = true;
window.Android.requestEvent(true);
}
});
// 輪播滾動事件監(jiān)聽
$('.slidepics').on('touchstart touchmove touchend touchcancel' , function(event) {
var touch = event.originalEvent.targetTouches[0];
switch (event.type) {
case "touchstart":
mLastClientX = touch.clientX;
mLastClientY = touch.clientY;
isBeingDrag = false;
window.Android.requestEvent(true);
break;
case "touchmove":
if (!isBeingDrag) {
var xDiff = Math.abs(touch.clientX - mLastClientX);
var yDiff = Math.abs(touch.clientY - mLastClientY);
//console.log(xDiff + " " + yDiff);
if (xDiff >= touchSlop) {
isBeingDrag = true;
} else if(yDiff > touchSlop) {
//產(chǎn)生app縱向滑動,父控件不強(qiáng)制請求放行事件,這段邏輯主要是不影響外層垂直滑動控件的滑動
//js端控制滑動與app端的標(biāo)準(zhǔn)不一樣,所以結(jié)合上面的onSliderMove:function方法來判斷
//如果H5端控件已經(jīng)產(chǎn)生滑動時(shí)則必須請求父控件放行事件
//如果有些開源控件沒有類似onSliderMove方法時(shí),只需提供控件是否產(chǎn)生滑動就行,原理都是一樣
isBeingDrag = true;
window.Android.requestEvent(false);
}
}
break;
case "touchend":
window.Android.requestEvent(false);
break;
}
});
//app端的滑動值
var touchSlop = 0;
//初次按下時(shí)的x, y軸值
var mLastClientX, mLastClientY;
//是否處于滑動中
var isBeingDrag = false;
//js附值,在web加載完成時(shí)將android的滑動單位值傳給js
function initTouchSlop(appTouchSlop) {
touchSlop = appTouchSlop;
}
JS端事件代碼邏輯跟app端差不多,android代碼
/**
* js交互類
*/
private class JsCallback {
@JavascriptInterface
public void requestEvent(boolean request) {
Log.i("you", "requestDisallowInterceptTouchEvent " + request+" "+Thread.currentThread().getName());
mWebView.requestDisallowInterceptTouchEvent(request);
}
}
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
int touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
StringBuilder jsSb = new StringBuilder("javascript:initTouchSlop('").append(touchSlop).append("')");
mWebView.loadUrl(jsSb.toString());
}
});
需要注意的是:js交互回調(diào)不是在主線程中執(zhí)行.如果您的界面中沒有涉及即有水平又有垂直滑動的復(fù)雜嵌套,js代碼也可以簡單化,只需要監(jiān)聽touchstart, touchend事件即可
小細(xì)節(jié):requestDisallowInterceptTouchEvent()方法在SwipeRefreshLayout中可能會不生效,此方法在SwipeRefreshLayout中被重寫,在SDK22以下版本的V4包中,此方法什么都不執(zhí)行,google的版本問題甚是頭疼,要解決這問題就是下拉刷新嵌套的控件去實(shí)現(xiàn)NestedScroll,設(shè)置isNestedScrollingEnabled值為true,google也有寫好的NestedScrollView
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
|| (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
// Nope.
// 可以看出SwipeRefreshLayout的第一個child沒有實(shí)現(xiàn)NestedScroll時(shí)不能申請到上層控件不攔截事件,
//這也是為什么SwipeRefreshLayout嵌套很多控件下拉刷新會有沖突的原因,子控件無法傳遞requestDisallowInterceptTouchEvent
} else {
super.requestDisallowInterceptTouchEvent(b);
}
}
在使用嵌套下拉刷新SwipeRefreshLayout時(shí)注意V4包的版本,SDK22版本以下是不支持的,H5端的沖突也就沒法處理
如果您有更好的方法解決沖突,請多多指教
更多文章請關(guān)注:http://www.itdecent.cn/u/b1cff340957c
最后附上源碼https://github.com/youxiaochen/WebViewDemo
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
- 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
- 場景描述 最近在接觸h5與android混合開發(fā)時(shí)遇到一個問題,在一個activity使用ViewPager+Fr...
- 最近做了一個Android UI相關(guān)開源項(xiàng)目庫匯總,里面集合了OpenDigg 上的優(yōu)質(zhì)的Android開源項(xiàng)目庫...