¿Cómo mostrar HTML en Textview junto con imágenes en Android?

Hay algunas situaciones en las que se necesita mostrar texto con formato enriquecido en la aplicación, como para una aplicación de blog o una aplicación como Quora, pero la función incorporada de Android no permite mostrar imágenes en línea de forma predeterminada, además, muestran la fea línea azul al a la izquierda de cualquier cosa dentro de la etiqueta blockquote. Aquí está la solución simple para mostrar HTML en TextView junto con imágenes en Android . Tenga en cuenta que vamos a implementar este proyecto utilizando el lenguaje Kotlin en Android. A continuación se muestra una captura de pantalla de demostración de la aplicación.

demo

requisitos previos

Acercarse

Paso 1: Crear un nuevo Proyecto

Para crear un nuevo proyecto en Android Studio, consulte . Tenga en cuenta que seleccione Kotlin como lenguaje de programación.

Paso 2: Configuración del proyecto antes de codificar

  • Agregue algunos colores en el archivo colors.xml . Los colores aquí son para el estilo blockquote. Usted es libre de elegir diferentes colores.
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#0F9D58</color>
    <color name="colorPrimaryDark">#0F9D58</color>
    <color name="colorAccent">#03DAC5</color>
    <color name="Grey">#878585</color>
</resources>

// Picasso library to downloading images 
implementation 'com.squareup.picasso:picasso:2.71828'
// Coroutines dependency to put the downloading 
process in background thread implementation 
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'

Paso 3: trabajar con el archivo activity_main.xml

En el siguiente archivo activity_main.xml hemos agregado los siguientes widgets:

  • EditText donde el usuario ingresará el texto HTML,
  • Botón para activar un evento para mostrar texto HTML,
  • ScrollView para un desplazamiento suave,
  • TextView para mostrar el HTML después de procesar la entrada.

actividad_principal.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">
  
  
    <!-- Edittext to take input for this sample app,
          for real app you will not need this -->
    <EditText
        android:id="@+id/editor"
        android:layout_width="0dp"
        android:layout_height="40dp"
        app:layout_constraintEnd_toStartOf="@+id/display_html"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
  
    <!-- Button to trigger an event to display html/Rich text-->
    <Button
        android:id="@+id/display_html"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:text="Display Html"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
  
    <!-- Scroll View for smooth scrolling -->
    <ScrollView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editor">
  
        <!-- Text View in which you will display the html after 
             processing the input-->
        <TextView
            android:id="@+id/html_viewer"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </TextView>
          
    </ScrollView>
  
</androidx.constraintlayout.widget.ConstraintLayout>

Interfaz de usuario de salida:

Paso 4: Cree una clase Kotlin ImageGetter.kt

Cree una clase que descargue imágenes contenidas en la etiqueta img . A continuación se muestra el archivo ImageGetter.kt completo. Comprenda el código completo consultando los comentarios correspondientes dentro del código. 

ImageGetter.kt

import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.text.Html
import android.widget.TextView
import com.squareup.picasso.Picasso
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
  
// Class to download Images which extends [Html.ImageGetter]
class ImageGetter(
    private val res: Resources,
    private val htmlTextView: TextView
) : Html.ImageGetter {
  
    // Function needs to overridden when extending [Html.ImageGetter] ,
    // which will download the image
    override fun getDrawable(url: String): Drawable {
        val holder = BitmapDrawablePlaceHolder(res, null)
  
        // Coroutine Scope to download image in Background
        GlobalScope.launch(Dispatchers.IO) {
            runCatching {
  
                // downloading image in bitmap format using [Picasso] Library
                val bitmap = Picasso.get().load(url).get()
                val drawable = BitmapDrawable(res, bitmap)
  
                // To make sure Images don't go out of screen , Setting width less
                // than screen width, You can change image size if you want
                val width = getScreenWidth() - 150
  
                // Images may stretch out if you will only resize width,
                // hence resize height to according to aspect ratio
                val aspectRatio: Float =
                    (drawable.intrinsicWidth.toFloat()) / (drawable.intrinsicHeight.toFloat())
                val height = width / aspectRatio
                drawable.setBounds(10, 20, width, height.toInt())
                holder.setDrawable(drawable)
                holder.setBounds(10, 20, width, height.toInt())
                withContext(Dispatchers.Main) {
                    htmlTextView.text = htmlTextView.text
                }
            }
        }
        return holder
    }
  
    // Actually Putting images
    internal class BitmapDrawablePlaceHolder(res: Resources, bitmap: Bitmap?) :
        BitmapDrawable(res, bitmap) {
        private var drawable: Drawable? = null
  
        override fun draw(canvas: Canvas) {
            drawable?.run { draw(canvas) }
        }
  
        fun setDrawable(drawable: Drawable) {
            this.drawable = drawable
        }
    }
  
    // Function to get screenWidth used above
    fun getScreenWidth() =
        Resources.getSystem().displayMetrics.widthPixels
}

Nota : Uno puede cambiar la altura y el ancho de la imagen de acuerdo a su voluntad en la función.

Paso 5: trabajar con el archivo MainActivity.kt

  • Crea un click-Listener para el botón dentro del método.

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        // Click Listener to listener button click events.
        // display_html is button id from activity_main.xml 
        // You don't need to get reference to it because kotlin 
        // provides synthetic import and 
        // We are gonna use for the same for rest of Views
        display_html.setOnClickListener {
            if (editor.text.isNotEmpty()) {
                displayHtml(editor.text.toString())
            }
        }
    }
}
  • Cree una función a la que acaba de llamar dentro del oyente de clics.

Nota: Kotlin tiene importación sintética, por lo que no fue necesario almacenar la referencia a las vistas en una variable y vamos a hacer lo mismo con todas las demás vistas.

Kotlin

private fun displayHtml(html: String) {
    
      // Creating object of ImageGetter class you just created
      val imageGetter = ImageGetter(resources, html_viewer)
       
      // Using Html framework to parse html
      val styledText=HtmlCompat.fromHtml(html, 
                                         HtmlCompat.FROM_HTML_MODE_LEGACY,
                                         imageGetter,null)
         
      // to enable image/link clicking
      html_viewer.movementMethod = LinkMovementMethod.getInstance()
        
      // setting the text after formatting html and downloading and setting images
      html_viewer.text = styledText
  }
  • Entonces, ahora que el trabajo de la imagen está hecho, ahora debería cargar las imágenes. A continuación se muestra el código completo del archivo MainActivity.kt .

Kotlin

import android.os.Bundle
import android.text.method.LinkMovementMethod
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat
import kotlinx.android.synthetic.main.activity_main.*
  
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        // Click Listener to listener button click events.
        // display_html is button id from activity_main.xml 
        // You don't need to get reference to it 
        // because kotlin provides synthetic import and 
        // We are gonna use for the same for rest of Views
        display_html.setOnClickListener {
            if (editor.text.isNotEmpty()) {
                displayHtml(editor.text.toString())
            }
        }
    }
  
    private fun displayHtml(html: String) {
        // Creating object of ImageGetter class you just created
        val imageGetter = ImageGetter(resources, html_viewer)
  
        // Using Html framework to parse html
        val styledText =
            HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY, imageGetter,null)
  
        // to enable image/link clicking
        html_viewer.movementMethod = LinkMovementMethod.getInstance()
  
        // setting the text after formatting html and downloading and setting images
        html_viewer.text = styledText
    }
}
  • Veamos cómo está funcionando ahora. Usar HTML de otra página web. Aquí hemos utilizado la siguiente página web del artículo de GFG . Obtenga el archivo de string HTML desde aquí .

Nota : Recuerde que ingrese el texto HTML dentro de EditText y luego haga clic en el botón «MOSTRAR HTML»

Producción

ouput screen

Las imágenes ahora se están cargando, pero se puede ver, para la parte de la cita en bloque, hay una línea azul simple y fea. Ahora vamos a arreglar eso. 

Paso 6: Cree una clase de Kotlin QuoteSpanClass.kt

Cree una clase de Kotlin llamada QuoteSpanClass y agregue el siguiente código a este archivo.

QuoteSpanClass.kt

import android.graphics.Canvas
import android.graphics.Paint
import android.text.Layout
import android.text.style.LeadingMarginSpan
import android.text.style.LineBackgroundSpan
  
class QuoteSpanClass(
    private val backgroundColor: Int,
    private val stripeColor: Int,
    private val stripeWidth: Float,
    private val gap: Float
) : LeadingMarginSpan, LineBackgroundSpan {
      
    // Margin for the block quote tag
    override fun getLeadingMargin(first: Boolean): Int {
        return (stripeWidth + gap).toInt()
    }
  
    // this function draws the margin.
    override fun drawLeadingMargin(
        c: Canvas,
        p: Paint,
        x: Int,
        dir: Int,
        top: Int,
        baseline: Int,
        bottom: Int,
        text: CharSequence,
        start: Int,
        end: Int,
        first: Boolean,
        layout: Layout
    ) {
  
        val style = p.style
        val paintColor = p.color
        p.style = Paint.Style.FILL
        p.color = stripeColor
  
        // Creating margin according to color and stripewidth it recieves
        // Press CTRL+Q on function name to read more
        c.drawRect(x.toFloat(), top.toFloat(), x + dir * stripeWidth, bottom.toFloat(), p)
        p.style = style
        p.color = paintColor
    }
  
    override fun drawBackground(
        c: Canvas,
        p: Paint,
        left: Int,
        right: Int,
        top: Int,
        baseline: Int,
        bottom: Int,
        text: CharSequence,
        start: Int,
        end: Int,
        lnum: Int
    ) {
        val paintColor = p.color
        p.color = backgroundColor
  
        // It draws the background on which blockquote text is written
        c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), p)
        p.color = paintColor
    }
}

Paso 7: trabajar con el archivo MainActivity.kt

  • Dado que se crea QuoteSpanClass , es hora de usar esta clase en MainActivity.kt , pero antes necesita crear una función para analizar etiquetas de comillas en bloque y dibujar el margen y el fondo usando QuoteSpanClass.

MainActivity.kt

private fun replaceQuoteSpans(spannable: Spannable) 
{
  val quoteSpans: Array<QuoteSpan> = 
      spannable.getSpans(0, spannable.length - 1, QuoteSpan::class.java)
  
  for (quoteSpan in quoteSpans) 
  {
    val start: Int = spannable.getSpanStart(quoteSpan)
    val end: Int = spannable.getSpanEnd(quoteSpan)
    val flags: Int = spannable.getSpanFlags(quoteSpan)
    spannable.removeSpan(quoteSpan)
       spannable.setSpan(
               QuoteSpanClass(
                    // background color
                    ContextCompat.getColor(this, R.color.colorPrimary),  
                    // strip color
                    ContextCompat.getColor(this, R.color.colorAccent),
                    // strip width
                    10F, 50F
                ),
                start, end, flags
            )
        }   
  }
  • Y finalmente, llame a esta función desde la función antes de configurar el texto para html_viewer. A continuación se muestra el código completo del archivo MainActivity.kt .

Kotlin

import android.os.Bundle
import android.text.Spannable
import android.text.method.LinkMovementMethod
import android.text.style.QuoteSpan
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import kotlinx.android.synthetic.main.activity_main.*
  
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        // Click Listener to listener button click events.
        // display_html is button id from activity_main.xml 
        // You don't need to get reference to it
        // because kotlin provides synthetic import and
        // We are gonna use for the same for rest of Views
        display_html.setOnClickListener {
            if (editor.text.isNotEmpty()) {
                displayHtml(editor.text.toString())
            }
        }
    }
  
    private fun displayHtml(html: String) {
        // Creating object of ImageGetter class you just created
        val imageGetter = ImageGetter(resources, html_viewer)
  
        // Using Html framework to parse html
        val styledText =
            HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY, imageGetter,null)
  
        replaceQuoteSpans(styledText as Spannable)
  
        // setting the text after formatting html and downloading and setting images
        html_viewer.text = styledText
  
        // to enable image/link clicking
        html_viewer.movementMethod = LinkMovementMethod.getInstance()
  
    }
  
    private fun replaceQuoteSpans(spannable: Spannable)
    {
        val quoteSpans: Array<QuoteSpan> =
            spannable.getSpans(0, spannable.length - 1, QuoteSpan::class.java)
  
        for (quoteSpan in quoteSpans)
        {
            val start: Int = spannable.getSpanStart(quoteSpan)
            val end: Int = spannable.getSpanEnd(quoteSpan)
            val flags: Int = spannable.getSpanFlags(quoteSpan)
            spannable.removeSpan(quoteSpan)
            spannable.setSpan(
                QuoteSpanClass(
                    // background color
                    ContextCompat.getColor(this, R.color.colorPrimary),
                    // strip color
                    ContextCompat.getColor(this, R.color.colorAccent),
                    // strip width
                    10F, 50F
                ),
                start, end, flags
            )
        }
    }
}

Producción

Ahora veamos los cambios en la pantalla de salida.

output screen

Nota: Uno puede cambiar el color de la tira, el color de fondo, el ancho de la tira de su elección en la función replaceQuoteSpans y embellecerlo.

Paso 8: Cree un método ImageClick() en el archivo MainActivity.kt

  • Pero todavía hay un problema . ¿Qué pasa si haces clic en una imagen? No pasa nada a partir de ahora. Necesita agregar algunas líneas más para manejar eso.

Kotlin

// Function to parse image tags and enable click events
fun ImageClick(html: Spannable) 
{
  for (span in html.getSpans(0, html.length, ImageSpan::class.java))
  {
    val flags = html.getSpanFlags(span)
    val start = html.getSpanStart(span)
    val end = html.getSpanEnd(span)
    html.setSpan(object : URLSpan(span.source) {
         override fun onClick(v: View) {
                Log.d(TAG, "onClick: url is ${span.source}")
            }
  }, start, end, flags)
}
  • Y luego llame a esta función desde Function antes de configurar el texto para html_viewer. A continuación se muestra el código completo del archivo MainActivity.kt

Kotlin

import android.os.Bundle
import android.text.Spannable
import android.text.method.LinkMovementMethod
import android.text.style.ImageSpan
import android.text.style.QuoteSpan
import android.text.style.URLSpan
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import kotlinx.android.synthetic.main.activity_main.*
  
private const val TAG="MainActivity"
  
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        // Click Listener to listener button click events.
        // display_html is button id from activity_main.xml 
        // You don't need to get reference to it
        // because kotlin provides synthetic import and
        // We are gonna use for the same for rest of Views
        display_html.setOnClickListener {
            if (editor.text.isNotEmpty()) {
                displayHtml(editor.text.toString())
            }
        }
    }
  
    private fun displayHtml(html: String) {
        // Creating object of ImageGetter class you just created
        val imageGetter = ImageGetter(resources, html_viewer)
  
        // Using Html framework to parse html
        val styledText =
            HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY, imageGetter, null)
  
        replaceQuoteSpans(styledText as Spannable)
        ImageClick(styledText as Spannable)
  
        // setting the text after formatting html and downloading and setting images
        html_viewer.text = styledText
  
        // to enable image/link clicking
        html_viewer.movementMethod = LinkMovementMethod.getInstance()
  
    }
  
    private fun replaceQuoteSpans(spannable: Spannable) {
        val quoteSpans: Array<QuoteSpan> =
            spannable.getSpans(0, spannable.length - 1, QuoteSpan::class.java)
  
        for (quoteSpan in quoteSpans) {
            val start: Int = spannable.getSpanStart(quoteSpan)
            val end: Int = spannable.getSpanEnd(quoteSpan)
            val flags: Int = spannable.getSpanFlags(quoteSpan)
            spannable.removeSpan(quoteSpan)
            spannable.setSpan(
                QuoteSpanClass(
                    // background color
                    ContextCompat.getColor(this, R.color.colorPrimary),
                    // strip color
                    ContextCompat.getColor(this, R.color.colorAccent),
                    // strip width
                    10F, 50F
                ),
                start, end, flags
            )
        }
    }
  
    // Function to parse image tags and enable click events
    fun ImageClick(html: Spannable) {
        for (span in html.getSpans(0, html.length, ImageSpan::class.java)) {
            val flags = html.getSpanFlags(span)
            val start = html.getSpanStart(span)
            val end = html.getSpanEnd(span)
            html.setSpan(object : URLSpan(span.source) {
                override fun onClick(v: View) {
                    Log.d(TAG, "onClick: url is ${span.source}")
                }
            }, start, end, flags)
        }
    }
}
  • Ahora, si hace clic en cualquier imagen y comprueba Logcat , verá que se está registrando la URL de la imagen. Desde allí, puede activar alguna función con esa URL.

Salida: ejecutar en el emulador

Recurso: Obtenga el archivo completo del proyecto aquí .

Publicación traducida automáticamente

Artículo escrito por sunny52525 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *