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.
requisitos previos
- Conocimientos básicos de la biblioteca Coroutines .
- Conocimientos básicos de la biblioteca Picasso .
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>
- Vaya al archivo build.gradle(Module:app) y agregue la siguiente dependencia.
// 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
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.
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