Last active
October 31, 2024 11:35
-
-
Save alexzaitsev/060fc607d5a16eeef17276bb57e0914f to your computer and use it in GitHub Desktop.
How to display HTML using Android Compose
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Set this on a textview and then you can potentially open links locally if applicable | |
*/ | |
public class DefaultLinkMovementMethod extends LinkMovementMethod { | |
private OnLinkClickedListener mOnLinkClickedListener; | |
public DefaultLinkMovementMethod(OnLinkClickedListener onLinkClickedListener) { | |
mOnLinkClickedListener = onLinkClickedListener; | |
} | |
public boolean onTouchEvent(TextView widget, android.text.Spannable buffer, android.view.MotionEvent event) { | |
int action = event.getAction(); | |
//http://stackoverflow.com/questions/1697084/handle-textview-link-click-in-my-android-app | |
if (action == MotionEvent.ACTION_UP) { | |
int x = (int) event.getX(); | |
int y = (int) event.getY(); | |
x -= widget.getTotalPaddingLeft(); | |
y -= widget.getTotalPaddingTop(); | |
x += widget.getScrollX(); | |
y += widget.getScrollY(); | |
Layout layout = widget.getLayout(); | |
int line = layout.getLineForVertical(y); | |
int off = layout.getOffsetForHorizontal(line, x); | |
URLSpan[] link = buffer.getSpans(off, off, URLSpan.class); | |
if (link.length != 0) { | |
String url = link[0].getURL(); | |
boolean handled = mOnLinkClickedListener.onLinkClicked(url); | |
if (handled) { | |
return true; | |
} | |
return super.onTouchEvent(widget, buffer, event); | |
} | |
} | |
return super.onTouchEvent(widget, buffer, event); | |
} | |
public interface OnLinkClickedListener { | |
boolean onLinkClicked(String url); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun fromHtml(context: Context, html: String): Spannable = parse(html).apply { | |
removeLinksUnderline() | |
styleBold(context) | |
} | |
private fun parse(html: String): Spannable = | |
(HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT) as Spannable) | |
private fun Spannable.removeLinksUnderline() { | |
for (s in getSpans(0, length, URLSpan::class.java)) { | |
setSpan(object : UnderlineSpan() { | |
override fun updateDrawState(tp: TextPaint) { | |
tp.isUnderlineText = false | |
} | |
}, getSpanStart(s), getSpanEnd(s), 0) | |
} | |
} | |
private fun Spannable.styleBold(context: Context) { | |
val bold = ResourcesCompat.getFont(context, R.font.inter_medium)!! | |
for (s in getSpans(0, length, StyleSpan::class.java)) { | |
if (s.style == Typeface.BOLD) { | |
setSpan(ForegroundColorSpan(Color.BLACK), getSpanStart(s), getSpanEnd(s), 0) | |
setSpan(bold.getTypefaceSpan(), getSpanStart(s), getSpanEnd(s), 0) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private const val LINK_1 = "link_1" | |
private const val LINK_2 = "link_2" | |
private const val SPACING_FIX = 3f | |
@Composable | |
fun HtmlText( | |
modifier: Modifier = Modifier, | |
html: String, | |
textStyle: TextStyle = Typography.body1, | |
onLink1Clicked: (() -> Unit)? = null, | |
onLink2Clicked: (() -> Unit)? = null | |
) { | |
AndroidView( | |
modifier = modifier, | |
update = { it.text = fromHtml(it.context, html) }, | |
factory = { context -> | |
val spacingReady = | |
max(textStyle.lineHeight.value - textStyle.fontSize.value - SPACING_FIX, 0f) | |
val extraSpacing = spToPx(spacingReady.toInt(), context) | |
val gravity = when (textStyle.textAlign) { | |
TextAlign.Center -> Gravity.CENTER | |
TextAlign.End -> Gravity.END | |
else -> Gravity.START | |
} | |
val fontResId = when (textStyle.fontWeight) { | |
FontWeight.Medium -> R.font.inter_medium | |
else -> R.font.inter_regular | |
} | |
val font = ResourcesCompat.getFont(context, fontResId) | |
TextView(context).apply { | |
// general style | |
textSize = textStyle.fontSize.value | |
setLineSpacing(extraSpacing, 1f) | |
setTextColor(textStyle.color.toArgb()) | |
setGravity(gravity) | |
typeface = font | |
// links | |
setLinkTextColor(Primary.toArgb()) | |
movementMethod = DefaultLinkMovementMethod() { link -> | |
when (link) { | |
LINK_1 -> onLink1Clicked?.invoke() | |
LINK_2 -> onLink2Clicked?.invoke() | |
} | |
true | |
} | |
} | |
} | |
) | |
} | |
fun spToPx(sp: Int, context: Context): Float = | |
TypedValue.applyDimension( | |
TypedValue.COMPLEX_UNIT_SP, | |
sp.toFloat(), | |
context.resources.displayMetrics | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun Typeface.getTypefaceSpan(): MetricAffectingSpan = | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | |
typefaceSpanCompatV28(this) | |
} else { | |
CustomTypefaceSpan(this) | |
} | |
@TargetApi(Build.VERSION_CODES.P) | |
private fun typefaceSpanCompatV28(typeface: Typeface) = TypefaceSpan(typeface) | |
private class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() { | |
override fun updateDrawState(paint: TextPaint) { | |
paint.typeface = typeface | |
} | |
override fun updateMeasureState(paint: TextPaint) { | |
paint.typeface = typeface | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment