全文搜索界面优化 #262

This commit is contained in:
HapeLee committed 2025-11-09 16:17:32 +08:00
1 parent 55bf83e845
commit 8add267239
4 files changed
+91 -55

No files matched your search

@@ -61,13 +61,7 @@ class SearchContentActivity :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// val bbg = bottomBackground
// val btc = getPrimaryTextColor(ColorUtils.isColorLight(bbg))
// binding.llSearchBaseInfo.setBackgroundColor(bbg)
binding.llSearchBaseInfo.applyNavigationBarPadding() binding.llSearchBaseInfo.applyNavigationBarPadding()
// binding.tvCurrentSearchInfo.setTextColor(btc)
// binding.ivSearchContentTop.setColorFilter(btc)
// binding.ivSearchContentBottom.setColorFilter(btc)
val searchResultList = IntentData.get<List<SearchResult>>("searchResultList") val searchResultList = IntentData.get<List<SearchResult>>("searchResultList")
val position = intent.getIntExtra("searchResultIndex", 0) val position = intent.getIntExtra("searchResultIndex", 0)
val noSearchResult = searchResultList == null val noSearchResult = searchResultList == null
@@ -143,7 +137,6 @@ class SearchContentActivity :
private fun initRecyclerView() { private fun initRecyclerView() {
binding.recyclerView.layoutManager = mLayoutManager binding.recyclerView.layoutManager = mLayoutManager
binding.recyclerView.addItemDecoration(VerticalDivider(this))
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
} }
@@ -1,21 +1,19 @@
package io.legado.app.ui.book.searchContent package io.legado.app.ui.book.searchContent
//import io.legado.app.lib.theme.accentColor
import android.content.Context import android.content.Context
import android.view.ViewGroup import android.view.ViewGroup
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.databinding.ItemSearchListBinding import io.legado.app.databinding.ItemSearchListBinding
import io.legado.app.utils.hexString
import io.legado.app.utils.themeColor import io.legado.app.utils.themeColor
class SearchContentAdapter(context: Context, val callback: Callback) : class SearchContentAdapter(context: Context, val callback: Callback) :
RecyclerAdapter<SearchResult, ItemSearchListBinding>(context) { RecyclerAdapter<SearchResult, ItemSearchListBinding>(context) {
val textColor = context.themeColor(com.google.android.material.R.attr.colorOnSurface).hexString.substring(2) private val textColorInt = context.themeColor(com.google.android.material.R.attr.colorOnSurface)
val accentColor = private val accentColorInt = context.themeColor(androidx.appcompat.R.attr.colorPrimary)
context.themeColor(androidx.appcompat.R.attr.colorPrimary).hexString.substring(2) private val bgColorInt = context.themeColor(com.google.android.material.R.attr.colorSecondaryContainer)
override fun getViewBinding(parent: ViewGroup): ItemSearchListBinding { override fun getViewBinding(parent: ViewGroup): ItemSearchListBinding {
return ItemSearchListBinding.inflate(inflater, parent, false) return ItemSearchListBinding.inflate(inflater, parent, false)
@@ -30,14 +28,19 @@ class SearchContentAdapter(context: Context, val callback: Callback) :
binding.run { binding.run {
val isDur = callback.durChapterIndex() == item.chapterIndex val isDur = callback.durChapterIndex() == item.chapterIndex
if (payloads.isEmpty()) { if (payloads.isEmpty()) {
tvSearchResult.text = item.getHtmlCompat(textColor, accentColor) tvTitle.text = item.getTitleSpannable(textColorInt)
tvSearchResult.paint.isFakeBoldText = isDur tvContent.text = item.getContentSpannable(textColorInt, accentColorInt, bgColorInt)
cdRoot.setCardBackgroundColor(
if (isDur) context.themeColor(com.google.android.material.R.attr.colorSurfaceContainer)
else context.themeColor(com.google.android.material.R.attr.colorSurface)
)
} }
} }
} }
override fun registerListener(holder: ItemViewHolder, binding: ItemSearchListBinding) { override fun registerListener(holder: ItemViewHolder, binding: ItemSearchListBinding) {
holder.itemView.setOnClickListener { binding.cdRoot.setOnClickListener {
getItem(holder.layoutPosition)?.let { getItem(holder.layoutPosition)?.let {
if (it.query.isNotBlank()) callback.openSearchResult(it, holder.layoutPosition) if (it.query.isNotBlank()) callback.openSearchResult(it, holder.layoutPosition)
} }
@@ -1,7 +1,13 @@
package io.legado.app.ui.book.searchContent package io.legado.app.ui.book.searchContent
import android.text.Spanned import android.graphics.Typeface
import androidx.core.text.HtmlCompat import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.text.style.UnderlineSpan
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
data class SearchResult( data class SearchResult(
@@ -17,44 +23,48 @@ data class SearchResult(
val queryIndexInChapter: Int = 0 val queryIndexInChapter: Int = 0
) { ) {
fun getHtmlCompat(textColor: String, accentColor: String): Spanned { fun getTitleSpannable(accentColor: Int): SpannableString {
return if (query.isNotBlank()) { return if (AppConfig.isEInkMode) {
val queryIndexInSurrounding = resultText.indexOf(query) SpannableString(chapterTitle).apply {
val leftString = resultText.substring(0, queryIndexInSurrounding) setSpan(UnderlineSpan(), 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
val rightString =
resultText.substring(queryIndexInSurrounding + query.length, resultText.length)
// 检查是否为墨水屏模式
val html = if (AppConfig.isEInkMode) {
// 墨水屏模式:使用下划线
buildString {
append("<u>${chapterTitle}</u>")
append("<br>")
append(leftString)
append("<u>${query}</u>")
append(rightString)
}
} else {
// 普通模式:使用颜色
buildString {
append(chapterTitle.colorTextForHtml(accentColor))
append("<br>")
append(leftString.colorTextForHtml(textColor))
append(query.colorTextForHtml(accentColor))
append(rightString.colorTextForHtml(textColor))
}
} }
HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
} else { } else {
val html = if (AppConfig.isEInkMode) { SpannableString(chapterTitle).apply {
resultText setSpan(ForegroundColorSpan(accentColor), 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} else {
resultText.colorTextForHtml(textColor)
} }
HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
} }
} }
private fun String.colorTextForHtml(textColor: String) = fun getContentSpannable(textColor: Int, accentColor: Int, bgColor: Int): SpannableStringBuilder {
"<font color=#${textColor}>$this</font>" val spannable = SpannableStringBuilder(resultText)
if (query.isNotBlank()) {
var searchStart = 0
while (true) {
val start = resultText.indexOf(query, searchStart, ignoreCase = true)
if (start == -1) break
val end = start + query.length
if (AppConfig.isEInkMode) {
spannable.setSpan(UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} else {
// 字体加粗
spannable.setSpan(StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// 字体颜色
spannable.setSpan(ForegroundColorSpan(accentColor), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// 背景色
spannable.setSpan(BackgroundColorSpan(bgColor), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
searchStart = end
}
}
// 设置整体字体颜色(正文)
if (!AppConfig.isEInkMode) {
spannable.setSpan(ForegroundColorSpan(textColor), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
return spannable
}
} }
+35 -5
View File
@@ -4,12 +4,42 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:layout_margin="8dp">
android:padding="16dp">
<TextView <com.google.android.material.listitem.ListItemCardView
android:id="@+id/tv_search_result" android:id="@+id/cd_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"
android:checkable="true"
app:cardCornerRadius="16dp">
<LinearLayout
android:orientation="vertical"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="2dp"
android:textColor="?attr/colorOnSurface"
android:textStyle="bold"
android:textSize="14sp"/>
<TextView
android:id="@+id/tvContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnSurface"
android:textSize="13sp"
android:lineSpacingExtra="2dp"/>
</LinearLayout>
</com.google.android.material.listitem.ListItemCardView>
</LinearLayout> </LinearLayout>