【问题标题】:Android webview wont consistently open to last-scroll locationAndroid webview 不会一直打开到最后滚动位置
【发布时间】:2015-01-21 21:53:16
【问题描述】:

我有一个 Android 应用程序,它使用 KitKat 级别的 WebView 来显示文档(主要作为使用 HTML 文件的电子阅读器)。当用户在应用程序中打开一个文档(因此为该文档新实例化了 webview)时,我想滚动到文档中最后一个已知的光标位置,以便用户可以从他们离开的地方继续阅读。显然,当我第一次打开文档时,我独立于 webview 保留和持久化光标位置,以允许这种滚动。

问题: 当文档第一次打开时,我可以在我的 javascript 文档中看到最后一个已知位置(光标)的初始突出显示,但是在大约 50% 的情况下,我看到文档以某种方式滚动回顶部,因此光标位置不再显示。在这些情况下它如何以及为什么滚动回顶部对我来说是一个谜(我不相信我以编程方式让它以任何方式滚动回顶部)。

注意事项: 为了在 webview 中实现滚动,我使用了一个 javascript 函数,一旦 webview 发出信号,它已加载并准备好使用,它就会从我的 webview 片段(通过 webview.loadUrl)调用。这是从文档视图片段到 webview 的回调:

	@TargetApi(Build.VERSION_CODES.KITKAT) @SuppressLint("ClickableViewAccessibility") @Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
	{		
		View mainView = inflater.inflate(R.layout.document_webview_reader, container, false);
		getReaderActivity().setFooterLocationBarValues(mainView);
		
		mWebviewLoading = true;
		final WebView webview = (WebView) mainView.findViewById(R.id.document_webReader_webView);
		webview.getSettings().setJavaScriptEnabled(true);
		webview.getSettings().setAllowFileAccessFromFileURLs(true); 
		webview.getSettings().setAllowUniversalAccessFromFileURLs(true);
		webview.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
		webview.setBackgroundColor(0);
		webview.setBackgroundColor(Color.LTGRAY);
		
		if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
		{
		    WebView.setWebContentsDebuggingEnabled(true);
		}
		
		mJsInterface = new JsCall();
		webview.addJavascriptInterface(mJsInterface, mJsInterface.getInterfaceName());
		
		webview.setWebViewClient(new WebViewClient()
		{			
			public void onPageFinished(WebView view, String url)
			{
				drawInitialCursor(getDocument().getCursorRange());						
			}
		});
		

	private void drawInitialCursor(final WordRange range)
	{
		if( getReaderActivity().activityIsPaused() ) return;
    	
		getActivity().runOnUiThread(new Runnable()
		{
			
			@Override
			public void run()
			{
				Log.d(ReaderApplication.VDREADER_TAG,"highlighting initial range");
				WordRange currentFragmentRange = mTextFragmentRanges.get(mCurrentTextFragmentIndex);
				int relativeLocation = range.getLocation() - currentFragmentRange.getStartRange();
				WordRange rangeInCurrentFragment = new WordRange(relativeLocation, range.getLength());
						
				WebView webview = (WebView) getActivity().findViewById(R.id.document_webReader_webView);
				
				String applyString = String.format("javascript:highlightInitialRange(%d,%d,%d,%d)", 
						rangeInCurrentFragment.getStartRange(),rangeInCurrentFragment.getEndRange(), 
						mCurrentTextFragmentIndex,
						mCursorPositionSetting.ordinal());

				webview.loadUrl(applyString);
			}
		});
	}

'highlightInitialRange' 的 javascript 是:

function highlightInitialRange(start, end, elementIndex, cursorPagePositionSetting)
    {
        if( wordHighlightApplier == null ) {
            wordHighlightApplier = rangy.createClassApplier("voice-dream-spoken-word",
                { elementTagName: "vdword"}
            );
        }
        if( selRange == null ) {
            selRange = rangy.createRange();
        }

        wordHighlightApplier.undoToRange(selRange);
        var	myNodelist = document.querySelectorAll(VD_cssSelector);

        selRange.selectCharacters(myNodelist[elementIndex], start, end);
        wordHighlightApplier.applyToRange(selRange);

        var wordCursor = document.getElementsByTagName("vdword");
        wordCursor[0].scrollIntoView(true);

    }

上面的 javascript 基本上只是在文档中找到相关范围(作为该元素内元素索引和偏移量的组合),对其应用突出显示(通过 rangy),然后将该突出显示的元素滚动到视图中。

正如我所说,我知道这种策略通常有效,因为当加载文档并且 web 视图已准备好使用时,我总能看到它突出显示正确的位置;但在我运行此程序的大约 50% 的时间里,我看到光标短暂突出显示,然后随着文档滚动回顶部它消失(当然,假设光标范围超出了文档的初始页面)。

** 我会在此处发布正确光标突出显示的图像,但显然我没有足够的声誉来允许这样的琐事。 **

问题: 我在这里错过了有关 webview 文档渲染的内容吗?我知道在突出显示和突出显示元素的初始滚动到视图方面一切正常,并且我仅在 webview 完全加载后执行此突出显示代码(通过 webview 客户端上的 onPageFinished 通知)。当文档完全加载以确保文档显示在其最顶部的滚动位置时,webview 本身是否存在不确定性执行的内容?

*** 由所有者解决

事后看来,这个问题的解决方案相当明显:我使用了一个“包装器”html 文档,在其中加载了各种 JS 库 - JSQuery、Rangy 等,并且在其中我使用 JSQuery 将文档正文替换为我要加载/阅读的“真实”html 文档。因此,从 HTML 文档的角度来看,加载分两个阶段进行:

  1. 加载包装器
  2. Wrapper 通过 $("body").load("") 加载真正的 html 文档

由于这种两阶段加载,我的应用程序看到 Webview.onPageFinished 事件被触发 - 但这仅表示包装器 html 的加载,而不是整个正文。所以我需要做的就是在 JSQuery .load 方法成功完成后从我的 webview / javascript 添加一个显式调用到应用程序:

<body>
<script>
    $( "body" ).load( "%s", function( response, status, xhr )
    {
        if (status == "error")
        {
            var msg = "Sorry but there was an error: ";
            $("#error").html(msg + xhr.status + " " + xhr.statusText);
        }
        else
        {
            // append our line highlight div, floating
            var newDiv = $("<div id='vd-spoken-line' class='voice-dream-spoken-line'/>");
            $( "body" ).append(newDiv);

            notifyDocumentBodyLoaded();    <-- new callback to android app here
        }
    });
</script>
</body>

因此,'notifyDocumentBodyLoaded' 执行之前在 onPageFinished 方法中执行的 Android/java 代码,但只有在 JSQuery 加载了真正的文档正文之后。这样加载-> 光标绘制的顺序都以正确的顺序发生。解决了。​​

【问题讨论】:

  • 在这里解决了我自己的问题 - 请参阅更新的问题文本。
  • 请考虑为社区发布实际答案并解决它。

标签: javascript android webview


【解决方案1】:

如更新问题中所述(添加此答案以便我解决此问题):

事后看来,这个问题的解决方案相当明显:我使用了一个“包装器”html 文档,在其中加载了各种 JS 库 - JSQuery、Rangy 等,并且在其中我使用 JSQuery 将文档正文替换为我要加载/阅读的“真实”html 文档。因此,从 HTML 文档的角度来看,加载分两个阶段进行:

加载包装器 Wrapper 通过 $("body").load("") 加载真正的 html 文档 由于这种两阶段加载,我的应用程序看到 Webview.onPageFinished 事件被触发 - 但这仅表示包装器 html 的加载,而不是整个正文。所以我需要做的就是在 JSQuery .load 方法成功完成后从我的 webview / javascript 添加一个显式调用到应用程序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多