添加搜索文字功能(基本完成),还可优化。

This commit is contained in:
ocean 2025-10-27 10:17:33 +08:00
parent 8e4c25d54f
commit ed7430e7d2
9 changed files with 469 additions and 81 deletions

View File

@ -3,13 +3,19 @@ package com.all.pdfreader.pro.app.ui.act
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityPdfViewBinding
import com.all.pdfreader.pro.app.model.FileActionEvent
import com.all.pdfreader.pro.app.model.PdfPickerSource
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.ui.dialog.BookmarksDialogFragment
import com.all.pdfreader.pro.app.ui.dialog.ListMoreDialogFragment
@ -17,7 +23,11 @@ import com.all.pdfreader.pro.app.ui.dialog.PdfPasswordProtectionDialogFragment
import com.all.pdfreader.pro.app.ui.dialog.ViewModelDialogFragment
import com.all.pdfreader.pro.app.ui.view.CustomScrollHandle
import com.all.pdfreader.pro.app.util.AppUtils
import com.all.pdfreader.pro.app.util.AppUtils.hideKeyboard
import com.all.pdfreader.pro.app.util.AppUtils.showKeyboard
import com.all.pdfreader.pro.app.util.FileUtils
import com.all.pdfreader.pro.app.util.PDFHighlighter
import com.all.pdfreader.pro.app.util.PDFSearchManager
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
import com.all.pdfreader.pro.app.viewmodel.observeEvent
import com.github.barteksc.pdfviewer.listener.OnErrorListener
@ -26,7 +36,13 @@ import com.github.barteksc.pdfviewer.listener.OnPageChangeListener
import com.github.barteksc.pdfviewer.listener.OnTapListener
import com.gyf.immersionbar.BarHide
import com.gyf.immersionbar.ImmersionBar
import com.tom_roush.pdfbox.pdmodel.PDDocument
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeListener,
@ -51,6 +67,10 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
private val repository = getRepository()
private var isFullScreen = false
private var lastLoadedFilePath: String? = null
private var showSearchTextView = false
private lateinit var searchManager: PDFSearchManager
private lateinit var highlighter: PDFHighlighter
private var pageCheckJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -58,6 +78,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
setContentView(binding.root)
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.white).init()
setupDoubleBackExit()
initObserve()
val filePath = intent.getStringExtra(EXTRA_PDF_HASH)
?: throw IllegalArgumentException("PDF file hash is required")
@ -165,7 +186,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
private fun setupOnClick() {
binding.backBtn.setOnClickListener {
finish()
onBackPressedDispatcher.onBackPressed()
}
binding.eyeCareOverlayBtn.setOnClickListener {
appStore.isEyeCareMode = !appStore.isEyeCareMode
@ -188,6 +209,55 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
binding.moreBtn.setOnClickListener {
ListMoreDialogFragment(pdfDocument.filePath).show(supportFragmentManager, FRAG_TAG)
}
binding.searchTextBtn.setOnClickListener {
showSearchTextView = true
updateSearchState(true)
binding.selectNextBtn.visibility = View.GONE
binding.selectPreviousBtn.visibility = View.GONE
}
binding.searchEdit.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
val query = s?.toString().orEmpty()
binding.deleteIv.visibility = if (query.isEmpty()) View.GONE else View.VISIBLE
binding.searchIv.visibility = if (query.isEmpty()) View.VISIBLE else View.GONE
}
})
binding.searchEdit.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
val searchText = binding.searchEdit.text.toString().trim()
if (searchText.isNotEmpty()) {
performSearch(searchText)
binding.searchEdit.hideKeyboard() // 收起键盘
} else {
showToast(getString(R.string.enter_search_key))
}
true
} else {
false
}
}
binding.deleteIv.setOnClickListener {
binding.searchEdit.apply {
setText("")
clearComposingText()
setSelection(0)
}
clearHighlights()
binding.selectNextBtn.visibility = View.GONE
binding.selectPreviousBtn.visibility = View.GONE
}
binding.selectNextBtn.setOnClickListener {
highlighter.selectNext { id ->
showToast(getString(id))
}
}
binding.selectPreviousBtn.setOnClickListener {
highlighter.selectPrevious { id ->
showToast(getString(id))
}
}
}
private fun loadPdf() {
@ -199,6 +269,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
finish()
return
}
setupPDFSearchManager()//初始化搜索文本需要的类
if (pdfDocument.isPassword) {
showPasswordDialog(file)
} else {
@ -239,10 +310,22 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
lastReadPage = page, readingProgress = (page.toFloat() / pageCount.toFloat()) * 100
)
saveReadingProgress()
}
override fun onDestroy() {
super.onDestroy()
val doc = searchManager.getCachedDoc() ?: return
// 取消上一次检查任务
pageCheckJob?.cancel()
// 启动新的防抖任务
pageCheckJob = lifecycleScope.launch(Dispatchers.IO) {
delay(120) // 防抖 120ms
val hasText = checkPageHasText(doc, page)
if (hasText && searchManager.hasSearched && !searchManager.currentSearchText.isNullOrEmpty()) {
searchTextIng(
File(pdfDocument.filePath),
searchManager.currentSearchText ?: "",
binding.pdfview.currentPage
)
}
}
}
private fun saveReadingProgress() {
@ -276,9 +359,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
onTap(this@PdfViewActivity) // 单击回调
onPageChange(this@PdfViewActivity) // 页面改变回调
scrollHandle(CustomScrollHandle(this@PdfViewActivity)) // 自定义的页数展示
onDraw { canvas, pageWidth, pageHeight, displayedPage ->
}
onDraw(highlighter)
if (appStore.isPageFling) {
pageSnap(true)//页面可在逐页中,可居中展示,非逐页模式,滑动停止后可自动定格在居中位置。
pageFling(true)//逐页
@ -299,6 +380,9 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
}
private fun toggleFullScreen() {
if (showSearchTextView) {//如果是搜索文本时,不全屏
return
}
isFullScreen = !isFullScreen
updateStatusAndNavigationLayout(isFullScreen)
if (isFullScreen) {
@ -337,4 +421,129 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(navColor).init()
}
private fun setupDoubleBackExit() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (showSearchTextView) {//如果当前是搜索,则退出搜索
showSearchTextView = false
updateSearchState(false)
clearHighlights()
binding.searchEdit.apply {
setText("")
clearComposingText()
setSelection(0)
}
} else {
isEnabled = false // 解除拦截
onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑
}
}
})
}
private fun updateSearchState(b: Boolean) {
if (b) {
binding.searchTextLayout.visibility = View.VISIBLE
binding.navigationLayout.visibility = View.GONE
binding.searchEdit.showKeyboard()
} else {
binding.searchTextLayout.visibility = View.GONE
binding.navigationLayout.visibility = View.VISIBLE
binding.searchEdit.hideKeyboard()
}
}
private fun setupPDFSearchManager() {
searchManager = PDFSearchManager(binding.pdfview)
highlighter = PDFHighlighter(binding.pdfview, searchManager)
initSearchDocument()
}
private fun initSearchDocument() {
val file = File(pdfDocument.filePath)
lifecycleScope.launch(Dispatchers.IO) {
searchManager.getDocument(file, onLoaded = { doc ->
lifecycleScope.launch(Dispatchers.IO) {
checkPageHasText(doc, binding.pdfview.currentPage)
}
}, onError = { e ->
runOnUiThread {
binding.searchTextBtn.visibility = View.GONE
binding.searchTextBtn.isEnabled = false
}
})
}
}
/**
* 异步检查指定页是否有文字并更新按钮
*/
private suspend fun checkPageHasText(doc: PDDocument, pageIndex: Int): Boolean {
val hasText = searchManager.pageHasText(doc, pageIndex, minChars = 10) // 可调整阈值
withContext(Dispatchers.Main) {
if (hasText) {
binding.searchTextBtn.visibility = View.VISIBLE
binding.searchTextBtn.isEnabled = true
} else {
binding.searchTextBtn.visibility = View.GONE
binding.searchTextBtn.isEnabled = false
}
}
return hasText
}
private fun performSearch(searchText: String) {
val file = File(pdfDocument.filePath)
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) {
if (!results.isEmpty()) {
binding.selectNextBtn.visibility = View.VISIBLE
binding.selectPreviousBtn.visibility = View.VISIBLE
results.firstOrNull()?.let { firstResult ->
onSearchResultClicked(firstResult)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
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
}
private fun clearHighlights() {
searchManager.clearHighlights()
highlighter.clearCurrentHighlights()
}
override fun onDestroy() {
super.onDestroy()
searchManager.closeCachedDocument()
}
}

View File

@ -100,6 +100,15 @@ object AppUtils {
}, 200)
}
fun EditText.hideKeyboard() {
clearFocus()
postDelayed({
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.hideSoftInputFromWindow(windowToken, 0)
}, 200)
}
/**
* 分享文件自动识别 MIME 类型
* @param context 上下文

View File

@ -3,6 +3,7 @@ package com.all.pdfreader.pro.app.util
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import com.all.pdfreader.pro.app.R
import com.github.barteksc.pdfviewer.PDFView
import com.github.barteksc.pdfviewer.listener.OnDrawListener
@ -11,6 +12,13 @@ class PDFHighlighter(
private val searchManager: PDFSearchManager
) : OnDrawListener {
enum class ClickType {
NEXT,
PREVIOUS
}
private var type = ClickType.NEXT
private val highlightPaint = Paint().apply {
color = 0x80FFD700.toInt() // 半透明黄色
style = Paint.Style.FILL
@ -22,13 +30,34 @@ class PDFHighlighter(
strokeWidth = 2f
}
private val selectedPaint = Paint().apply {
color = 0x80FF4500.toInt() // 当前选中高亮,橙红色半透明
style = Paint.Style.FILL
}
private val selectedStrokePaint = Paint().apply {
color = 0xFFFF6347.toInt() // 当前选中边框,亮橙红
style = Paint.Style.STROKE
strokeWidth = 2f
}
private var currentPageHighlights: List<PDFSearchManager.TextPositionInfo> = emptyList()
private var currentSelectedIndex: Int = -1
/**
* 设置当前页面的高亮
*/
fun setCurrentPageHighlights(page: Int) {
currentPageHighlights = searchManager.getHighlightsForPage(page)
currentSelectedIndex = when (type) {
ClickType.NEXT -> {
if (currentPageHighlights.isNotEmpty()) 0 else -1
}
ClickType.PREVIOUS -> {
if (currentPageHighlights.isNotEmpty()) currentPageHighlights.size - 1 else -1
}
}
pdfView.invalidate()
}
@ -37,18 +66,78 @@ class PDFHighlighter(
*/
fun clearCurrentHighlights() {
currentPageHighlights = emptyList()
currentSelectedIndex = -1
pdfView.invalidate()
}
override fun onLayerDrawn(canvas: Canvas, pageWidth: Float, pageHeight: Float, displayedPage: Int) {
// 只绘制当前显示页面的高亮
/**
* 选中下一条高亮
*/
fun selectNext(rid: ((Int) -> Unit)? = null) {
type = ClickType.NEXT
if (currentPageHighlights.isEmpty()) {//当前页没有对应的文字,直接跳转下一页
val nextPage = searchManager.currentHighlightPage + 1
pdfView.jumpTo(nextPage, true)
return
}
if (currentSelectedIndex < currentPageHighlights.size - 1) {
currentSelectedIndex++
//下一页+,需要刷新
pdfView.invalidate()
} else {
// 当前页最后一条,尝试跳到下一页
val nextPage = searchManager.currentHighlightPage + 1
if (nextPage < pdfView.pageCount) {
pdfView.jumpTo(nextPage, true)
currentSelectedIndex = 0
} else {
rid?.invoke(R.string.no_more_results)
}
}
}
/**
* 选中上一条高亮
*/
fun selectPrevious(rid: ((Int) -> Unit)? = null) {
type = ClickType.PREVIOUS
if (currentPageHighlights.isEmpty()) { // 当前页没有对应的文字,直接跳转上一页
val prevPage = searchManager.currentHighlightPage - 1
if (prevPage >= 0) {
pdfView.jumpTo(prevPage, true)
} else {
rid?.invoke(R.string.no_more_results)
}
return
}
if (currentSelectedIndex > 0) {
currentSelectedIndex--
pdfView.invalidate()
} else {
val prevPage = searchManager.currentHighlightPage - 1
if (prevPage >= 0) {
pdfView.jumpTo(prevPage, true)
currentPageHighlights = emptyList()
currentSelectedIndex = -1
} else {
rid?.invoke(R.string.no_more_results)
}
}
}
override fun onLayerDrawn(
canvas: Canvas,
pageWidth: Float,
pageHeight: Float,
displayedPage: Int
) {
if (displayedPage != searchManager.currentHighlightPage) {
searchManager.currentHighlightPage = displayedPage
setCurrentPageHighlights(displayedPage + 1) // PDFView 页面从0开始我们的搜索从1开始
// setCurrentPageHighlights(displayedPage + 1)
}
// 绘制高亮区域
currentPageHighlights.forEach { position ->
currentPageHighlights.forEachIndexed { index, position ->
try {
val rect = position.getRelativeRect()
val absoluteRect = RectF(
@ -58,7 +147,6 @@ class PDFHighlighter(
rect.bottom * pageHeight
)
// 可选:添加小的边距让高亮更明显
val margin = 1f
val paddedRect = RectF(
absoluteRect.left - margin,
@ -67,15 +155,16 @@ class PDFHighlighter(
absoluteRect.bottom + margin
)
// 绘制半透明高亮
canvas.drawRect(paddedRect, highlightPaint)
// 绘制边框
canvas.drawRect(paddedRect, highlightStrokePaint)
if (index == currentSelectedIndex) {
canvas.drawRect(paddedRect, selectedPaint)
canvas.drawRect(paddedRect, selectedStrokePaint)
} else {
canvas.drawRect(paddedRect, highlightPaint)
canvas.drawRect(paddedRect, highlightStrokePaint)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}

View File

@ -198,49 +198,74 @@ class PDFSearchManager(private val pdfView: PDFView) {
)
}
/**
* 在一页的 TextPositionInfo 列表中搜索指定文本并返回每个匹配的高亮区域
*
* 逻辑说明
* 1. 按视觉顺序纵向 y, 横向 x排序字符
* 2. 根据 y 坐标将字符分组为同一行避免跨行高亮
* 3. 对每一行独立进行逐字符匹配
* - 清理不可打印字符零宽空格控制字符等
* - 如果匹配成功则将匹配字符合并为一个矩形区域
*
* @param positions 当前页的所有字符位置信息TextPositionInfo
* @param searchText 要搜索的文本不区分大小写
* @return 匹配的高亮区域列表每个 TextPositionInfo 对象表示一段匹配文字
*/
private fun findTextMatches(
positions: List<TextPositionInfo>,
searchText: String
): List<TextPositionInfo> {
val matches = mutableListOf<TextPositionInfo>()
val fullText = positions.joinToString("") { it.text }.toLowerCase()
val chars = searchText.lowercase().toCharArray()
var index = 0
while (index < fullText.length) {
val matchIndex = fullText.indexOf(searchText, index)
if (matchIndex == -1) break
// 按纵向 y、横向 x 排序,保证字符视觉顺序
val sortedPositions = positions.sortedWith(compareBy({ it.y }, { it.x }))
// 精确查找匹配的文本段在positions中的范围
var currentLength = 0
val matchedPositions = mutableListOf<TextPositionInfo>()
// 按行拆分字符,避免高亮跨行
val lines = mutableListOf<MutableList<TextPositionInfo>>()
var currentLine = mutableListOf<TextPositionInfo>()
var lastY = sortedPositions.firstOrNull()?.y ?: 0f
val lineThreshold = 2f // y 差值小于阈值则视为同一行,可根据字体大小微调
for (pos in positions) {
val positionStart = currentLength
val positionEnd = currentLength + pos.text.length
for (pos in sortedPositions) {
if (currentLine.isNotEmpty() && kotlin.math.abs(pos.y - lastY) > lineThreshold) {
// y 坐标变化超过阈值,说明换行
lines.add(currentLine)
currentLine = mutableListOf()
}
currentLine.add(pos)
lastY = pos.y
}
if (currentLine.isNotEmpty()) lines.add(currentLine)
// 精确检查:这个位置是否在当前匹配范围内
if (positionStart < matchIndex + searchText.length &&
positionEnd > matchIndex
) {
matchedPositions.add(pos)
// 对每一行独立匹配搜索文本
for (line in lines) {
var i = 0
while (i <= line.size - chars.size) {
var matched = true
val matchedPositions = mutableListOf<TextPositionInfo>()
for (j in chars.indices) {
// 清理不可打印字符(零宽空格、控制字符等)
val posChar = line[i + j].text.replace(Regex("\\p{C}"), "").lowercase()
if (posChar != chars[j].toString()) {
matched = false
break
}
matchedPositions.add(line[i + j])
}
currentLength += pos.text.length
// 如果已经超过当前匹配范围,立即停止
if (currentLength >= matchIndex + searchText.length) break
if (matched) {
// 合并匹配字符为矩形区域
matches.add(mergePositions(matchedPositions, searchText))
i += chars.size
} else {
i++
}
}
// 计算合并后的矩形区域
if (matchedPositions.isNotEmpty()) {
val mergedPosition = mergePositions(matchedPositions, searchText)
matches.add(mergedPosition)
}
index = matchIndex + searchText.length
}
Log.d("PDF_DEBUG", "找到 ${matches.size} 个独立匹配")
return matches
}
@ -297,10 +322,6 @@ class PDFSearchManager(private val pdfView: PDFView) {
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++

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M333.1,126.5l-0.7,0.7c-12.3,12.3 -12.3,32.4 0,44.7l339.9,339.9 -340.1,340.1c-12.5,12.5 -12.5,32.9 0,45.4s32.9,12.5 45.4,0L740,535s0.1,-0.1 0.2,-0.1l0.7,-0.7c12.3,-12.3 12.3,-32.4 0,-44.7l-363,-363c-12.4,-12.3 -32.5,-12.3 -44.8,0z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M690.8,126.5l0.7,0.7c12.3,12.3 12.3,32.4 0,44.7l-340,340L691.6,852c12.5,12.5 12.5,32.9 0,45.4s-32.9,12.5 -45.4,0L283.9,535s-0.1,-0.1 -0.2,-0.1l-0.7,-0.7c-12.3,-12.3 -12.3,-32.4 0,-44.7l363,-363c12.4,-12.3 32.5,-12.3 44.8,0z"
android:fillColor="#000000"/>
</vector>

View File

@ -2,6 +2,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:color="@color/line_color"
android:width="0.5dp"/>
android:width="1dp"/>
<corners android:radius="8dp" />
</shape>

View File

@ -42,47 +42,87 @@
<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:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchEdit"
style="@style/TextViewFont_PopRegular"
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:background="@null"
android:hint="@string/search_hint"
android:textSize="14sp" />
android:background="@drawable/dr_item_img_frame"
android:gravity="center_vertical">
<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:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
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"
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/search" />
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:id="@+id/selectPreviousBtn"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center">
<ImageView
android:id="@+id/deleteIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/delete_cha_icon"
android:visibility="gone" />
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/arrow_previous_icon" />
</LinearLayout>
<LinearLayout
android:id="@+id/selectNextBtn"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:layout_marginEnd="16dp"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/arrow_next_icon" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -109,7 +149,7 @@
android:layout_marginStart="8dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center"
android:visibility="gone">
android:visibility="visible">
<ImageView
android:layout_width="24dp"

View File

@ -163,4 +163,6 @@
<string name="no_app_to_open_image">No app found to open image</string>
<string name="failed_to_open_image">Failed to open image</string>
<string name="press_again_to_exit">Press again to exit</string>
<string name="enter_search_key">Please enter the search keyword</string>
<string name="no_more_results">No more results</string>
</resources>