我的 HTML 中有锚链接(例如:
<a href="#P12"/>
),我将其加载到 WebView 中,并且此 WebView 位于 NestedScrollView 中。当我暂时禁用 NestedScrollView 时,因为每个人都建议在锚链接无法正常工作时这样做,这些锚链接可以正常工作。但是,由于多种原因,我的布局确实需要这个 NestedScrollView:
app:layout_behavior="@string/appbar_scrolling_view_behavior
需要设置;预期的行为是单击锚链接时内容跳转到网页的正确部分。实际行为是内容跳到某个随机位置,您无法再滚动整个页面(顶部或底部被截断)。
是否有可能使这些锚链接在我的 NestedScrollView 内的 WebView 中工作?
因为当我偶然发现一个没有答案的问题时让我很沮丧,我会回答我自己的问题,这样其他人就会知道我是如何解决这个问题的。
坏消息是,这个特殊问题无法解决。至少,2021 年还没有。
我所在的公司和客户决定做的是改变这个屏幕的整个实现。整个屏幕现在是一个 WebView,而不仅仅是屏幕的一部分是 WebView。
我不能用上面提供的方法做到这一点,所以我尝试了差异方法
使用您的 webview 启用 JS
webView.getSettings().setJavaScriptEnabled(true);
添加和定义 javascript 接口关键字,以便稍后从 javascript 访问它
//Adding Javascript interface so that javascript can access annotated functions in this Class
webView.addJavascriptInterface ( this , "android" );`
然后创建一个扩展
WebViewClient
的类
启用 html5 功能并添加一些 javascript 代码
private void loadWebViewDatafinal(WebView wv) {
WebSettings ws=wv.getSettings();
ws.setJavaScriptEnabled(true);
ws.setAllowFileAccess(true);
//this try block enables html5
try {
Log.e("WEB_VIEW_JS", "Enabling HTML5-Features");
Method m1=WebSettings.class.getMethod("setDomStorageEnabled", new Class[]{Boolean.TYPE});
m1.invoke(ws, Boolean.TRUE);
Method m2=WebSettings.class.getMethod("setDatabaseEnabled", new Class[]{Boolean.TYPE});
m2.invoke(ws, Boolean.TRUE);
Method m3=WebSettings.class.getMethod("setDatabasePath", new Class[]{String.class});
m3.invoke(ws, "/data/data/" + mContext.getPackageName() + "/databases/");
Method m4=WebSettings.class.getMethod("setAppCacheMaxSize", new Class[]{Long.TYPE});
m4.invoke(ws, 1024 * 1024 * 8);
Method m5= WebSettings.class.getMethod("setAppCachePath", new Class[]{String.class});
m5.invoke(ws, "/data/data/" + mContext.getPackageName() + "/cache/");
Method m6=WebSettings.class.getMethod("setAppCacheEnabled", new Class[]{Boolean.TYPE});
m6.invoke(ws, Boolean.TRUE);
Log.e("WEB_VIEW_JS", "Enabled HTML5-Features");
} catch (Exception e) {
Log.e("WEB_VIEW_JS", "Reflection fail", e);
}
//this javascipt code give me the offset from the top of the link which
//i send to the android java class using the keyword 'android' which was defined above .
wv.loadUrl("javascript:(function(){\n"+
// anchor tag and perform in click on anchor click
"var anchors= document.getElementsByTagName(\"a\");\n" +
"\n" +
"for( let i = 0;i < anchors.length;i+=1)\n" +
"{\n" +
" let currentanchor=anchors[i];\n" +
" \n" +
" currentanchor.onclick= function(){\n" +
" if (this.hash !== \"\") {\n" +
"\n" +
" event.preventDefault();\n" +
"\n" +
" var hash = this.hash;\n" +
"\n" +
" var div = document.getElementById(hash.replace('#',''));\n" +
"\n" +
//this is the line by which we can link javascript values from html and Java
" android.OnAnchorClickScroll(div.offsetTop);\n"+
"\n" +
" };\n" +
"\n" +
" }" +
"}"+
"})();");
}
现在在你定义了 android 关键字的类中添加一个函数 并有权访问滚动视图以滚动
@JavascriptInterface
public void OnAnchorClickScroll(float pos) {
GlobalClassForFunctions.getInstance ( ).PrintMessageOnTheConsole ( "THIS IS Anchor pos --" + pos );
int offsetInt = (int)getDensityIndependentPixels(pos, mContext);
scrollView.smoothScrollTo(0,webView.getTop()+offsetInt);
}
这是正在使用的功能
// this function is to be used whenever you are getting pixels from javascript and want them to be converted in android dp
//We need this, because the measured pixels in the WebView don't use density of the screen
public float getDensityIndependentPixels(float pixels, Context context){
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
pixels,
context.getResources().getDisplayMetrics()
);
}
这是我的工作解决方案。单击锚链接将适当地滚动 ScrollView。单击返回将滚动回到顶部。
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val wvClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
if (url != null && url.contains("#")) {
val anchor = url.substring(url.lastIndexOf("#") + 1)
view?.evaluateJavascript(
"(function() { " +
"var element = document.getElementById('$anchor');" +
"if (element) {" +
" var rect = element.getBoundingClientRect();" +
" return rect.top;" +
"}" +
"return -1;" +
"})();"
) { value ->
val anchorY = value.toFloat().toInt()
if (anchorY != -1) {
// scroll to to anchor target
binding.svContent.scrollTo(0, anchorY.toPx())
}
}
} else {
// Scroll to top
binding.svContent.scrollTo(0, 0)
}
}
}
with (binding.myWebview) {
settings.javaScriptEnabled = true
webViewClient = wvClient
loadUrl("https://html.com/anchors-links/")
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
val callback: OnBackPressedCallback =
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.myWebview.canGoBack()){
binding.myWebview.goBack()
}
}
}
requireActivity().onBackPressedDispatcher.addCallback(
this,
callback
)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
fun Int.toPx() : Int = (this * Resources.getSystem().displayMetrics.density).toInt()