項目為Android應(yīng)用,使用WebView加載H5頁面。
此文僅記錄項目開發(fā)中遇到的問題及解決方法。
目錄
一,下拉刷新
二,H5喚起支付寶
三,H5本地文件選擇
四,加載圖片失敗
五,輸入框被軟鍵盤遮擋
一,下拉刷新
頁面Reload需要下拉刷新功能,所以使用了SwipeRefreshLayout包裹WebView。但使用時不管頁面處在哪個位置只要下拉,都會觸發(fā)刷新。
于是通過對WebView的位置進行判斷,來決定是否允許SwipeRefreshLayout刷新功能生效。
現(xiàn)在H5頁面大多都不再是頁面本身滾動,反映到日志就是WebView的getScrollY() 得到的值一直是0,無法用于判斷,于是采用迂回的方式。
首先自定義WebView的子類控件OverScrollWebView,重寫過度滾動監(jiān)聽overScrollBy,當(dāng)其被觸發(fā)的時候,允許下拉刷新。注意:向上過度滑動也會觸發(fā)這個回調(diào),但SwipeRefreshLayout的刷新僅會被下拉觸發(fā),所以這里沒有考慮方向問題。
private boolean isOverScroll = false;
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
if (!isOverScroll) {
isOverScroll = true;
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
public boolean isOverScroll() {
return isOverScroll;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
//在用戶開始或結(jié)束觸屏?xí)r復(fù)位
isOverScroll = false;
break;
}
return super.onTouchEvent(event);
}
?同時,在頁面中設(shè)置允許WebView過度滾動,并重寫觸屏事件監(jiān)聽,根據(jù)頁面是否已處在頂部來設(shè)置是否允許SwipeRefreshLayout下拉刷新。
//允許webView過度滾動
webView.setOverScrollMode(View.OVER_SCROLL_ALWAYS);
if (webView instanceof OverScrollWebView) {
webView.setOnTouchListener((v, event) -> {
swipeRefreshLayout.setEnabled(((OverScrollWebView) webView).isOverScroll());
return false;
});
}
二,H5喚起支付寶
項目支付功能由H5頁面調(diào)用支付寶接口,但無法正確喚起支付寶App,僅生成了吱口令。
因為安卓原生的谷歌瀏覽器自從 chrome25 版本開始,URL Scheme 就無法啟動Android應(yīng)用了。
所以,需要重寫攔截邏輯,手動喚起App。
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading (WebView view, String url){
JCLog.i(TAG, " shouldOverrideUrlLoading url: " + url);
if (url.startsWith("http:") || url.startsWith("https:")) {
//正常的頁面,不攔截不處理
return false;
}
try {
//將H5喚起App,變?yōu)锳pp間互相喚起
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
});
這里是籠統(tǒng)的處理,也可以根據(jù)頁面需求進行精準處理。
參考:android中WebView調(diào)用H5頁面的支付寶、微信支付失敗錯誤返回ERR_UNKNOWN_URL_SCHEME_webview打開支付寶付款沒反應(yīng)_呂氏春秋i的博客-CSDN博客
三,H5本地文件選擇
?這個就比較簡單了,就是重寫WebChromeClient的onShowFileChooser方法,將H5的文件選擇請求轉(zhuǎn)換成安卓原聲的文件選取。
需要注意幾點:
1,項目需要選擇多類型文件,所以我在注冊回調(diào)時用的是OpenMultipleDocuments,如果是單文件、僅圖片等要根據(jù)具體情況選擇。
2,mFilePathCallback的onReceiveValue方法一定要調(diào)用,就算沒有內(nèi)容,也要像我這樣傳一個空數(shù)組new Uri[0]給它調(diào)用,這個方法如果不調(diào)用,onShowFileChooser就不會再工作了,就是說你沒法再次進行文件選擇了。
3,數(shù)組fileChooserParams.getAcceptTypes()是H5提供的要選擇文件類型,部分控件給的是文件后綴,但安卓需要的必須是MimeType,所以我代碼里有一部分是專門用于轉(zhuǎn)換參數(shù)的。
private ValueCallback<Uri[]> mFilePathCallback;
ActivityResultLauncher<String[]> chooseFileLauncher = registerResultCallback(new ActivityResultContracts.OpenMultipleDocuments(), result -> {
JCLog.i(TAG, "onActivityResult result:" + result);
Uri[] uris;
if (null == result || result.isEmpty()) {
uris = new Uri[0];
} else {
uris = new Uri[result.size()];
for (int i = 0; i < result.size(); i++) {
uris[i] = result.get(i);
}
}
JCLog.i(TAG, "onActivityResult uris: " + Arrays.toString(uris));
if (null != mFilePathCallback) {
mFilePathCallback.onReceiveValue(uris);
mFilePathCallback = null;
}
});
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onShowFileChooser (WebView webView, ValueCallback < Uri[]>
filePathCallback, WebChromeClient.FileChooserParams
fileChooserParams){
JCLog.i(TAG, "onShowFileChooser: " + Arrays.toString(fileChooserParams.getAcceptTypes()));
if (null != chooseFileLauncher) {
mFilePathCallback = filePathCallback;
String[] types = fileChooserParams.getAcceptTypes();
if (null != types && types.length > 0) {
for (int i = 0; i < types.length; i++) {
String type = types[i];
if (TextUtils.isEmpty(type) || !type.startsWith(".")) {
continue;
}
type = type.replaceFirst(".", "");
String newType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(type);
if (!TextUtils.isEmpty(newType)) {
types[i] = newType;
}
}
}
JCLog.i(TAG, "onShowFileChooser before launch: " + Arrays.toString(types));
chooseFileLauncher.launch(types);
return true;
}
return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
}
});
四,加載圖片失敗
測試時發(fā)現(xiàn)某些圖片加載失敗,與H5開發(fā)人員確認,圖片使用的scheme是http。
WebView在安卓5以后,則需要增加設(shè)置:
WebSettings webSettings = webView.getSettings();
……
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
這樣就能正常顯示http的圖片了。文章來源:http://www.zghlxwxcb.cn/news/detail-787783.html
五,輸入框被軟鍵盤遮擋
H5頁面中有輸入框,點擊后,安卓彈出的軟鍵盤遮擋了輸入框,使用網(wǎng)上找到的AndroidBug5497Workaround解決問題,結(jié)果發(fā)現(xiàn)手機底部虛擬按鍵會遮擋頁面,于是再次尋找解決辦法,最后找到了通過修改AndroidBug5497Workaround完美解決這兩個問題的辦法。特此記錄。因項目開發(fā)工作比較忙,此次更新時間間隔較久,所以無法找到代碼引用的原文了,見諒。文章來源地址http://www.zghlxwxcb.cn/news/detail-787783.html
public class AndroidBug5497Workaround {
private final Activity activity;
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
public static void assistActivity(Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private int statusBarHeight;
//狀態(tài)欄高度
private AndroidBug5497Workaround(final Activity activity) {
this.activity = activity;
if (checkDeviceHasNavigationBar(activity)) {
//獲取狀態(tài)欄的高度
int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);
}
//1?找到Activity的最外層布局控件,它其實是一個DecorView,它所用的控件就是FrameLayout
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
//2?獲取到setContentView放進去的View
mChildOfContent = content.getChildAt(0);
//3?給Activity的xml布局設(shè)置View樹監(jiān)聽,當(dāng)布局有變化,如鍵盤彈出或收起時,都會回調(diào)此監(jiān)聽
mChildOfContent.getViewTreeObserver().
addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
//4?軟鍵盤彈起會使GlobalLayout發(fā)生變化
public void onGlobalLayout() {
//5?當(dāng)前布局發(fā)生變化時,對Activity的xml布局進行重繪
possiblyResizeChildOfContent(checkDeviceHasNavigationBar(activity));
}
});
//6?獲取到Activity的xml布局的放置參數(shù)
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
/*** 重新調(diào)整布局高度
* 獲取界面可用高度,如果軟鍵盤彈起后,Activity的xml布局可用高度需要減去鍵盤高度
** @param hasNav*/
private void possiblyResizeChildOfContent(boolean hasNav) {
//1?獲取當(dāng)前界面可用高度,鍵盤彈起后,當(dāng)前界面可用布局會減少鍵盤的高度
int usableHeightNow = computeUsableHeight(hasNav);
//2?如果當(dāng)前可用高度和原始值不一樣
if (usableHeightNow != usableHeightPrevious) {
//3?獲取Activity中xml中布局在當(dāng)前界面顯示的高度
int usableHeightSansKeyboard;
if (hasNav) usableHeightSansKeyboard = mChildOfContent.getHeight();//兼容華為等機型
else {
usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();//這個判斷是為了解決19之前的版本不支持沉浸式狀態(tài)欄導(dǎo)致布局顯示不完全的問題
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
Rect frame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
usableHeightSansKeyboard -= statusBarHeight;
}
}
//4?Activity中xml布局的高度-當(dāng)前可用高度
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
//5?高度差大于屏幕1/4時,說明鍵盤彈出
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// keyboard probably just became visible
// 6?鍵盤彈出了,Activity的xml布局高度應(yīng)當(dāng)減去鍵盤高度
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && hasNav) {
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
} else {
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
}
} else {
if (hasNav) frameLayoutParams.height = usableHeightNow + statusBarHeight;
else frameLayoutParams.height = usableHeightSansKeyboard;
}
//7? 重繪Activity的xml布局
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
/**
* 計算mChildOfContent可見高度
* * @return
*/
private int computeUsableHeight(boolean hasNav) {
if (hasNav) {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
// 全屏模式下:直接返回r.bottom,r.top其實是狀態(tài)欄的高度
if (r.top < statusBarHeight)
return r.bottom - statusBarHeight;
else return r.bottom - r.top;
} else {
Rect frame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
//這個判斷是為了解決19之后的版本在彈出軟鍵盤時,鍵盤和推上去的布局(adjustResize)之間有黑色區(qū)域的問題
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return (r.bottom - r.top) + statusBarHeight;
}
return (r.bottom - r.top);
}
}
/*** 通過"qemu.hw.mainkeys"判斷是否存在NavigationBar
** @return 是否有NavigationBar
*/
private static boolean checkDeviceHasNavigationBar(Activity activity) {
boolean hasNavigationBar = false;
Resources rs = activity.getResources();
int id = rs.getIdentifier("config_showNavigationBar", "bool", "android");
if (id > 0) {
hasNavigationBar = rs.getBoolean(id);
}
try {
Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
Method m = systemPropertiesClass.getMethod("get", String.class);
String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
hasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
hasNavigationBar = true;
} else {
hasNavigationBar = hasNavBar(activity);
}
} catch (Exception e) {
}
return hasNavigationBar;
}
/***
* 根據(jù)屏幕真實寬高-可用寬高>0來判斷是否存在NavigationBar
** @param activity 上下文* @return 是否有NavigationBar
*/
private static boolean hasNavBar(Activity activity) {
WindowManager windowManager = activity.getWindowManager();
Display d = windowManager.getDefaultDisplay();
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
d.getRealMetrics(realDisplayMetrics);
}
int realHeight = realDisplayMetrics.heightPixels;
int realWidth = realDisplayMetrics.widthPixels;
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
int displayWidth = displayMetrics.widthPixels;
return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
}
}
到了這里,關(guān)于項目筆記——安卓WebView加載H5頁面問題處理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!