【发布时间】: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 文档的角度来看,加载分两个阶段进行:
- 加载包装器
- 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