ParagraphStyle คือชุดของพารามิเตอร์การจัดรูปแบบที่กำหนดลักษณะที่ปรากฏของย่อหน้าข้อความ ในการพัฒนามือถือ ParagraphStyle ประกอบด้วยการจัดแนว (left, center, right, justified) ระยะห่างระหว่างบรรทัด การเยื้องบรรทัดแรก ระยะห่างระหว่างย่อหน้า และทิศทางข้อความ ตาม Material Design Typography Guidelines (2025) การใช้ ParagraphStyle อย่างสม่ำเสมอบนหน้าจอทั้งหมดของแอปพลิเคชันช่วยให้จังหวะการพิมพ์เป็นหนึ่งเดียวและลดความรบกวนทางสายตาเมื่อสลับระหว่างส่วนต่างๆ
ประเด็นสำคัญ
ParagraphStyle (สไตล์ย่อหน้า) คือชุดของกฎการจัดรูปแบบที่สามารถนำไปใช้กับย่อหน้าข้อความได้หนึ่งย่อหน้าหรือมากกว่า แตกต่างจากสไตล์อักขระ (bold, italic, font-size) ที่ทำงานในระดับอักขระแต่ละตัว ParagraphStyle จัดการคุณสมบัติระดับมหภาค: ตำแหน่งข้อความในคอนเทนเนอร์ ระยะห่างระหว่างบรรทัดและย่อหน้า ทิศทางการเขียน และกฎการตัดคำ
ในอดีต แนวคิดของ ParagraphStyle มาจากระบบเดสก์ท็อปพับลิชชิ่ง (QuarkXPress, PageMaker, InDesign) ซึ่งแต่ละย่อหน้ามีชุดพารามิเตอร์ของตัวเอง ในการพัฒนามือถือ แนวทางนี้ได้เปลี่ยนเป็น API ซอฟต์แวร์: NSMutableParagraphStyle บน iOS คลาส ParagraphStyle และ LineBreakStyle ใน Android และพารามิเตอร์เช่น textAlign และ textHeightBehavior ใน Flutter ทั้งหมดนี้มีวัตถุประสงค์เดียวกัน — การจัดการรูปแบบย่อหน้า ผ่านโค้ด โดยไม่ผูกติดกับเอนจินข้อความเฉพาะ
ตาม Bringhurst — The Elements of Typographic Style (2024) การกำหนดค่า ParagraphStyle ที่เหมาะสมคือรากฐานของวัฒนธรรมการพิมพ์ของอินเทอร์เฟซ หากแบบอักษรและขนาดฟอนต์กำหนด «เสียง» ของข้อความ สไตล์ย่อหน้าจะกำหนด «ลมหายใจ» ของมัน: จังหวะ การหยุด และการเน้น ในอินเทอร์เฟซมือถือซึ่งพื้นที่จำกัด ParagraphStyle ที่ถูกต้องช่วยให้ใส่ข้อมูลได้มากขึ้นโดยไม่สูญเสียความสามารถในการอ่าน
ParagraphStyle ประกอบด้วยคุณสมบัติหลายกลุ่ม แต่ละกลุ่มรับผิดชอบด้านเฉพาะของการจัดรูปแบบ มาดูพารามิเตอร์หลักที่พร้อมใช้งานใน API มือถือกัน การจัดแนวเป็นพารามิเตอร์หลักที่กำหนดตำแหน่งแนวนอนของข้อความ บน iOS มี NSTextAlignment บน Android — Layout.Alignment ใน Flutter — TextAlign
| คุณสมบัติ | iOS (NSMutableParagraphStyle) | Android (Compose) | Flutter (TextStyle) |
|---|---|---|---|
| การจัดแนว | alignment | textAlign | textAlign |
| การเยื้องซ้าย | headIndent | textIndent (ใน Compose) | — |
| การเยื้องขวา | tailIndent | — | — |
| การเยื้องบรรทัดแรก | firstLineHeadIndent | textIndent (firstLine) | — |
| ระยะห่างระหว่างย่อหน้า | paragraphSpacing | paragraphStyle (lineHeight) | — |
| ระยะห่างก่อนย่อหน้า | paragraphSpacingBefore | — | — |
| ทิศทางข้อความ | baseWritingDirection | TextDirection (Compose) | textDirection |
| โหมดการขึ้นบรรทัดใหม่ | lineBreakMode | overflow (TextStyle) | overflow (TextStyle) |
สิ่งสำคัญคือต้องเข้าใจว่า ไม่ใช่ทุกคุณสมบัติ ที่มีอยู่ในทุกแพลตฟอร์มในระดับเดียวกัน ตัวอย่างเช่น การเยื้องบรรทัดแรก (firstLineHeadIndent) รองรับอย่างสมบูรณ์เฉพาะบน iOS ผ่าน NSMutableParagraphStyle ใน Android ใช้ SpannableString กับ LeadingMarginSpan เพื่อจุดประสงค์นี้ และใน Flutter ใช้ RichText กับ TextSpan ที่กำหนดเองและ padding ในการพัฒนาข้ามแพลตฟอร์ม (Flutter, React Native) คุณสมบัติ ParagraphStyle บางอย่างต้องถูกจำลองผ่านคอนเทนเนอร์หรือ Spacer
บน iOS เครื่องมือหลักสำหรับการทำงานกับสไตล์ย่อหน้าคือคลาส NSMutableParagraphStyle — คลาสย่อยของ NSParagraphStyle โดยให้ชุดคุณสมบัติที่สมบูรณ์สำหรับการจัดการรูปแบบย่อหน้าและถูกนำไปใช้ผ่าน NSAttributedString NSMutableParagraphStyle ถูกใช้ไม่เพียงแต่ใน UILabel และ UITextView แต่ยังใช้ใน Core Text ผ่าน CTParagraphStyle ด้วย
// iOS: การกำหนดค่า ParagraphStyle เต็มรูปแบบ
let paragraphStyle = NSMutableParagraphStyle()
// การจัดแนว
paragraphStyle.alignment = .justified // จัดชิดขอบ
// การเยื้อง
paragraphStyle.headIndent = 16 // การเยื้องซ้าย
paragraphStyle.tailIndent = -16 // การเยื้องขวา (ค่าลบ)
paragraphStyle.firstLineHeadIndent = 32 // การเยื้องบรรทัดแรก
// ระยะห่างระหว่างย่อหน้า
paragraphStyle.paragraphSpacing = 12 // หลังย่อหน้า
paragraphStyle.paragraphSpacingBefore = 8 // ก่อนย่อหน้า
// ความสูงบรรทัด
paragraphStyle.minimumLineHeight = 24
paragraphStyle.maximumLineHeight = 24
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 16),
.paragraphStyle: paragraphStyle
]
let label = UILabel()
label.attributedText = NSAttributedString(
string: "This paragraph has a custom ParagraphStyle.\n\nSecond paragraph with 12 pt spacing.",
attributes: attributes
)
ใน SwiftUI ไม่มีสิ่งที่เทียบเท่าโดยตรงกับ NSMutableParagraphStyle — แต่จะใช้การรวมกันของตัวปรับแต่ง: .multilineTextAlignment() สำหรับการจัดแนว .lineSpacing() สำหรับระยะห่างระหว่างบรรทัด สำหรับการเยื้องบรรทัดแรกและระยะห่างระหว่างย่อหน้าใน SwiftUI จำเป็นต้องใช้ Text + padding หรือ AttributedString (พร้อมใช้งานตั้งแต่ iOS 15+) ซึ่งรองรับ NSParagraphStyle ผ่านแอตทริบิวต์
// SwiftUI: AttributedString กับ ParagraphStyle (iOS 15+)
var attributed: AttributedString = """
This is the first paragraph with indent.
This is the second paragraph with 12 pt spacing.
"""
let ps = NSMutableParagraphStyle()
ps.paragraphSpacing = 12
ps.firstLineHeadIndent = 20
let container = AttributeContainer()
.paragraphStyle(ps)
attributed.mergeAttributes(container)
Text(attributed)
.font(.body)
ตาม Apple — Text Programming Guide (2025) NSMutableParagraphStyle รองรับพารามิเตอร์การจัดรูปแบบสูงสุด 14 รายการ สิ่งสำคัญคือต้องจำว่า tailIndent ถูกตั้งค่าเป็นค่าลบสัมพันธ์กับความกว้างของคอนเทนเนอร์ ในขณะที่ headIndent เป็นค่าบวก พฤติกรรมที่ไม่ชัดเจนนี้มักทำให้เกิดข้อผิดพลาดในนักพัฒนา iOS มือใหม่
บน Android แนวทางของ ParagraphStyle แตกต่างกันระหว่างระบบ View และ Jetpack Compose ในระบบ View แบบคลาสสิก พารามิเตอร์ย่อหน้าถูกกำหนดผ่านแอตทริบิวต์ XML ของ TextView (android:textAlignment, android:lineSpacingExtra) หรือผ่าน SpannableString กับการใช้งานอินเทอร์เฟซ ParagraphStyle จากแพ็คเกจ android.text.style
// ระบบ Android View: ParagraphStyle ผ่าน SpannableString
val text = "First paragraph with settings.\n\nSecond paragraph."
val spannable = SpannableString(text)
// การจัดแนวแบบชิดขอบ
spannable.setSpan(
AlignmentSpan.Standard(Layout.Alignment.ALIGN_NORMAL),
0, text.length,
Spannable.SPAN_PARAGRAPH
)
// การเยื้องบรรทัดแรกผ่าน LeadingMarginSpan
spannable.setSpan(
LeadingMarginSpan.Standard(0, 20),
0, text.indexOf('\n'),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
ใน Jetpack Compose ParagraphStyle ถูกจัดการผ่านพารามิเตอร์ TextStyle ใช้ textAlign สำหรับการจัดแนว lineHeight (ใน sp หรือ em) สำหรับระยะห่างระหว่างบรรทัด และ textIndent สำหรับการเยื้องบรรทัดแรก Compose ยังรองรับ LineBreak สำหรับการควบคุมการขึ้นบรรทัดใหม่แบบละเอียด (Simple, Heading, Paragraph) ซึ่งมีประโยชน์โดยเฉพาะสำหรับข้อความหลายภาษา
// Jetpack Compose: ParagraphStyle ผ่าน TextStyle
Text(
text = "ย่อหน้าแรกด้วยสไตล์ที่กำหนดเอง
ย่อหน้าที่สองด้วยการเยื้อง",
style = TextStyle(
textAlign = TextAlign.Justify,
lineHeight = 24.sp,
textIndent = TextIndent(firstLine = 16.sp),
lineBreak = LineBreak.Paragraph
)
)
ตาม Android Developers — Compose ParagraphStyle (2025) เมื่อใช้ TextIndent ใน Compose สิ่งสำคัญคือต้องตั้งค่า firstLine เป็น sp (ไม่ใช่ dp) เนื่องจากการเยื้องบรรทัดแรกควรปรับขนาดตามข้อความ หากตั้งค่าการเยื้องเป็น dp เมื่อขนาดฟอนต์เพิ่มขึ้น การเยื้องบรรทัดแรกจะเล็กลงเมื่อมองเห็น ซึ่งจะทำลายสัดส่วนการพิมพ์
ใน Flutter กระบวนทัศน์ ParagraphStyle ไม่ได้ถูกแยกออกเป็นคลาสเฉพาะ — แต่จะใช้การรวมกันของพารามิเตอร์ TextStyle และคลาสเพิ่มเติม การจัดแนวถูกกำหนดผ่าน TextAlign ระยะห่างระหว่างบรรทัดผ่าน height (ไม่มีหน่วย) และ TextHeightBehavior ควบคุมการเยื้องที่มองเห็นของบรรทัดแรกและบรรทัดสุดท้าย
// Flutter: ParagraphStyle ผ่านพารามิเตอร์ Text
Text(
'ย่อหน้าที่จัดสไตล์ใน Flutter
ย่อหน้าที่สองด้วยการเยื้อง',
style: TextStyle(
fontSize: 16,
height: 1.5,
leadingDistribution: TextLeadingDistribution.proportional,
),
textAlign: TextAlign.justify,
textHeightBehavior: TextHeightBehavior(
applyHeightToFirstAscent: true,
applyHeightToLastDescent: true,
),
)
สำหรับสถานการณ์ที่ซับซ้อนยิ่งขึ้น Flutter เสนอการใช้ RichText กับ TextSpan และ WidgetSpan ที่กำหนดเอง ซึ่งช่วยให้รวมข้อความกับวิดเจ็ตแบบอินไลน์ (ไอคอน รูปภาพ) ในย่อหน้าเดียว โดยคงกฎการจัดรูปแบบที่สม่ำเสมอ อย่างไรก็ตาม วิธีนี้ต้องการการควบคุมการขึ้นบรรทัดใหม่ด้วยตนเองและไม่รองรับการตัดคำอัตโนมัติ
// Flutter: RichText กับการจัดรูปแบบย่อหน้าที่กำหนดเอง
RichText(
text: TextSpan(
style: TextStyle(fontSize: 16, height: 1.5),
children: [
TextSpan(text: 'First paragraph with icon '),
WidgetSpan(
child: Icon(Icons.star, size: 16),
alignment: PlaceholderAlignment.middle,
),
TextSpan(text: '\n\nSecond paragraph after spacing.'),
],
),
)
ตาม Flutter Documentation — Text Widget (2025) TextHeightBehavior เป็นพารามิเตอร์สำคัญสำหรับการแสดง ParagraphStyle ที่ถูกต้องใน Flutter หากตั้งค่า applyHeightToFirstAscent เป็น false การเยื้องด้านบนของบรรทัดแรกจะถูกละเว้นและข้อความ «ติด» กับขอบบนของคอนเทนเนอร์ ซึ่งมีประโยชน์สำหรับหัวเรื่อง แต่ไม่พึงประสงค์สำหรับข้อความหลัก
ParagraphStyle ต้องคำนึงถึงทิศทางการเขียนสำหรับแต่ละท้องถิ่น ในแอปมือถือที่รองรับภาษาอาหรับ ฮิบรู หรืออูรดู (การเขียนจากขวาไปซ้าย RTL) การเยื้องและการจัดแนวควรเปลี่ยนแบบกลับด้าน iOS รองรับการสลับอัตโนมัติผ่าน baseWritingDirection = .natural Android ผ่าน TextDirection Flutter ผ่าน textDirection
การกำหนดค่าการจัดแนวสำหรับท้องถิ่น RTL ต้องการความใส่ใจเป็นพิเศษ หากในโหมด LTR headIndent ตั้งค่าการเยื้องซ้าย ใน RTL มันจะกลายเป็นการเยื้องขวาโดยอัตโนมัติ อย่างไรก็ตาม ไม่ใช่ทุกการใช้งานที่จะจัดการกับการเปลี่ยนแปลงนี้อย่างถูกต้องเมื่อระบุการจัดแนวอย่างชัดเจน (NSTextAlignment.left แทน NSTextAlignment.natural) ตาม Google Internationalization Guide (2025) สำหรับแอปหลายภาษา ให้ใช้การจัดแนวแบบธรรมชาติเสมอและตรวจสอบการเยื้องในทั้งสองรูปแบบ
// Android: การรองรับ RTL ใน ParagraphStyle
val textView = TextView(context)
// เปิดใช้งานการรองรับ RTL อัตโนมัติ
textView.textDirection = View.TEXT_DIRECTION_LOCALE
textView.textAlignment = View.TEXT_ALIGNMENT_TEXT_START
// Spannable ที่รองรับ RTL
val spannable = SpannableString(arabicText)
spannable.setSpan(
AlignmentSpan.Standard(Layout.Alignment.ALIGN_NORMAL),
0, arabicText.length,
Spannable.SPAN_PARAGRAPH
)
ลักษณะเฉพาะของภูมิภาคยังรวมถึงกฎที่แตกต่างกันสำหรับการเยื้องบรรทัดแรก ในการพิมพ์แบบยุโรป การเยื้องบรรทัดแรก (firstLineHeadIndent) เป็นมาตรฐานสำหรับการแยกย่อหน้า ในการพิมพ์แบบญี่ปุ่นและจีนก็ใช้การเยื้องบรรทัดแรกเช่นกัน แต่ขนาดอาจแตกต่างกัน (ปกติ 1 em เทียบกับ 1.5 em ในประเพณียุโรป) สำหรับการพิมพ์แบบอาหรับ การเยื้องบรรทัดแรกนั้นหายาก — ย่อหน้ามักถูกแยกด้วยระยะห่างแนวตั้ง
ข้อผิดพลาดที่พบบ่อยที่สุดคือ การใช้ ParagraphStyle ผ่านแอตทริบิวต์ส่วนกลางของฟิลด์ข้อความเท่านั้น โดยไม่พิจารณาการจัดรูปแบบที่แตกต่างกันของย่อหน้าแต่ละย่อหน้า ใน UILabel หรือ TextView ที่มีหลายย่อหน้าคั่นด้วย ParagraphStyle จะถูกนำไปใช้กับข้อความทั้งหมดอย่างสม่ำเสมอ เว้นแต่จะใช้ข้อความที่มีแอตทริบิวต์กับสไตล์ที่แตกต่างกันสำหรับแต่ละย่อหน้า
ตาม UX Collective — Common Typography Bugs in Mobile Apps (2025) ปัญหาเกี่ยวกับ ParagraphStyle คิดเป็น 18% ของข้อบกพร่องด้านการพิมพ์ทั้งหมดในแอปพลิเคชันมือถือ กรณีที่สำคัญที่สุดคือเมื่อ paragraphSpacing ที่ไม่ถูกต้องนำไปสู่การทับซ้อนของข้อความในบรรทัดต่างๆ หรือเมื่อท้องถิ่น RTL แสดงผลด้วยการเยื้อง LTR ขอแนะนำให้สร้างการทดสอบหน่วยสำหรับแต่ละพารามิเตอร์ ParagraphStyle ด้วยสตริงทดสอบในภาษาต่างๆ
คำถามที่พบบ่อย
NSMutableParagraphStyle จัดการการจัดรูปแบบย่อหน้า (การจัดแนว การเยื้อง ระยะห่างระหว่างบรรทัด) ในขณะที่ TextStyle จัดการแอตทริบิวต์อักขระ (ฟอนต์ สี ขนาด) ทั้งสองถูกนำไปใช้ผ่าน NSAttributedString ParagraphStyle ทำงานในระดับย่อหน้า TextStyle ในระดับอักขระ สามารถมี TextStyle หลายอินสแตนซ์ในย่อหน้าเดียว แต่มี ParagraphStyle เพียงหนึ่งตัวต่อย่อหน้า
ใน Jetpack Compose การเยื้องบรรทัดแรกถูกกำหนดผ่าน TextIndent ภายใน TextStyle: TextStyle(textIndent = TextIndent(firstLine = 16.sp)) ค่าควรเป็น sp เพื่อปรับขนาดตามข้อความ สำหรับข้อความหลายบรรทัด การเยื้องจะใช้กับแต่ละบรรทัดที่เริ่มย่อหน้าใหม่ (อักขระ ) สำหรับย่อหน้าแรก การเยื้องจะถูกใช้เสมอ
ใน SwiftUI ไม่มีสิ่งที่เทียบเท่าโดยตรงกับ paragraphSpacing สำหรับระยะห่างระหว่างย่อหน้า ให้ใช้ AttributedString (iOS 15+) กับ NSMutableParagraphStyle และคุณสมบัติ paragraphSpacing หรือแยกย่อหน้าผ่าน Text("...\n\n...") และเพิ่ม .padding() ระหว่างบล็อกข้อความ สำหรับสถานการณ์การพิมพ์ที่ซับซ้อน ให้ใช้ NSAttributedString ผ่าน UILabelRepresentable
TextView ใน Android ไม่มีคุณสมบัติ paragraphSpacing — ถูกกำหนดผ่าน SpannableString กับ LeadingMarginSpan หรือผ่าน LineBackgroundSpan ที่กำหนดเอง สำหรับระยะห่างระหว่างบรรทัดให้ใช้ setLineSpacing สำหรับระยะห่างระหว่างย่อหน้า ให้แยกย่อหน้าด้วย เพิ่มเติมหรือใช้คอนเทนเนอร์แบบประกอบจากหลายอินสแตนซ์ TextView
ParagraphStyle ที่เหมาะสมช่วยปรับปรุงการเข้าถึง: paragraphSpacing ที่เพียงพอช่วยให้ผู้ใช้ที่มีความบกพร่องทางสติปัญญาแยกแยะขอบเขตย่อหน้าได้ดีขึ้น WCAG 2.2 แนะนำการแยกย่อหน้าด้วยสายตา (การเยื้องหรือระยะห่าง) ParagraphSpacing ไม่ควรน้อยกว่า 1.5 × line-height สำหรับท้องถิ่น RTL ตรวจสอบว่าการเยื้องถูกกลับด้านอย่างถูกต้อง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ