【问题标题】:How to change the default image (place holder) loaded in a TextView with Spanned via Html.fromHtml(text)如何通过 Html.fromHtml(text) 更改在 TextView 中加载的默认图像(占位符)
【发布时间】:2021-02-17 04:47:33
【问题描述】:

我的应用中有一个新闻部分,它从我的网站加载一些新闻,其中一些包含图像,所以我从互联网加载它们。但是在图片没有加载的时候,有一个绿色的方块,只有在图片加载的时候才会消失。

图片未加载:

然后加载图像:

我想让那个绿色方块不可见。

为简单起见,假设我什至不会加载图像,只是想让绿色方块不可见,而不用空文本替换图像标签。

代码:

val exampleText =  "Example <br> <img src=\"https://www.w3schools.com/images/w3schools_green.jpg\" alt=\"W3Schools.com\"> <br> Example"
    tv_body.text = fromHtml(exampleText)

fun fromHtml(html: String?): Spanned? {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
    } else {
        Html.fromHtml(html);
    }
}

有没有办法在不做任何恶作剧的情况下更改默认图像?

我的解决方法:

我为解决这个问题所做的是调整自定义 fromHtml 函数。

private var drawable: Drawable? = null
fun fromHtml(context: Activity?, tv: TextView?, text: String?) {
    if (TextUtils.isEmpty(text) || context == null || tv == null) return

   //Replace all image tags with an empty text
    val noImageText = text!!.replace("<img.*?>".toRegex(), "") 

        //Set the textview text with the imageless html
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            tv.text = Html.fromHtml(noImageText, Html.FROM_HTML_MODE_LEGACY)
        } else {
            tv.text = Html.fromHtml(noImageText)
        }

        Thread {
            //Creating the imageGetter
            val imageGetter = ImageGetter { url ->
                drawable = getImageFromNetwork(url)

                if (drawable != null) {
                    var w = drawable!!.intrinsicWidth
                    var h = drawable!!.intrinsicHeight
                    // Scaling the width and height
                    if (w < h && h > 0) {
                        val scale = 400.0f / h
                        w = (scale * w).toInt()
                        h = (scale * h).toInt()
                    } else if (w > h && w > 0) {
                        val scale = 1000.0f / w
                        w = (scale * w).toInt()
                        h = (scale * h).toInt()
                    }
                    drawable!!.setBounds(0, 0, w, h)
                } else if (drawable == null) {
                    return@ImageGetter null
                }
                drawable!!
            }


            val textWithImage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY, imageGetter, null)
            } else {
                Html.fromHtml(text, imageGetter, null)
            }

            // update runOnUiThread and change the textview text from the textWithoutImage to the textWithImage
            context.runOnUiThread(Runnable { tv.text = textWithImage })
        }.start()
}

private fun getImageFromNetwork(imageUrl: String): Drawable? {
    var myFileUrl: URL? = null
    var drawable: Drawable? = null
    try {
        myFileUrl = URL(imageUrl)
        val conn = myFileUrl
                .openConnection() as HttpURLConnection
        conn.doInput = true
        conn.connect()
        val `is` = conn.inputStream
        drawable = Drawable.createFromStream(`is`, null)
        `is`.close()
    } catch (e: Exception) {
        e.printStackTrace()
        return null
    }
    return drawable
}

所以当我调用它时

  val exampleText =  "Example <br> <img src=\"https://www.w3schools.com/images/w3schools_green.jpg\" alt=\"W3Schools.com\"> <br> Example"
    fromHtml((activity as NewsActivity?), tv_body, exampleText)

它首先显示了这一点:

(因为我用空文本替换了图像标签)

然后,当图像加载时,它会显示:

我仍然认为制作无图像文本更多的是一种解决方法而不是适当的解决方案,我认为可能会有一些更简单的方法,例如:

<style name="LIGHT" parent="Theme.AppCompat.DayNight.DarkActionBar">
    <item name="android:placeHolderDefaultImage">@drawable/invisible</item>

所以绿色方块将是一个不可见的可绘制对象,我不需要设置 html 文本两次,尽管我真的不知道如何更改默认占位符图像。我想我会坚持解决方法

【问题讨论】:

  • 拍摄与其他背景相同的图像。
  • @blackapps 你是什么意思?问题是正方形在图像加载之前就出现了,我想让它不可见。
  • 请说明为什么它显示了两次示例。
  • @blackapps 因为我创建了一个名为 exampleText 的变量,其中包含两次“example”。 val exampleText = "示例
    w3schools.com/images/w3schools_green.jpg\" alt=\"W3Schools.com\">
    示例"
  • 是的,我现在看到了。抱歉,我不知道如何提供帮助。

标签: java android html kotlin spanned


【解决方案1】:

您看到的占位符图像来自 com.android.internal.R.drawable.unknown_image,如果 ImageGetter 返回 null 则设置。来自 Html 中的函数startImg()

private static void startImg(Editable text, Attributes attributes, Html.ImageGetter img) {
    ...
    if (d == null) {
        d = Resources.getSystem().
                getDrawable(com.android.internal.R.drawable.unknown_image);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
    }
    ....
}

因此,在原始代码中的某处,您从 ImageGetter 返回了一个空值。因为未知的图像drawable是硬编码的,所以不能通过样式或主题来触摸它。如果您想解决问题,您可以通过反思来做一些事情。

我建议不要在下载的图像可用之前或之后操作 HTML 文本,我建议包装从 ImageGetter 返回的可绘制对象,以便可以在不直接操作文本的情况下更改图像。最初,包装器将包含占位符图像,稍后,当下载的图像可用时,包装器将包含该图像。

这里是一些展示这种技术的示例代码。占位符是一个可显示的可绘制对象,但它可以是您想要的任何东西。我使用可见的可绘制对象(Html.java 中的默认值,但使用“E”表示“空”)来显示它确实显示并且可以更改。您可以提供一个透明的可绘制对象以不显示任何内容。

MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var tvBody: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tvBody = findViewById(R.id.tv_body)
        val exampleText =
            "Example <br> <img src=\"https://www.w3schools.com/images/w3schools_green.jpg\" alt=\"W3Schools.com\"> <br> Example"
        tvBody.text = fromHtml(exampleText, this)
    }

    private fun fromHtml(html: String?, context: Context): Spanned? {
        // Define the ImageGetter for Html. The default "no image, yet" drawable is
        // R.drawable.placeholder but can be another drawable.
        val imageGetter = Html.ImageGetter { url ->
            val d = ContextCompat.getDrawable(context, R.drawable.placeholder) as BitmapDrawable
            // Simulate a network fetch of the real image we want to display.
            ImageWrapper(d).apply {
                simulateNetworkFetch(context, this, url)
            }
        }
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY, imageGetter, null)
        } else {
            Html.fromHtml(html, imageGetter, null)
        }
    }

    private fun simulateNetworkFetch(context: Context, imageWrapper: ImageWrapper, url: String) {
        GlobalScope.launch {
            Log.d("Applog", "Simulating fetch of $url")
            // Just wait for a busy network to get back to us.
            delay(4000)
            // Get the "downloaded" image and place it in our image wrapper.
            val d = ContextCompat.getDrawable(context, R.drawable.downloaded) as BitmapDrawable
            imageWrapper.setBitmapDrawable(d)
            // Force a remeasure/relayout of the TextView with the new image.
            this@MainActivity.runOnUiThread {
                tvBody.text = tvBody.text
            }
        }
    }

    // Simple wrapper for a BitmapDrawable.
    private class ImageWrapper(d: BitmapDrawable) : Drawable() {
        private lateinit var mBitMapDrawable: BitmapDrawable

        init {
            setBitmapDrawable(d)
        }

        override fun draw(canvas: Canvas) {
            mBitMapDrawable.draw(canvas)
        }

        override fun setAlpha(alpha: Int) {
        }

        override fun setColorFilter(colorFilter: ColorFilter?) {
        }

        override fun getOpacity(): Int {
            return PixelFormat.OPAQUE
        }

        fun setBitmapDrawable(bitmapDrawable: BitmapDrawable) {
            mBitMapDrawable = bitmapDrawable
            mBitMapDrawable.setBounds(
                0,
                0,
                mBitMapDrawable.intrinsicWidth,
                mBitMapDrawable.intrinsicHeight
            )
            setBounds(mBitMapDrawable.bounds)
        }
    }
}

这是它在模拟器中的样子:


示例项目是 here,其中包括对 API 23+ 的 DrawableWrapper 的使用,IMO 更简洁一些。上面的代码也同样有效。不幸的是,DrawableWrapperAppCompat 版本受到限制。

【讨论】:

    猜你喜欢
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    • 2014-05-21
    • 1970-01-01
    • 2014-04-30
    • 2019-08-23
    • 2014-02-23
    • 1970-01-01
    相关资源
    最近更新 更多