[新增] 字典可显示图片 (@Luoyacheng)
This commit is contained in:
5 files changed
+187
-5
No files matched your search
@@ -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.base.BaseBottomSheetDialogFragment
|
||||||
import io.legado.app.data.entities.DictRule
|
import io.legado.app.data.entities.DictRule
|
||||||
import io.legado.app.databinding.DialogDictBinding
|
import io.legado.app.databinding.DialogDictBinding
|
||||||
|
import io.legado.app.help.GlideImageGetter
|
||||||
import io.legado.app.utils.gone
|
import io.legado.app.utils.gone
|
||||||
import io.legado.app.utils.setHtml
|
import io.legado.app.utils.setHtml
|
||||||
import io.legado.app.utils.toastOnUi
|
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 viewModel by viewModels<DictViewModel>()
|
||||||
private val binding by viewBinding(DialogDictBinding::bind)
|
private val binding by viewBinding(DialogDictBinding::bind)
|
||||||
|
|
||||||
private var word: String? = null
|
private var word: String? = null
|
||||||
|
private var glideImageGetter: GlideImageGetter? = null
|
||||||
|
|
||||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
binding.tvDict.movementMethod = LinkMovementMethod()
|
binding.tvDict.movementMethod = LinkMovementMethod()
|
||||||
@@ -44,6 +45,12 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) {
|
|||||||
observeDicts()
|
observeDicts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
glideImageGetter?.clear()
|
||||||
|
glideImageGetter = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupTabs() {
|
private fun setupTabs() {
|
||||||
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||||
override fun onTabReselected(tab: TabLayout.Tab) = Unit
|
override fun onTabReselected(tab: TabLayout.Tab) = Unit
|
||||||
@@ -90,6 +97,9 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) {
|
|||||||
} else {
|
} else {
|
||||||
binding.tvDict.visible()
|
binding.tvDict.visible()
|
||||||
binding.tvDict.setHtml(result)
|
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.View
|
||||||
import android.view.textclassifier.TextClassifier
|
import android.view.textclassifier.TextClassifier
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseBottomSheetDialogFragment
|
import io.legado.app.base.BaseBottomSheetDialogFragment
|
||||||
import io.legado.app.databinding.DialogTextViewBinding
|
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")) ?: ""
|
val content = IntentData.get(it.getString("content")) ?: ""
|
||||||
when (it.getString("mode")) {
|
when (it.getString("mode")) {
|
||||||
Mode.MD.name -> viewLifecycleOwner.lifecycleScope.launch {
|
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 markwon: Markwon
|
||||||
val markdown = withContext(IO) {
|
val markdown = withContext(IO) {
|
||||||
markwon = Markwon.builder(requireContext())
|
markwon = Markwon.builder(requireContext())
|
||||||
.usePlugin(GlideImagesPlugin.create(requireContext()))
|
.usePlugin(GlideImagesPlugin.create(Glide.with(requireContext())))
|
||||||
.usePlugin(HtmlPlugin.create())
|
.usePlugin(HtmlPlugin.create())
|
||||||
.usePlugin(TablePlugin.create(requireContext()))
|
.usePlugin(TablePlugin.create(requireContext()))
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import androidx.core.view.marginBottom
|
|||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager.widget.ViewPager
|
import androidx.viewpager.widget.ViewPager
|
||||||
|
import io.legado.app.help.GlideImageGetter
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.lib.theme.TintHelper
|
import io.legado.app.lib.theme.TintHelper
|
||||||
import io.legado.app.utils.canvasrecorder.CanvasRecorder
|
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?) {
|
fun TextView.setTextIfNotEqual(charSequence: CharSequence?) {
|
||||||
if (text != charSequence) {
|
if (text != charSequence) {
|
||||||
text = charSequence
|
text = charSequence
|
||||||
|
|||||||
Reference in new issue
Block a user