Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

แสดง HTML ใน TextView บน Android


HtmlCompat: วิธีมาตรฐาน (API 24+ / androidx)

ใช้ androidx.core.text.HtmlCompat รองรับ HTML Tag พื้นฐาน และทำงานเหมือนกันทุก API Level

HTML Tags ที่รองรับ

TagSpan/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>SuperscriptSpanx<sup>2</sup>
<sub>SubscriptSpanH<sub>2</sub>O
<a href="...">URLSpan (Clickable)<a href="https://...">Link</a>
<img src="...">ImageGetter (ต้อง Implement เอง)<img src="icon.png">
<br>New LineLine 1<br>Line 2
<p>Paragraph (Margin)<p>Para</p>
<div>Block (Margin)<div>Div</div>
<ul>, <ol>, <li>BulletSpan, LeadingMarginSpanList
<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" + "

Visit Android Developers

\n" + "
  • Item 1
  • Item 2
  • Item 3
"; // ใช้ HtmlCompat (แนะนำ - รองรับ API ทุกเวอร์ชัน) Spanned spanned = HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_LEGACY); // หรือ FROM_HTML_MODE_COMPACT (API 24+) - เพิ่มบรรทัดว่างระหว่าง Block textView.setText(spanned); // ทำให้ Link คลิกได้ textView.setMovementMethod(LinkMovementMethod.getInstance()); } }

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)

Welcome

This is bold and italic text.

Click here

]]>-->
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