diff --git a/app/src/main/java/io/legado/app/help/GlideImageGetter.kt b/app/src/main/java/io/legado/app/help/GlideImageGetter.kt new file mode 100644 index 000000000..6cb1cea59 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/GlideImageGetter.kt @@ -0,0 +1,129 @@ +package io.legado.app.help + +import android.content.Context +import android.graphics.ColorFilter +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable +import android.os.Build +import android.text.Html +import android.widget.TextView +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import java.lang.ref.WeakReference + +class GlideImageGetter(private val context: Context, textView: TextView, private val originalHtml: String) : + Html.ImageGetter { + companion object { + fun create(context: Context, textView: TextView, html: String): GlideImageGetter { + return GlideImageGetter(context, textView, html) + } + private fun createEmptyDrawable(): Drawable { + return object : Drawable() { + override fun draw(canvas: android.graphics.Canvas) = Unit + override fun setAlpha(alpha: Int) = Unit + override fun setColorFilter(colorFilter: ColorFilter?) = Unit + @Deprecated("Deprecated in Java") + override fun getOpacity(): Int = PixelFormat.TRANSPARENT + } + } + } + + private val loadedDrawables = mutableMapOf() + private val textViewRef = WeakReference(textView) + private val targets = mutableSetOf>() + + override fun getDrawable(source: String?): Drawable { + if (source.isNullOrBlank()) { + return createEmptyDrawable() + } + val urlDrawable = GlideUrlDrawable() + val target = createImageTarget(urlDrawable, source) + targets.add(target) + Glide.with(context) + .load(source) + .into(target) + return urlDrawable + } + + private fun createImageTarget( + urlDrawable: GlideUrlDrawable, + source: String + ): CustomTarget { + return object : CustomTarget() { + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + targets.remove(this) + val textView = textViewRef.get() ?: return + val availableWidth = textView.width - textView.paddingLeft - textView.paddingRight + val maxWidth = availableWidth.takeIf { it > 0 } ?: 700 + val drawableWidth = resource.intrinsicWidth.coerceAtLeast(1) + val drawableHeight = resource.intrinsicHeight.coerceAtLeast(1) + val scale = if (drawableWidth > maxWidth) { + maxWidth.toFloat() / drawableWidth + } else { + 1f + } + val width = (drawableWidth * scale).toInt() + val height = (drawableHeight * scale).toInt() + resource.setBounds(0, 0, width, height) + urlDrawable.setDrawable(resource) + loadedDrawables[source] = urlDrawable + refreshTextView() + } + + override fun onLoadCleared(placeholder: Drawable?) { + targets.remove(this) + urlDrawable.setDrawable(placeholder) + refreshTextView() + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + targets.remove(this) + val placeholder = errorDrawable ?: createEmptyDrawable() + urlDrawable.setDrawable(placeholder) + loadedDrawables[source] = urlDrawable + refreshTextView() + } + + override fun onLoadStarted(placeholder: Drawable?) { + urlDrawable.setDrawable(placeholder) + } + } + } + + fun clear() { + targets.forEach { + Glide.with(context).clear(it) + } + targets.clear() + textViewRef.clear() + } + + private fun refreshTextView() { + val textView = textViewRef.get() ?: return + textView.post { + // 创建新的ImageGetter,但使用缓存 + val cachedImageGetter = CachedImageGetter(loadedDrawables) + val newHtml = + Html.fromHtml(originalHtml, Html.FROM_HTML_MODE_COMPACT, cachedImageGetter, null) + textView.text = newHtml + } + } + + private class CachedImageGetter( + private val drawableCache: Map + ) : Html.ImageGetter { + override fun getDrawable(source: String?): Drawable { + if (source.isNullOrBlank()) { + return createEmptyDrawable() + } + drawableCache[source]?.let { return it } + return createEmptyDrawable().apply { + setBounds(0, 0, 1, 1) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/GlideUrlDrawable.kt b/app/src/main/java/io/legado/app/help/GlideUrlDrawable.kt new file mode 100644 index 000000000..923bd3542 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/GlideUrlDrawable.kt @@ -0,0 +1,33 @@ +package io.legado.app.help + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable + +class GlideUrlDrawable(): Drawable() { + private var mDrawable: Drawable? = null + fun setDrawable(drawable: Drawable?) { + this.mDrawable = drawable + drawable?.bounds?.let { bounds -> + setBounds(bounds) + } + invalidateSelf() + } + + override fun draw(canvas: Canvas) { + mDrawable?.draw(canvas) + } + @Deprecated("Deprecated in Java") + override fun getOpacity(): Int { + return PixelFormat.TRANSLUCENT + } + + override fun setAlpha(alpha: Int) { + mDrawable?.alpha = alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + mDrawable?.colorFilter = colorFilter + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/dict/DictDialog.kt b/app/src/main/java/io/legado/app/ui/dict/DictDialog.kt index 3374a17e9..e8ecfaf2f 100644 --- a/app/src/main/java/io/legado/app/ui/dict/DictDialog.kt +++ b/app/src/main/java/io/legado/app/ui/dict/DictDialog.kt @@ -9,6 +9,7 @@ import io.legado.app.R import io.legado.app.base.BaseBottomSheetDialogFragment import io.legado.app.data.entities.DictRule import io.legado.app.databinding.DialogDictBinding +import io.legado.app.help.GlideImageGetter import io.legado.app.utils.gone import io.legado.app.utils.setHtml import io.legado.app.utils.toastOnUi @@ -28,8 +29,8 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) { private val viewModel by viewModels() private val binding by viewBinding(DialogDictBinding::bind) - private var word: String? = null + private var glideImageGetter: GlideImageGetter? = null override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { binding.tvDict.movementMethod = LinkMovementMethod() @@ -44,6 +45,12 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) { observeDicts() } + override fun onDestroyView() { + glideImageGetter?.clear() + glideImageGetter = null + super.onDestroyView() + } + private fun setupTabs() { binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabReselected(tab: TabLayout.Tab) = Unit @@ -90,6 +97,9 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) { } else { binding.tvDict.visible() binding.tvDict.setHtml(result) + glideImageGetter?.clear() + glideImageGetter = GlideImageGetter.create(requireContext(), binding.tvDict, result) + binding.tvDict.setHtml(result, glideImageGetter) } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt b/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt index 794aaa6a4..aea88d19a 100644 --- a/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import android.view.textclassifier.TextClassifier import androidx.lifecycle.lifecycleScope +import com.bumptech.glide.Glide import io.legado.app.R import io.legado.app.base.BaseBottomSheetDialogFragment import io.legado.app.databinding.DialogTextViewBinding @@ -66,13 +67,11 @@ class TextDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_text_view) { val content = IntentData.get(it.getString("content")) ?: "" when (it.getString("mode")) { Mode.MD.name -> viewLifecycleOwner.lifecycleScope.launch { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - binding.textView.setTextClassifier(TextClassifier.NO_OP) - } + binding.textView.setTextClassifier(TextClassifier.NO_OP) val markwon: Markwon val markdown = withContext(IO) { markwon = Markwon.builder(requireContext()) - .usePlugin(GlideImagesPlugin.create(requireContext())) + .usePlugin(GlideImagesPlugin.create(Glide.with(requireContext()))) .usePlugin(HtmlPlugin.create()) .usePlugin(TablePlugin.create(requireContext())) .build() diff --git a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt index 5a6b4a84d..2ee4c9711 100644 --- a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt @@ -39,6 +39,7 @@ import androidx.core.view.marginBottom import androidx.core.view.updateLayoutParams import androidx.recyclerview.widget.RecyclerView import androidx.viewpager.widget.ViewPager +import io.legado.app.help.GlideImageGetter import io.legado.app.help.config.AppConfig import io.legado.app.lib.theme.TintHelper import io.legado.app.utils.canvasrecorder.CanvasRecorder @@ -234,6 +235,16 @@ fun TextView.setHtml(html: String) { } } +@SuppressLint("ObsoleteSdkInt") +fun TextView.setHtml(html: String, imageGetter: GlideImageGetter?) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + text = Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT, imageGetter, null) + } else { + @Suppress("DEPRECATION") + text = Html.fromHtml(html, imageGetter, null) + } +} + fun TextView.setTextIfNotEqual(charSequence: CharSequence?) { if (text != charSequence) { text = charSequence