* [修复] 透明主题阅读详情页FAB变黑和书架标签右侧半透明框问题 (@Piktowo) (#665)

Issue #659, #661

* [修复] 透明主题控件点击出现实色框问题,补全 surfaceContainer 系列颜色定义及主题映射 (@Piktowo) (#665) 

Issue #659

* 修复 TOC 自动定位竞态导致章节选取错误,修复书架共享元素返回动画位置错误 (@Piktowo) (#665) 

Issue #664, #647

* [修复] 阅读页背景Bitmap提前recycle导致的崩溃 (@Piktowo) (#665)
This commit is contained in:
Piktowo
2026-03-05 01:23:11 +08:00
committed by GitHub
parent 8956f1d682
commit 2363e95702
19 changed files with 62 additions and 25 deletions
@@ -106,7 +106,7 @@ object ReadBookConfig {
shareConfig = c ?: configList.getOrNull(5) ?: Config()
}
fun upBg(width: Int, height: Int) {
fun upBg(width: Int, height: Int): Drawable? {
val drawable = durConfig.curBgDrawable(width, height)
if (drawable is BitmapDrawable && drawable.bitmap != null) {
bgMeanColor = drawable.bitmap.getMeanColor()
@@ -115,7 +115,9 @@ object ReadBookConfig {
}
val tmp = bg
bg = drawable
(tmp as? BitmapDrawable)?.bitmap?.recycle()
// 返回旧 Drawable,由调用方在视图更新完成后再 recycle,
// 避免视图仍引用旧 BitmapDrawable 时 bitmap 已被 recycle 导致崩溃
return tmp
}
fun save() {
@@ -660,7 +660,7 @@ class BookInfoActivity :
duration = 400L
addUpdateListener { animation ->
val color = animation.animatedValue as Int
binding.btnRead.setBackgroundColor(color)
binding.btnRead.backgroundTintList = ColorStateList.valueOf(color)
}
}
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.RectF
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
@@ -616,10 +617,13 @@ class ReadView(context: Context, attrs: AttributeSet) :
* 更新背景
*/
fun upBg() {
ReadBookConfig.upBg(width, height)
val oldBg = ReadBookConfig.upBg(width, height)
curPage.upBg()
prevPage.upBg()
nextPage.upBg()
// 所有视图背景更新完成后再 recycle 旧 bitmap
// 防止视图仍持有旧 BitmapDrawable 引用时 bitmap 已被 recycle 导致崩溃
(oldBg as? BitmapDrawable)?.bitmap?.recycle()
}
/**
@@ -110,6 +110,8 @@ import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import java.text.SimpleDateFormat
@@ -229,13 +231,22 @@ fun TocScreen(
var hasAutoScrolled by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(state.items) {
if (!hasAutoScrolled && state.items.isNotEmpty()) {
val targetIndex = state.items.indexOfFirst { it.isDur }
LaunchedEffect(Unit) {
if (!hasAutoScrolled) {
// 等待列表包含当前阅读章节后自动定位,避免在无 isDur 项时提前退出
// 使用 animateScrollToItem 以便用户看到滚动动画,避免误操作
val items = snapshotFlow { state.items }
.filter { list -> list.any { it.isDur } }
.first()
val targetIndex = items.indexOfFirst { it.isDur }
if (targetIndex != -1) {
listState.scrollToItem(
// 等待视口尺寸测量完成,确保 offset 计算正确
snapshotFlow { listState.layoutInfo.viewportEndOffset }
.filter { it > 0 }
.first()
listState.animateScrollToItem(
index = targetIndex,
scrollOffset = -offset
scrollOffset = -(listState.layoutInfo.viewportEndOffset / 4)
)
hasAutoScrolled = true
}
@@ -10,8 +10,10 @@ import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.core.graphics.ColorUtils
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import io.legado.app.R
@@ -22,6 +24,7 @@ import io.legado.app.databinding.FragmentBookshelf1Binding
import io.legado.app.help.config.AppConfig
import io.legado.app.ui.book.group.GroupEditDialog
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.main.bookshelf.BaseBookshelfFragment
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.themeColor
@@ -49,6 +52,7 @@ class BookshelfFragment1() : BaseBookshelfFragment(R.layout.fragment_bookshelf1)
override val groupId: Long get() = selectedGroup?.groupId ?: 0
private lateinit var adapter: TabFragmentPageAdapter
private var tabFadeListener: AppBarLayout.OnOffsetChangedListener? = null
override val books: List<Book>
get() {
@@ -87,17 +91,20 @@ class BookshelfFragment1() : BaseBookshelfFragment(R.layout.fragment_bookshelf1)
}
private fun updateTabFadeEffect() {
binding.titleBar.addOnOffsetChangedListener({ appBarLayout, verticalOffset ->
if (-verticalOffset >= appBarLayout.totalScrollRange) {
val drawable = binding.tabRightFade.background as? GradientDrawable
drawable?.colors = intArrayOf(Color.TRANSPARENT,
requireContext().themeColor(com.google.android.material.R.attr.colorSurfaceContainer))
tabFadeListener?.let { binding.titleBar.removeOnOffsetChangedListener(it) }
val listener = AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
val drawable = binding.tabRightFade.background as? GradientDrawable
val opacity = if (ThemeConfig.enableBlur) ThemeConfig.containerOpacity / 100f else 1f
val baseColor = if (-verticalOffset >= appBarLayout.totalScrollRange) {
requireContext().themeColor(com.google.android.material.R.attr.colorSurfaceContainer)
} else {
val drawable = binding.tabRightFade.background as? GradientDrawable
drawable?.colors = intArrayOf(Color.TRANSPARENT,
requireContext().themeColor(com.google.android.material.R.attr.colorSurface))
requireContext().themeColor(com.google.android.material.R.attr.colorSurface)
}
})
val newAlpha = (Color.alpha(baseColor) * opacity).toInt()
drawable?.colors = intArrayOf(Color.TRANSPARENT, ColorUtils.setAlphaComponent(baseColor, newAlpha))
}
tabFadeListener = listener
binding.titleBar.addOnOffsetChangedListener(listener)
}
private fun showTabSelectionMenu() {
@@ -27,6 +27,7 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) :
payloads: MutableList<Any>
) = binding.run {
if (payloads.isEmpty()) {
cdCover.transitionName = "book_${item.bookUrl}"
tvName.text = item.name
ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
upRefresh(binding, item)
@@ -28,6 +28,7 @@ class BooksAdapterGridCompact(context: Context, private val callBack: CallBack)
payloads: MutableList<Any>
) = binding.run {
if (payloads.isEmpty()) {
cvContent.transitionName = "book_${item.bookUrl}"
tvName.text = item.name
ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
upRefresh(binding, item)
@@ -28,6 +28,7 @@ class BooksAdapterGridCover(context: Context, private val callBack: CallBack) :
payloads: MutableList<Any>
) = binding.run {
if (payloads.isEmpty()) {
cvContent.transitionName = "book_${item.bookUrl}"
tvName.gone()
ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
upRefresh(binding, item)
@@ -35,6 +35,7 @@ class BooksAdapterList(
payloads: MutableList<Any>
) = binding.run {
if (payloads.isEmpty()) {
cdCover.transitionName = "book_${item.bookUrl}"
tvName.text = item.name
tvAuthor.text = item.author
tvRead.text = item.durChapterTitle
@@ -36,6 +36,7 @@ class BooksAdapterListCompact(
payloads: MutableList<Any>
) = binding.run {
if (payloads.isEmpty()) {
cdCover.transitionName = "book_${item.bookUrl}"
tvName.text = item.name
tvAuthor.text =
context.getString(
@@ -64,6 +64,7 @@ class BooksAdapterGrid(context: Context, callBack: CallBack) :
RecyclerView.ViewHolder(binding.root) {
fun onBind(item: Book) = binding.run {
cvContent.transitionName = "book_${item.bookUrl}"
tvName.text = item.name
ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
upRefresh(this, item)
@@ -71,6 +71,7 @@ class BooksAdapterGridCompact(context: Context, callBack: CallBack) :
RecyclerView.ViewHolder(binding.root) {
fun onBind(item: Book) = binding.run {
cvContent.transitionName = "book_${item.bookUrl}"
tvName.text = item.name
ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
upRefresh(this, item)
@@ -71,6 +71,7 @@ class BooksAdapterGridCover(context: Context, callBack: CallBack) :
RecyclerView.ViewHolder(binding.root) {
fun onBind(item: Book) = binding.run {
cvContent.transitionName = "book_${item.bookUrl}"
tvName.gone()
ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
upRefresh(this, item)
@@ -60,6 +60,7 @@ class BooksAdapterList(context: Context, callBack: CallBack) :
RecyclerView.ViewHolder(binding.root) {
fun onBind(item: Book) = binding.run {
cdCover.transitionName = "book_${item.bookUrl}"
tvName.text = item.name
tvAuthor.text = item.author
tvRead.text = item.durChapterTitle
@@ -60,6 +60,7 @@ class BooksAdapterListCompact(context: Context, callBack: CallBack) :
RecyclerView.ViewHolder(binding.root) {
fun onBind(item: Book) = binding.run {
cdCover.transitionName = "book_${item.bookUrl}"
tvName.text = item.name
tvAuthor.text = item.author
tvLast.text = item.latestChapterTitle
@@ -17,7 +17,7 @@ internal object TransparentColorScheme : BaseColorScheme() {
secondaryContainer = Color(0xB0FFFFFF),
onSecondaryContainer = Color(0xFF000000),
tertiary = Color(0xFF000000),
onTertiary = Color(0xFF000000),
onTertiary = Color(0xFFFFFFFF),
tertiaryContainer = Color(0xB0FFFFFF),
onTertiaryContainer = Color(0xFF000000),
error = Color(0xFFBA1A1A),
@@ -51,7 +51,7 @@ internal object TransparentColorScheme : BaseColorScheme() {
surfaceDim = Color(0x1AFFFFFF),
surfaceBright = Color(0x33FFFFFF),
surfaceContainerLowest = Color(0x0DFFFFFF),
surfaceContainerLow = Color(0x8FFFFFFF),
surfaceContainerLow = Color(0x1AFFFFFF),
surfaceContainer = Color(0x33FFFFFF),
surfaceContainerHigh = Color(0x4DFFFFFF),
surfaceContainerHighest = Color(0x66FFFFFF),
@@ -67,7 +67,7 @@ internal object TransparentColorScheme : BaseColorScheme() {
secondaryContainer = Color(0xA0FFFFFF),
onSecondaryContainer = Color(0xFF000000),
tertiary = Color(0x80FFFFFF),
onTertiary = Color(0xFF000000),
onTertiary = Color(0xFFFFFFFF),
tertiaryContainer = Color(0x40FFFFFF),
onTertiaryContainer = Color(0xFF000000),
error = Color(0xFFCF6679),
@@ -101,7 +101,7 @@ internal object TransparentColorScheme : BaseColorScheme() {
surfaceDim = Color(0x1AFFFFFF),
surfaceBright = Color(0x33FFFFFF),
surfaceContainerLowest = Color(0x0DFFFFFF),
surfaceContainerLow = Color(0x8F000000),
surfaceContainerLow = Color(0x1AFFFFFF),
surfaceContainer = Color(0x33FFFFFF),
surfaceContainerHigh = Color(0x4DFFFFFF),
surfaceContainerHighest = Color(0x66FFFFFF),