[新增] 字典可显示图片 (@Luoyacheng)
This commit is contained in:
@@ -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<String, Drawable>()
|
||||
private val textViewRef = WeakReference(textView)
|
||||
private val targets = mutableSetOf<CustomTarget<*>>()
|
||||
|
||||
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<Drawable> {
|
||||
return object : CustomTarget<Drawable>() {
|
||||
override fun onResourceReady(
|
||||
resource: Drawable,
|
||||
transition: Transition<in Drawable>?
|
||||
) {
|
||||
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<String, Drawable>
|
||||
) : 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<DictViewModel>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user