package com.heytap.rjkf;
import android.speech.tts.UtteranceProgressListener;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.app.Activity;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.content.res.Configuration;
import android.widget.Button;
import android.widget.Toast;
import android.webkit.ValueCallback;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Color;
import java.util.Locale;
import java.util.UUID;
import android.os.Handler;
import java.util.regex.Pattern;
public class MainActivity extends Activity implements TextToSpeech.OnInitListener {
private WebView mWebView;
private TextToSpeech tts;
private StringBuilder textBuilder = new StringBuilder();
private boolean isSpeaking = false;
private boolean isPaused = false;
private int currentSentenceIndex = 0;
private int currentCharIndex = 0;
private String[] sentences;
private Handler handler = new Handler();
private static final String PREFS_NAME = "ReadProgress";
private static final String KEY_SENTENCE_INDEX = "sentenceIndex";
private static final String KEY_CHAR_INDEX = "charIndex";
private boolean shouldHighlight = false;
// 优化后的正则表达式常量
private static final Pattern BIBLE_REF_PATTERN = Pattern.compile(
"\\b(?:创|亚|玛|太|可|启|但|何)\\s*?[一二百]*\\d+([、,]\\s*\\d+)*([~下]*\\d*[上]*)?[节]?\\b|" +
"\\s*?[一二十百]*\\d+([、,]\\s*\\d+)*([~下]*\\d*[上]*)?[节]?|" +
"(引用文|参\\s*(?:创|但何)\\s*?[一二三九十百]*\\d+([、,]\\s*\\d+)*([~下]*\\d*[上]*)?)"
);
private static final Pattern SENTENCE_DELIMITER = Pattern.compile("(?<=[ 。!??!])");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 检查是否有保存的进度
checkSavedProgress();
initTextToSpeech();
setupWebView();
loadWebContent("file:///android_asset/index.html");
setupButtons();
}
private void initTextToSpeech() {
tts = new TextToSpeech(this, this);
setUtteranceProgressListener();
}
private void checkSavedProgress() {
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
int savedSentenceIndex = prefs.getInt(KEY_SENTENCE_INDEX, -1);
int savedCharIndex = prefs.getInt(KEY_CHAR_INDEX, -1);
if (savedSentenceIndex != -1 && savedCharIndex != -1) {
new AlertDialog.Builder(this)
.setTitle("继续朗读?")
.setMessage("检测到上次未完成的朗读进度,是否继续?")
.setPositiveButton("是", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
currentSentenceIndex = savedSentenceIndex;
currentCharIndex = savedCharIndex;
shouldHighlight = true; // 标记需要高亮
}
})
.setNegativeButton("否", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
clearProgress();
}
})
.setCancelable(false)
.show();
}
}
// JavaScript接口类
private class JavaScriptInterface {
@android.webkit.JavascriptInterface
public void highlightSentence(int sentenceIndex, int charIndex) {
runOnUiThread(() -> {
try {
// 调用JS高亮方法
String js = String.format("highlightSentence(%d, %d)", sentenceIndex, charIndex);
mWebView.evaluateJavascript(js, null);
} catch (Exception e) {
Toast.makeText(MainActivity.this,
"高亮错误: " + e.getMessage(),
Toast.LENGTH_SHORT).show();
}
});
}
}
private void setupWebView() {
mWebView = findViewById(R.id.mWebView);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setDomStorageEnabled(true);
mWebView.addJavascriptInterface(new JavaScriptInterface(), "AndroidInterface");
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// 注入高亮JS
injectHighlightJS();
extractTextFromWebPage();
// 如果需要恢复高亮
if (shouldHighlight) {
highlightCurrentPosition();
shouldHighlight = false;
}
}
});
}
// 注入高亮JS脚本
private void injectHighlightJS() {
String highlightJS =
"javascript:(function() {" +
" window.highlightSentence = function(sentenceIndex, charIndex) {" +
" const sentences = document.querySelectorAll('.sentence');" +
" sentences.forEach(s => s.style.backgroundColor = '');" +
" if (sentenceIndex >= 0 && sentenceIndex < sentences.length) {" +
" const sentence = sentences[sentenceIndex];" +
" sentence.style.backgroundColor = '#FFF59D';" +
" sentence.scrollIntoView({behavior: 'smooth', block: 'center'});" +
" " +
" // 高亮单个字符(可选)" +
" const text = sentence.textContent;" +
" if (charIndex > 0 && charIndex < text.length) {" +
" const prefix = text.substring(0, charIndex);" +
" const currentChar = text.substring(charIndex, charIndex + 1);" +
" const suffix = text.substring(charIndex + 1);" +
" sentence.innerHTML = prefix + '<span style=\"background-color:#FFCC80\">' + currentChar + '</span>' + suffix;" +
" }" +
" }" +
" };" +
" console.log('Highlight JS injected');" +
"})()";
mWebView.evaluateJavascript(highlightJS, null);
}
private void extractTextFromWebPage() {
mWebView.evaluateJavascript("(function() {" +
"var nodes = document.body.childNodes;" +
"var index = 0;" +
"var sentences = [];" +
"for (var i = 0; i < nodes.length; i++) {" +
" var node = nodes[i];" +
" if (node.nodeType === Node.TEXT_NODE) {" +
" var text = node.nodeValue.trim();" +
" if (text) {" +
" var span = document.createElement('span');" +
" span.className = 'sentence';" +
" span.dataset.index = index++;" +
" span.textContent = text;" +
" node.parentNode.replaceChild(span, node);" +
" sentences.push(text);" +
" }" +
" } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() !== 'br') {" +
" var innerText = node.innerText.trim();" +
" if (innerText) {" +
" var span = document.createElement('span');" +
" span.className = 'sentence';" +
" span.dataset.index = index++;" +
" span.textContent = innerText;" +
" node.innerHTML = '';" +
" node.appendChild(span);" +
" sentences.push(innerText);" +
" }" +
" }" +
"}" +
"return sentences.join('|');" +
"})()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
runOnUiThread(() -> {
try {
if (value != null && !value.isEmpty() && !"null".equals(value)) {
textBuilder.setLength(0);
// 移除JSON字符串的引号
String rawText = value.replaceAll("^\"|\"$", "");
textBuilder.append(rawText);
// 使用预编译的正则表达式提高效率
String filteredText = BIBLE_REF_PATTERN.matcher(textBuilder.toString()).replaceAll("");
// 改进句子分割逻辑
sentences = SENTENCE_DELIMITER.split(filteredText);
currentSentenceIndex = 0;
currentCharIndex = 0;
// 如果需要恢复高亮
if (shouldHighlight) {
highlightCurrentPosition();
shouldHighlight = false;
}
} else {
Toast.makeText(MainActivity.this, "页面内容为空", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Toast.makeText(MainActivity.this, "文本处理错误: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
});
}
private void highlightCurrentPosition() {
if (mWebView != null) {
String js = String.format("highlightSentence(%d, %d)",
currentSentenceIndex,
currentCharIndex);
mWebView.evaluateJavascript(js, null);
}
}
private void saveProgress() {
SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit();
editor.putInt(KEY_SENTENCE_INDEX, currentSentenceIndex);
editor.putInt(KEY_CHAR_INDEX, currentCharIndex);
editor.apply();
}
private void clearProgress() {
SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit();
editor.remove(KEY_SENTENCE_INDEX);
editor.remove(KEY_CHAR_INDEX);
editor.apply();
}
// 其他方法保持不变(setupButtons, stopSpeaking, setUtteranceProgressListener等)
// 确保在onDestroy中保存当前进度
private void setupButtons() {
Button speakButton = findViewById(R.id.speakButton);
speakButton.setOnClickListener(v -> {
if (sentences == null || sentences.length == 0) {
Toast.makeText(MainActivity.this, "请先加载内容", Toast.LENGTH_SHORT).show();
return;
}
if (isPaused) {
isPaused = false;
speakFromCurrentPosition();
Toast.makeText(MainActivity.this, "朗读继续", Toast.LENGTH_SHORT).show();
} else {
if (isSpeaking) {
Toast.makeText(MainActivity.this, "正在朗读中", Toast.LENGTH_SHORT).show();
} else {
currentSentenceIndex = 0;
currentCharIndex = 0;
speakFromCurrentPosition();
Toast.makeText(MainActivity.this, "朗读开始", Toast.LENGTH_SHORT).show();
}
}
});
Button stopButton = findViewById(R.id.stopButton);
stopButton.setOnClickListener(v -> {
stopSpeaking();
Toast.makeText(MainActivity.this, "朗读已停止", Toast.LENGTH_SHORT).show();
});
Button pauseButton = findViewById(R.id.pauseButton);
pauseButton.setOnClickListener(v -> {
if (isSpeaking) {
tts.stop();
isSpeaking = false;
isPaused = true;
Toast.makeText(MainActivity.this, "朗读已暂停", Toast.LENGTH_SHORT).show();
}
});
}
private void stopSpeaking() {
if (tts != null) {
tts.stop();
}
isSpeaking = false;
isPaused = false;
}
private void setUtteranceProgressListener() {
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
isSpeaking = true;
isPaused = false;
}
@Override
public void onDone(String utteranceId) {
handler.post(() -> {
// 更新朗读位置(整句完成)
if (currentSentenceIndex < sentences.length) {
currentCharIndex = sentences[currentSentenceIndex].length();
}
handler.postDelayed(MainActivity.this::speakFromCurrentPosition, 100);
});
}
@Override
public void onError(String utteranceId) {
isSpeaking = false;
handler.post(() ->
Toast.makeText(MainActivity.this, "朗读错误", Toast.LENGTH_SHORT).show()
);
}
});
}
private void speakFromCurrentPosition() {
if (tts == null || isPaused || sentences == null) return;
try {
// 跳过空句子
while (currentSentenceIndex < sentences.length &&
(sentences[currentSentenceIndex] == null ||
sentences[currentSentenceIndex].trim().isEmpty())) {
currentSentenceIndex++;
currentCharIndex = 0;
}
if (currentSentenceIndex >= sentences.length) {
isSpeaking = false;
Toast.makeText(this, "播放完成", Toast.LENGTH_SHORT).show();
return;
}
String currentSentence = sentences[currentSentenceIndex];
if (currentCharIndex >= currentSentence.length()) {
// 移动到下一句
currentSentenceIndex++;
currentCharIndex = 0;
speakFromCurrentPosition();
return;
}
// 朗读当前句子剩余部分
String partToSpeak = currentSentence.substring(currentCharIndex);
if (!partToSpeak.trim().isEmpty()) {
Bundle params = new Bundle();
String utteranceId = UUID.randomUUID().toString();
params.putCharSequence(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
tts.speak(partToSpeak, TextToSpeech.QUEUE_FLUSH, params, utteranceId);
// 更新字符位置(整句朗读)
currentCharIndex = currentSentence.length();
} else {
// 跳过空白句子
currentSentenceIndex++;
currentCharIndex = 0;
speakFromCurrentPosition();
}
} catch (Exception e) {
Toast.makeText(this, "朗读异常: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private void loadWebContent(String url) {
try {
mWebView.loadUrl(url);
} catch (Exception e) {
Toast.makeText(this, "内容加载失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
public void onBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack();
} else {
super.onBackPressed();
}
}
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = tts.setLanguage(Locale.CHINESE);
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Toast.makeText(this, "语言不支持", Toast.LENGTH_SHORT).show();
} else {
// 设置朗读参数
tts.setPitch(1.0f);
tts.setSpeechRate(1.0f);
}
} else {
Toast.makeText(this, "TTS初始化失败", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
// 保存当前进度
if (isSpeaking || isPaused) {
saveProgress();
} else {
clearProgress();
}
if (tts != null) {
tts.stop();
tts.shutdown();
}
super.onDestroy();
}
}
你看下我代码,是什么原有导致到第一次打开快速闪下能看到webbiew ,然后就突然黑了看不到,只能看到底部的三个按钮组件, 下面是布局:<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- WebView应占据主要空间 -->
<WebView
android:id="@+id/mWebView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<!-- 按钮放在底部 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="@+id/speakButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="朗读"/>
<Button
android:id="@+id/pauseButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂停"/>
<Button
android:id="@+id/stopButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止"/>
</LinearLayout>
</LinearLayout>