添加搜索文字测试代码,后续看是否使用。(未完成)

This commit is contained in:
ocean 2025-10-23 17:16:28 +08:00
parent d9dfa75b9b
commit 8e4c25d54f
10 changed files with 890 additions and 24 deletions

View File

@ -105,13 +105,18 @@
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <activity
android:name=".ui.act.PdfToImageActivity" android:name=".ui.act.PdfToImageActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity
android:name=".ui.act.PdfTextSearchTestActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="portrait" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"

View File

@ -0,0 +1,226 @@
package com.all.pdfreader.pro.app.ui.act
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.databinding.ActivityPdfViewerTestBinding
import com.all.pdfreader.pro.app.ui.adapter.SearchResultsAdapter
import com.all.pdfreader.pro.app.util.PDFHighlighter
import com.all.pdfreader.pro.app.util.PDFSearchManager
import com.github.barteksc.pdfviewer.listener.OnPageChangeListener
import com.tom_roush.pdfbox.pdmodel.PDDocument
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class PdfTextSearchTestActivity : BaseActivity() {
override val TAG: String = "PdfTextSearchTestActivity"
private lateinit var binding: ActivityPdfViewerTestBinding
private lateinit var searchManager: PDFSearchManager
private lateinit var highlighter: PDFHighlighter
private var pdfFile: File? = null
private val searchResults = mutableListOf<PDFSearchManager.TextSearchResult>()
private lateinit var adapter: SearchResultsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPdfViewerTestBinding.inflate(layoutInflater)
setContentView(binding.root)
val pdfPath = intent.getStringExtra("pdf_path") ?: ""
if (pdfPath.isEmpty()) {
finish()
return
}
pdfFile = File(pdfPath)
initViews()
setupPDFViewer()
loadPDF()
}
private fun initViews() {
adapter = SearchResultsAdapter(searchResults) { result ->
onSearchResultClicked(result, true)
}
binding.searchResultsRecyclerView.adapter = adapter
binding.searchResultsRecyclerView.layoutManager = LinearLayoutManager(this)
binding.searchButton.alpha = 0.5f
binding.searchButton.isEnabled = false
binding.searchButton.setOnClickListener {
performSearch()
}
binding.clearButton.setOnClickListener {
clearHighlights()
}
}
private fun setupPDFViewer() {
searchManager = PDFSearchManager(binding.pdfView)
highlighter = PDFHighlighter(binding.pdfView, searchManager)
initSearchDocument()
}
private fun initSearchDocument() {
lifecycleScope.launch(Dispatchers.IO) {
searchManager.getDocument(
pdfFile!!,
onLoaded = { doc ->
lifecycleScope.launch(Dispatchers.IO) {
checkPageHasText(doc, binding.pdfView.currentPage)
}
},
onError = { e ->
runOnUiThread {
binding.searchButton.alpha = 0.5f
binding.searchButton.isEnabled = false
}
}
)
}
}
/**
* 异步检查指定页是否有文字并更新按钮
*/
private suspend fun checkPageHasText(doc: PDDocument, pageIndex: Int): Boolean {
val hasText = searchManager.pageHasText(doc, pageIndex, minChars = 8) // 可调整阈值
withContext(Dispatchers.Main) {
if (hasText) {
binding.searchButton.alpha = 1f
binding.searchButton.isEnabled = true
} else {
binding.searchButton.alpha = 0.5f
binding.searchButton.isEnabled = false
}
}
return hasText
}
private var pageCheckJob: Job? = null
private fun loadPDF() {
binding.pdfView.fromFile(pdfFile)
.enableSwipe(true)
.swipeHorizontal(true)
.enableDoubletap(true)
.defaultPage(0)
.onDraw(highlighter) // 设置绘制监听器
.enableAnnotationRendering(true)
.password(null)
.scrollHandle(null)
.swipeHorizontal(false)
.enableAntialiasing(true)
.onPageChange(object : OnPageChangeListener {
override fun onPageChanged(page: Int, pageCount: Int) {
logDebug("page->$page")
logDebug("pageCount->$pageCount")
val doc = searchManager.getCachedDoc()
if (doc != null) {
lifecycleScope.launch(Dispatchers.IO) {
pageCheckJob?.cancel()
pageCheckJob = lifecycleScope.launch(Dispatchers.IO) {
delay(50) // 防抖
val hasText = checkPageHasText(doc, page)
//确定这页有文字,是否进行搜索过,当前搜索的文字
if (hasText && searchManager.hasSearched && searchManager.currentSearchText != null) {
searchTextIng(
pdfFile!!,
searchManager.currentSearchText ?: "",
binding.pdfView.currentPage
)
}
}
}
}
}
})
.spacing(0)
.load()
}
private fun performSearch() {
val searchText = binding.searchEditText.text.toString().trim()
if (searchText.isEmpty()) {
Toast.makeText(this, "请输入搜索关键词", Toast.LENGTH_SHORT).show()
return
}
val file = pdfFile ?: return
binding.progressBar.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.IO) {
searchTextIng(file, searchText, binding.pdfView.currentPage)
}
}
private fun searchTextIng(pdfFile: File, key: String, targetPage: Int? = null) {
lifecycleScope.launch(Dispatchers.IO) {
try {
val results = searchManager.searchText(pdfFile, key, targetPage)
withContext(Dispatchers.Main) {
binding.progressBar.visibility = View.GONE
searchResults.clear()
searchResults.addAll(results)
adapter.notifyDataSetChanged()
if (!results.isEmpty()) {
results.firstOrNull()?.let { firstResult ->
onSearchResultClicked(firstResult)
}
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
binding.progressBar.visibility = View.GONE
}
}
}
}
private fun onSearchResultClicked(
result: PDFSearchManager.TextSearchResult,
isJumpTo: Boolean = false
) {
if (isJumpTo) {
// 跳转到对应页面 (PDFView 页面索引从0开始)
binding.pdfView.jumpTo(result.pageNumber - 1)
}
// 设置高亮
highlighter.setCurrentPageHighlights(result.pageNumber)
// 标记已搜索高亮
searchManager.currentSearchText = result.text
searchManager.hasSearched = true
// 可选:滚动到搜索结果列表中的对应项
val index = searchResults.indexOf(result)
if (index != -1) {
binding.searchResultsRecyclerView.scrollToPosition(index)
}
}
private fun clearHighlights() {
searchManager.clearHighlights()
highlighter.clearCurrentHighlights()
searchResults.clear()
adapter.notifyDataSetChanged()
}
override fun onDestroy() {
super.onDestroy()
searchManager.closeCachedDocument()
}
}

View File

@ -276,7 +276,9 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
onTap(this@PdfViewActivity) // 单击回调 onTap(this@PdfViewActivity) // 单击回调
onPageChange(this@PdfViewActivity) // 页面改变回调 onPageChange(this@PdfViewActivity) // 页面改变回调
scrollHandle(CustomScrollHandle(this@PdfViewActivity)) // 自定义的页数展示 scrollHandle(CustomScrollHandle(this@PdfViewActivity)) // 自定义的页数展示
onDraw { canvas, pageWidth, pageHeight, displayedPage ->
}
if (appStore.isPageFling) { if (appStore.isPageFling) {
pageSnap(true)//页面可在逐页中,可居中展示,非逐页模式,滑动停止后可自动定格在居中位置。 pageSnap(true)//页面可在逐页中,可居中展示,非逐页模式,滑动停止后可自动定格在居中位置。
pageFling(true)//逐页 pageFling(true)//逐页

View File

@ -0,0 +1,41 @@
package com.all.pdfreader.pro.app.ui.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.util.PDFSearchManager
class SearchResultsAdapter(
private val results: List<PDFSearchManager.TextSearchResult>,
private val onItemClick: (PDFSearchManager.TextSearchResult) -> Unit
) : RecyclerView.Adapter<SearchResultsAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val pageText: TextView = view.findViewById(R.id.pageText)
val matchCount: TextView = view.findViewById(R.id.matchCount)
val previewText: TextView = view.findViewById(R.id.previewText)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_search_result, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val result = results[position]
holder.pageText.text = "${result.pageNumber}"
holder.matchCount.text = "${result.positions.size} 处匹配"
holder.previewText.text = "搜索: \"${result.text}\""
holder.itemView.setOnClickListener {
onItemClick(result)
}
}
override fun getItemCount() = results.size
}

View File

@ -1,13 +1,12 @@
package com.all.pdfreader.pro.app.ui.fragment package com.all.pdfreader.pro.app.ui.fragment
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.databinding.FragmentHomeBinding import com.all.pdfreader.pro.app.databinding.FragmentHomeBinding
import com.all.pdfreader.pro.app.model.FragmentType import com.all.pdfreader.pro.app.model.FragmentType
@ -15,6 +14,7 @@ import com.all.pdfreader.pro.app.model.SortConfig
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.ui.act.MainActivity import com.all.pdfreader.pro.app.ui.act.MainActivity
import com.all.pdfreader.pro.app.ui.act.PdfTextSearchTestActivity
import com.all.pdfreader.pro.app.ui.act.PdfViewActivity import com.all.pdfreader.pro.app.ui.act.PdfViewActivity
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import com.all.pdfreader.pro.app.ui.dialog.ListMoreDialogFragment import com.all.pdfreader.pro.app.ui.dialog.ListMoreDialogFragment
@ -53,6 +53,11 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment {
adapter = PdfAdapter(pdfList = mutableListOf(), onItemClick = { pdf -> adapter = PdfAdapter(pdfList = mutableListOf(), onItemClick = { pdf ->
val intent = PdfViewActivity.createIntent(requireContext(), pdf.filePath) val intent = PdfViewActivity.createIntent(requireContext(), pdf.filePath)
startActivity(intent) startActivity(intent)
// val intent = Intent(context, PdfTextSearchTestActivity::class.java).apply {
// putExtra("pdf_path", pdf.filePath)
// }
// startActivity(intent)
}, onMoreClick = { pdf -> }, onMoreClick = { pdf ->
ListMoreDialogFragment(pdf.filePath).show(parentFragmentManager, FRAG_TAG) ListMoreDialogFragment(pdf.filePath).show(parentFragmentManager, FRAG_TAG)
}, onLongClick = { pdf -> }, onLongClick = { pdf ->

View File

@ -0,0 +1,81 @@
package com.all.pdfreader.pro.app.util
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import com.github.barteksc.pdfviewer.PDFView
import com.github.barteksc.pdfviewer.listener.OnDrawListener
class PDFHighlighter(
private val pdfView: PDFView,
private val searchManager: PDFSearchManager
) : OnDrawListener {
private val highlightPaint = Paint().apply {
color = 0x80FFD700.toInt() // 半透明黄色
style = Paint.Style.FILL
}
private val highlightStrokePaint = Paint().apply {
color = 0xFFFFA500.toInt() // 橙色边框
style = Paint.Style.STROKE
strokeWidth = 2f
}
private var currentPageHighlights: List<PDFSearchManager.TextPositionInfo> = emptyList()
/**
* 设置当前页面的高亮
*/
fun setCurrentPageHighlights(page: Int) {
currentPageHighlights = searchManager.getHighlightsForPage(page)
pdfView.invalidate()
}
/**
* 清除当前高亮
*/
fun clearCurrentHighlights() {
currentPageHighlights = emptyList()
pdfView.invalidate()
}
override fun onLayerDrawn(canvas: Canvas, pageWidth: Float, pageHeight: Float, displayedPage: Int) {
// 只绘制当前显示页面的高亮
if (displayedPage != searchManager.currentHighlightPage) {
searchManager.currentHighlightPage = displayedPage
setCurrentPageHighlights(displayedPage + 1) // PDFView 页面从0开始我们的搜索从1开始
}
// 绘制高亮区域
currentPageHighlights.forEach { position ->
try {
val rect = position.getRelativeRect()
val absoluteRect = RectF(
rect.left * pageWidth,
rect.top * pageHeight,
rect.right * pageWidth,
rect.bottom * pageHeight
)
// 可选:添加小的边距让高亮更明显
val margin = 1f
val paddedRect = RectF(
absoluteRect.left - margin,
absoluteRect.top - margin,
absoluteRect.right + margin,
absoluteRect.bottom + margin
)
// 绘制半透明高亮
canvas.drawRect(paddedRect, highlightPaint)
// 绘制边框
canvas.drawRect(paddedRect, highlightStrokePaint)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

View File

@ -0,0 +1,332 @@
package com.all.pdfreader.pro.app.util
import android.util.Log
import com.github.barteksc.pdfviewer.PDFView
import com.tom_roush.pdfbox.pdmodel.PDDocument
import com.tom_roush.pdfbox.pdmodel.PDPage
import com.tom_roush.pdfbox.text.PDFTextStripper
import com.tom_roush.pdfbox.text.TextPosition
import java.io.File
class PDFSearchManager(private val pdfView: PDFView) {
private var currentSearchResults: List<TextSearchResult> = emptyList()
var currentHighlightPage = -1
private var cachedDoc: PDDocument? = null // 缓存已打开的文档
private var cachedPath: String? = null // 记录缓存对应的文件路径
var currentSearchText: String? = null//记录当前的搜索文字
var hasSearched: Boolean = false//记录当前是否进行过搜索
data class TextSearchResult(
val pageNumber: Int,
val text: String,
val positions: List<TextPositionInfo>
)
data class TextPositionInfo(
val text: String, // 当前字符或文本片段
val x: Float, // 字符左下角的 X 坐标PDF页面坐标系
val y: Float, // 字符基线baseline在页面上的 Y 坐标PDF页面坐标系
val width: Float, // 字符宽度
val height: Float, // 字符高度
val pageWidth: Float, // 当前 PDF 页宽
val pageHeight: Float // 当前 PDF 页高
) {
/**
* 将字符位置信息转换为 PDFView 可用的相对矩形 (RectF)
*
* @return RectF 返回一个相对页面比例的矩形左上角和右下角的值在 0~1 范围
*
* 说明
* - PDFBox y 是字符基线 (baseline)不是顶部所以顶部需要计算为 y - height如果不这样那选中的位子会在文字的正下方
* - RectF left/top/right/bottom 坐标是相对于页面尺寸的比例
* - 添加了 padding使高亮框稍微大于文字避免高亮框太紧
*/
fun getRelativeRect(): android.graphics.RectF {
// padding用于微调高亮框大小按页面比例计算
val padding = 2f / pageHeight
// 计算相对页面坐标
val left = x / pageWidth - padding // 左边界
val top = (y - height) / pageHeight - padding // 顶部边界(基线减去字符高度,再减去 margin
val right = (x + width) / pageWidth + padding // 右边界
val bottom = y / pageHeight + padding // 底部边界(基线加上 margin
// 返回相对页面坐标的矩形
return android.graphics.RectF(left, top, right, bottom)
}
}
/**
* 获取 PDDocument若已缓存则直接返回否则加载并缓存
* @param file 要加载的 PDF 文件
* @param onLoaded 加载成功时回调 PDDocument 参数
* @param onError 加载失败时回调可选
*/
fun getDocument(
file: File,
onLoaded: ((PDDocument) -> Unit)? = null,
onError: ((Exception) -> Unit)? = null
): PDDocument {
if (cachedDoc == null || cachedPath != file.absolutePath) {
try {
// 如果是新的文件,则关闭旧文档
cachedDoc?.close()
cachedDoc = PDDocument.load(file)
cachedPath = file.absolutePath
// 通知加载成功
onLoaded?.invoke(cachedDoc!!)
} catch (e: Exception) {
onError?.invoke(e)
throw e
}
} else {
// 已缓存文档,仍可调用回调
onLoaded?.invoke(cachedDoc!!)
}
return cachedDoc!!
}
fun getCachedDoc(): PDDocument? {
return cachedDoc
}
/**
* 释放 PDF 资源
*/
fun closeCachedDocument() {
try {
cachedDoc?.close()
} catch (e: Exception) {
e.printStackTrace()
} finally {
cachedDoc = null
cachedPath = null
}
}
/**
* 主搜索方法
* @param pdfFile 要搜索的 PDF 文件
* @param key 搜索关键词
* @param targetPage 可选参数指定页码 0 开始如果为 null 则搜索整本
*/
fun searchText(pdfFile: File, key: String, targetPage: Int? = null): List<TextSearchResult> {
Log.d("ocean", "searchText pdfFile->$pdfFile")
Log.d("ocean", "searchText key->$key")
Log.d("ocean", "searchText targetPage->$targetPage")
val results = mutableListOf<TextSearchResult>()
try {
val doc = getDocument(pdfFile) // 这里不再每次重新 load
val startPage = targetPage ?: 0
val endPage = targetPage ?: (doc.numberOfPages - 1)
for (pageIndex in startPage..endPage) {
val page = doc.getPage(pageIndex)
val pageResults = searchInPage(doc, pageIndex, key, page)
results.add(pageResults)
}
} catch (e: Exception) {
e.printStackTrace()
}
currentSearchResults = results
return results
}
private fun searchInPage(
document: PDDocument,
pageIndex: Int,
searchText: String,
page: PDPage
): TextSearchResult {
val positions = mutableListOf<TextPositionInfo>()
val searchTextLower = searchText.toLowerCase()
val pageWidth = page.mediaBox.width
val pageHeight = page.mediaBox.height
val textStripper = object : PDFTextStripper() {
private val currentPagePositions = mutableListOf<TextPositionInfo>()
override fun writeString(text: String, textPositions: List<TextPosition>) {
// 收集文本位置信息
for (tp in textPositions) {
val positionInfo = TextPositionInfo(
text = tp.unicode,
x = tp.xDirAdj,
y = tp.yDirAdj,
width = tp.widthDirAdj,
height = tp.heightDir,
pageWidth = pageWidth,
pageHeight = pageHeight
)
currentPagePositions.add(positionInfo)
}
super.writeString(text, textPositions)
}
override fun endPage(page: PDPage?) {
// 在页面结束时进行搜索匹配
val matchedPositions = findTextMatches(currentPagePositions, searchTextLower)
if (matchedPositions.isNotEmpty()) {
positions.addAll(matchedPositions)
}
currentPagePositions.clear()
super.endPage(page)
}
}
textStripper.startPage = pageIndex + 1
textStripper.endPage = pageIndex + 1
try {
textStripper.getText(document)
} catch (e: Exception) {
e.printStackTrace()
}
return TextSearchResult(
pageNumber = pageIndex + 1,
text = searchText,
positions = positions
)
}
private fun findTextMatches(
positions: List<TextPositionInfo>,
searchText: String
): List<TextPositionInfo> {
val matches = mutableListOf<TextPositionInfo>()
val fullText = positions.joinToString("") { it.text }.toLowerCase()
var index = 0
while (index < fullText.length) {
val matchIndex = fullText.indexOf(searchText, index)
if (matchIndex == -1) break
// 精确查找匹配的文本段在positions中的范围
var currentLength = 0
val matchedPositions = mutableListOf<TextPositionInfo>()
for (pos in positions) {
val positionStart = currentLength
val positionEnd = currentLength + pos.text.length
// 精确检查:这个位置是否在当前匹配范围内
if (positionStart < matchIndex + searchText.length &&
positionEnd > matchIndex
) {
matchedPositions.add(pos)
}
currentLength += pos.text.length
// 如果已经超过当前匹配范围,立即停止
if (currentLength >= matchIndex + searchText.length) break
}
// 计算合并后的矩形区域
if (matchedPositions.isNotEmpty()) {
val mergedPosition = mergePositions(matchedPositions, searchText)
matches.add(mergedPosition)
}
index = matchIndex + searchText.length
}
Log.d("PDF_DEBUG", "找到 ${matches.size} 个独立匹配")
return matches
}
private fun mergePositions(
positions: List<TextPositionInfo>,
originalText: String
): TextPositionInfo {
val first = positions.first()
val minX = positions.minByOrNull { it.x }?.x ?: first.x
val maxX = positions.maxByOrNull { it.x + it.width }?.let { it.x + it.width }
?: (first.x + first.width)
val minY = positions.minByOrNull { it.y }?.y ?: first.y
val maxY = positions.maxByOrNull { it.y + it.height }?.let { it.y + it.height }
?: (first.y + first.height)
return TextPositionInfo(
text = originalText,
x = minX,
y = minY,
width = maxX - minX,
height = maxY - minY,
pageWidth = first.pageWidth,
pageHeight = first.pageHeight
)
}
/**
* 获取当前页面的高亮区域
*/
fun getHighlightsForPage(page: Int): List<TextPositionInfo> {
return currentSearchResults
.find { it.pageNumber == page }
?.positions ?: emptyList()
}
/**
* 清除所有高亮
*/
fun clearHighlights() {
currentSearchResults = emptyList()
pdfView.invalidate()
currentSearchText = null
hasSearched = false
}
/**
* 判断当前页是否有可搜索文字
* @param doc 当前 PDF 文档
* @param pageIndex 当前页索引
* @param minChars 阈值认为一页有文字的最少字符数
*/
fun pageHasText(doc: PDDocument, pageIndex: Int, minChars: Int = 10): Boolean {
var charCount = 0
val stripper = object : PDFTextStripper() {
override fun processTextPosition(text: TextPosition) {
Log.d(
"ocean",
"processTextPosition text->$text width->${text.width}height->${text.height}"
)
//太小的文字当没有
if (text.width > 1 && text.height > 2) {
charCount++
}
// 如果超过阈值,直接停止
if (charCount >= minChars) {
throw StopParsingException()
}
}
}
return try {
stripper.startPage = pageIndex + 1
stripper.endPage = pageIndex + 1
stripper.getText(doc)
// 解析完,如果字符数达不到阈值就返回 false
charCount >= minChars
} catch (e: StopParsingException) {
// 遇到足够字符,认为有文字
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
private class StopParsingException : RuntimeException()
}

View File

@ -39,6 +39,56 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/searchTextLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@drawable/dr_item_img_frame"
android:gravity="center_vertical"
android:visibility="gone">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchEdit"
style="@style/TextViewFont_PopRegular"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:background="@null"
android:hint="@string/search_hint"
android:textSize="14sp" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp">
<ImageView
android:id="@+id/searchIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/search" />
<ImageView
android:id="@+id/deleteIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/delete_cha_icon"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
style="@style/TextViewFont_PopMedium" style="@style/TextViewFont_PopMedium"
@ -53,10 +103,25 @@
android:textSize="16sp" /> android:textSize="16sp" />
<LinearLayout <LinearLayout
android:id="@+id/moreBtn" android:id="@+id/searchTextBtn"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/search" />
</LinearLayout>
<LinearLayout
android:id="@+id/moreBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:background="@drawable/dr_click_effect_oval_transparent" android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center"> android:gravity="center">
@ -67,6 +132,7 @@
android:src="@drawable/more" /> android:src="@drawable/more" />
</LinearLayout> </LinearLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,74 @@
<?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">
<!-- 搜索栏 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#f5f5f5"
android:orientation="horizontal"
android:padding="8dp">
<EditText
android:id="@+id/searchEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="输入搜索关键词"
android:imeOptions="actionSearch"
android:singleLine="true" />
<Button
android:id="@+id/searchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="搜索" />
<Button
android:id="@+id/clearButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="清除" />
</LinearLayout>
<TextView
android:id="@+id/debugTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- PDF显示区域 -->
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
<!-- 搜索结果列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/searchResultsRecyclerView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffffff" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,34 @@
<?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="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:background="?android:attr/selectableItemBackground">
<TextView
android:id="@+id/pageText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="#333333" />
<TextView
android:id="@+id/matchCount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#666666"
android:layout_marginTop="2dp" />
<TextView
android:id="@+id/previewText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#888888"
android:layout_marginTop="4dp"
android:maxLines="1"
android:ellipsize="end" />
</LinearLayout>