Android:使锚链接在 ScrollView 内的 WebView 中工作

问题描述 投票:0回答:3

我的 HTML 中有锚链接(例如:

<a href="#P12"/>
),我将其加载到 WebView 中,并且此 WebView 位于 NestedScrollView 中。当我暂时禁用 NestedScrollView 时,因为每个人都建议在锚链接无法正常工作时这样做,这些锚链接可以正常工作。但是,由于多种原因,我的布局确实需要这个 NestedScrollView:

  1. 它有一个CollapsingToolbarLayout和
    app:layout_behavior="@string/appbar_scrolling_view_behavior
    需要设置;
  2. WebView上下都有布局元素;
  3. 这些元素必须作为一个整体一起滚动。

预期的行为是单击锚链接时内容跳转到网页的正确部分。实际行为是内容跳到某个随机位置,您无法再滚动整个页面(顶部或底部被截断)。

是否有可能使这些锚链接在我的 NestedScrollView 内的 WebView 中工作?

android webview hyperlink scrollview anchor
3个回答
1
投票

因为当我偶然发现一个没有答案的问题时让我很沮丧,我会回答我自己的问题,这样其他人就会知道我是如何解决这个问题的。

坏消息是,这个特殊问题无法解决。至少,2021 年还没有。

我所在的公司和客户决定做的是改变这个屏幕的整个实现。整个屏幕现在是一个 WebView,而不仅仅是屏幕的一部分是 WebView。


1
投票

我不能用上面提供的方法做到这一点,所以我尝试了差异方法

  1. 使用您的 webview 启用 JS

    webView.getSettings().setJavaScriptEnabled(true);

  2. 添加和定义 javascript 接口关键字,以便稍后从 javascript 访问它

    //Adding Javascript interface so that javascript can access annotated functions in this Class
    webView.addJavascriptInterface ( this , "android" );`
    
  3. 然后创建一个扩展

    WebViewClient

    的类
  4. 启用 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" +
    
    
            "       }" +
            "}"+
      "})();");
    

    }

  5. 现在在你定义了 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()
    );
}

0
投票

这是我的工作解决方案。单击锚链接将适当地滚动 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()
© www.soinside.com 2019 - 2024. All rights reserved.