HtmlCompat: วิธีมาตรฐาน (API 24+ / androidx)
ใช้ androidx.core.text.HtmlCompat รองรับ HTML Tag พื้นฐาน และทำงานเหมือนกันทุก API Level
HTML Tags ที่รองรับ
| Tag | Span/Style | ตัวอย่าง |
|---|---|---|
| <b>, <strong> | StyleSpan (Bold) | <b>Bold</b> |
| <i>, <em>, <cite>, <dfn> | StyleSpan (Italic) | <i>Italic</i> |
| <u> | UnderlineSpan | <u>Underline</u> |
| <strike>, <s>, <del> | StrikethroughSpan | <s>Strike</s> |
| <big> | RelativeSizeSpan (1.25x) | <big>Big</big> |
| <small> | RelativeSizeSpan (0.8x) | <small>Small</small> |
| <font size="..." color="..." face="..."> | AbsoluteSizeSpan, ForegroundColorSpan, TypefaceSpan | <font color="#FF0000">Red</font> |
| <tt>, <code>, <kbd>, <samp> | TypefaceSpan (Monospace) | <code>Code</code> |
| <sup> | SuperscriptSpan | x<sup>2</sup> |
| <sub> | SubscriptSpan | H<sub>2</sub>O |
| <a href="..."> | URLSpan (Clickable) | <a href="https://...">Link</a> |
| <img src="..."> | ImageGetter (ต้อง Implement เอง) | <img src="icon.png"> |
| <br> | New Line | Line 1<br>Line 2 |
| <p> | Paragraph (Margin) | <p>Para</p> |
| <div> | Block (Margin) | <div>Div</div> |
| <ul>, <ol>, <li> | BulletSpan, LeadingMarginSpan | List |
| <blockquote> | Quote (Margin) | <blockquote>Quote</blockquote> |
ตัวอย่างพื้นฐาน
MainActivity.java
import android.os.Bundle;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.text.HtmlCompat;
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.htmlTextView);
String htmlText = "What is Android?
\n" +
"Android is an open source and Linux-based Operating System for mobile devices such as smartphones and tablet computers.
\n" +
"Android was developed by the Open Handset Alliance, led by Google, and other companies.
\n" +
"Android offers a unified approach to application development for mobile devices which means developers need only develop for Android, and their applications should be able to run on different devices powered by Android.
\n" +
"The first beta version of the Android Software Development Kit (SDK) was released by Google in 2007 whereas the first commercial version, Android 1.0, was released in September 2008.
\n" +
"\n" +
"- Item 1
- Item 2
- Item 3
Layout (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/htmlTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:lineSpacingMultiplier="1.2" />
</ScrollView>
Flag ใน HtmlCompat.fromHtml()
// FROM_HTML_MODE_LEGACY (0) - พฤติกรรมเดิม (API < 24)
// - ไม่เพิ่ม Margin ระหว่าง Block Elements (p, div, ul)
// - ใช้เมื่อต้องการ Layout แบบเก่า
// FROM_HTML_MODE_COMPACT (63 / 0x3F) - พฤติกรรมใหม่ (API 24+)
// - เพิ่ม Margin ระหว่าง Block Elements
// - รองรับ CSS ง่ายๆ (color, text-align, etc.)
// - แนะนำสำหรับ App ใหม่
// ใช้แบบนี้ (Best Practice):
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
? HtmlCompat.FROM_HTML_MODE_COMPACT
: HtmlCompat.FROM_HTML_MODE_LEGACY;
Spanned spanned = HtmlCompat.fromHtml(htmlText, flags);
ขั้นสูง: Custom Tag Handler (รองรับ Tag เพิ่มเติม)
import android.text.Editable;
import android.text.Html;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
public class CustomTagHandler implements Html.TagHandler {
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if (tag.equalsIgnoreCase("highlight")) {
if (opening) {
// เริ่ม Tag: เพิ่ม Span
int len = output.length();
output.setSpan(new ForegroundColorSpan(0xFFFF0000), len, len, Spanned.SPAN_MARK_MARK);
output.setSpan(new RelativeSizeSpan(1.2f), len, len, Spanned.SPAN_MARK_MARK);
} else {
// ปิด Tag: ปิด Span
int len = output.length();
Object[] spans = output.getSpans(0, len, Object.class);
for (Object span : spans) {
if (span instanceof ForegroundColorSpan || span instanceof RelativeSizeSpan) {
int start = output.getSpanStart(span);
int end = output.getSpanEnd(span);
if (start >= 0 && end == len && output.getSpanFlags(span) == Spanned.SPAN_MARK_MARK) {
output.removeSpan(span);
output.setSpan(span, start, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
}
}
}
// ใช้งาน
Spanned spanned = HtmlCompat.fromHtml(htmlText, flags, null, new CustomTagHandler());
ขั้นสูง: ImageGetter (โหลดภาพใน HTML)
HtmlCompat.fromHtml(htmlText, flags, new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
// source = ค่าใน src attribute
// 1. จาก Drawable Resource
int resId = getResources().getIdentifier(source, "drawable", getPackageName());
if (resId != 0) {
Drawable d = ContextCompat.getDrawable(MainActivity.this, resId);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
}
// 2. จาก Assets
try (InputStream is = getAssets().open(source)) {
Drawable d = Drawable.createFromStream(is, source);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
} catch (IOException e) { }
// 3. จาก URL (ต้อง Async - ใช้ Placeholder ก่อน)
// ต้องใช้ Library หรือ Custom Implementation
return null;
}
}, new CustomTagHandler());
HTML จาก String Resource (strings.xml)
WelcomeThis is bold and italic text.
]]>-->
String html = getString(R.string.html_content); Spanned spanned = HtmlCompat.fromHtml(html, flags); textView.setText(spanned); textView.setMovementMethod(LinkMovementMethod.getInstance());
ข้อจำกัดของ HtmlCompat
- ไม่รองรับ CSS ทั้งหมด - เฉพาะ Inline Style และ Tag พื้นฐาน
- ไม่รองรับ <table>, <form>, <script>, <style>
- ImageGetter ต้อง Sync - ไม่รองรับ Async Load ภาพจาก Network โดยตรง
- Performance - HTML ยาวมากอาจช้า ควร Parse แบบ Background
ทางเลือกทันสมัย: Markdown / Jetpack Compose
// Markdown (ใช้ Library: markdown-view atau Markwon)
implementation "io.github.markwon:markwon:4.6.0"
Markwon markwon = Markwon.create(this);
markwon.setMarkdown(textView, "# Hello **Markdown**");
// Jetpack Compose
@Composable
fun HtmlText(html: String) {
AndroidView(factory = { context ->
TextView(context).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
}
})
}
สรุป
- ใช้
HtmlCompat.fromHtml(html, flags)+LinkMovementMethod - Flag:
FROM_HTML_MODE_COMPACT(API 24+) หรือFROM_HTML_MODE_LEGACY - รองรับ Tag พื้นฐาน: b, i, u, a, img, ul/ol/li, p, div, br, font, sup/sub
- Custom Tag/Image ได้ผ่าน TagHandler, ImageGetter
- HTML 複雑/ใหญ่ → ใช้ Markdown Library หรือ WebView