sp — Scale-independent Pixels หน่วยวัดข้อความใน Android ที่คำนึงถึงการตั้งค่าขนาดฟอนต์บนอุปกรณ์ แตกต่างจาก dp ที่ sp จะปรับขนาดตามขนาดฟอนต์ของระบบ ทำให้มั่นใจได้ถึงการเข้าถึงอินเทอร์เฟซ บทความนี้อธิบายความแตกต่างระหว่าง sp และ dp แสดงวิธีตั้งค่า textSize ใน XML และ Kotlin และเปิดเผยแนวทางปฏิบัติที่ดีที่สุดสำหรับการทำงานกับการพิมพ์ใน Android
ประเด็นสำคัญ
sp (scale-independent pixel) — หน่วยวัดขนาดฟอนต์ใน Android ที่ปรับขนาดโดยอัตโนมัติตามการตั้งค่าระบบของผู้ใช้ เมื่อเจ้าของอุปกรณ์เพิ่มขนาดฟอนต์ในการตั้งค่า องค์ประกอบทั้งหมดที่มีค่าเป็น sp จะขยายใหญ่ขึ้นตามสัดส่วน สิ่งนี้ช่วยให้มั่นใจถึงการเข้าถึงอินเทอร์เฟซสำหรับผู้ที่มีความบกพร่องทางสายตา
อัตราส่วนพื้นฐาน: 1 sp = 1 dp ที่มาตราส่วนฟอนต์มาตรฐาน 1.0 (ปกติ) หากผู้ใช้ตั้งค่าฟอนต์เป็นใหญ่ (1.15×) 16 sp จะกลายเป็น 18.4 พิกเซลจริง ที่ขนาดฟอนต์เล็ก (0.85×) 16 sp เดียวกันจะกลายเป็น 13.6 px กลไกการปรับขนาดถูกจัดการผ่าน Settings.System.FONT_SCALE ในระดับ Android Framework
ตาม Google Material Design Guidelines ข้อความควรถูกระบุเป็น sp เท่านั้น ข้อยกเว้นคือป้ายกำกับคงที่ในส่วนประกอบที่ขนาดมีความสำคัญต่อเค้าโครง (เช่น ข้อความภายในปุ่มที่มีความสูงคงที่) ในกรณีเช่นนี้ แนะนำให้ใช้ dp กับการควบคุมการเข้าถึงด้วยตนเอง
ความแตกต่างระหว่าง sp และ dp เป็นหนึ่งในหัวข้อที่พบบ่อยที่สุดในการสัมภาษณ์นักพัฒนา Android และการตรวจสอบโค้ดจริง ทั้งสองหน่วยผูกติดกับพิกเซลที่ไม่ขึ้นกับความหนาแน่น (160 dpi = 1 dp = 1 px บนหน้าจอ mdpi) แต่ทำงานแตกต่างกันเมื่อขนาดฟอนต์ของระบบเปลี่ยนแปลง
| คุณลักษณะ | sp | dp |
|---|---|---|
| วัตถุประสงค์ | ขนาดข้อความ | ขนาดองค์ประกอบ ระยะห่าง ความกว้าง ความสูง |
| การปรับขนาดฟอนต์ | ใช่ (ปัจจัยมาตราส่วน) | ไม่ |
| การปรับขนาดความหนาแน่น | ใช่ | ใช่ |
| API การตั้งค่า | android:textSize | android:layout_width, layout_height, padding, margin |
| Token ของ Material Design | typescale | spacing, sizing |
ในทางปฏิบัติ ข้อผิดพลาดทั่วไปคือการใช้ dp สำหรับข้อความ หากข้อความถูกกำหนดใน dp จะไม่ปรับขนาดเมื่อขนาดฟอนต์เพิ่มขึ้น ทำให้อินเทอร์เฟซไม่สามารถเข้าถึงได้สำหรับผู้ใช้ที่มีสายตาไม่ดี ข้อผิดพลาดตรงกันข้ามคือการใช้ sp สำหรับความสูงของปุ่มหรือระยะห่าง เมื่อฟอนต์เพิ่มขึ้น ปุ่มอาจ "พองตัว" ทำให้เค้าโครงเสียหาย
การปรับขนาด sp ถูกใช้งานในระดับ Android Framework ในคลาส TypedValue สูตรการแปลงคือ: sp = dp × scaledDensity โดยที่ scaledDensity เป็นปัจจัยมาตราส่วนที่คำนึงถึงทั้งความหนาแน่นของหน้าจอและการตั้งค่าขนาดฟอนต์ของผู้ใช้
// การรับ scaledDensity ด้วยโปรแกรมใน Kotlin
val scaledDensity = with(context.resources) {
displayMetrics.scaledDensity
}
// การแปลง sp เป็น px
fun spToPx(sp: Float, context: Context): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
sp,
context.resources.displayMetrics
)
}
// การแปลง px เป็น sp
fun pxToSp(px: Float, context: Context): Float {
return px / context.resources.displayMetrics.scaledDensity
}เมธอด TypedValue.applyDimension เป็นวิธีที่ถูกต้องเท่านั้นในการแปลง sp เป็นพิกเซลในขณะรันไทม์ การคูณโดยตรงด้วย scaledDensity น่าเชื่อถือน้อยกว่าเนื่องจากไม่จัดการกับกรณีขอบ (ค่าศูนย์ การล้น) scaledDensity เปลี่ยนแปลงเมื่อการตั้งค่าฟอนต์ถูกปรับเปลี่ยนผ่าน Settings > Display > Font size
Android มีระดับมาตราส่วนฟอนต์ที่กำหนดไว้ล่วงหน้าห้าระดับ: เล็ก (0.85×), ค่าเริ่มต้น (1.0×), ใหญ่ (1.15×), ใหญ่ที่สุด (1.3×) และระดับเพิ่มเติมใหญ่มาก (>1.3×) ใน Android 14+ ผู้ผลิตอุปกรณ์ (Samsung, Xiaomi) เพิ่มระดับของตนเอง — มากถึง 2.0× ขนาดข้อความต่ำสุดใน sp ไม่เคยรับประกันจำนวนพิกเซลที่แน่นอน
ในเค้าโครง XML sp ถูกใช้เฉพาะในแอตทริบิวต์ android:textSize มิติอื่น ๆ ทั้งหมด — ความกว้าง ความสูง ระยะห่าง ระยะขอบ ระดับความสูง — ถูกกำหนดใน dp กฎนั้นง่าย: หากองค์ประกอบแสดงข้อความ ขนาดของมันคือ sp หากองค์ประกอบกำหนดเรขาคณิต ขนาดคือ dp
<!-- เค้าโครง TextView ที่มี textSize เป็น sp -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:textSize="16sp"
android:padding="12dp"
android:lineSpacingExtra="4dp" />
<!-- การใช้ทรัพยากร dimens -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/text_body_large" />แนะนำให้แยกขนาดข้อความออกเป็นไฟล์ dimens.xml สิ่งนี้ช่วยลดความซับซ้อนในการบำรุงรักษาการพิมพ์ อนุญาตให้แทนที่ขนาดสำหรับการกำหนดค่าหน้าจอที่แตกต่างกัน และรับประกันความสอดคล้องทั่วทั้งแอปพลิเคชัน
<!-- res/values/dimens.xml -->
<resources>
<!-- Material Design Type Scale -->
<dimen name="text_display_large">34sp</dimen>
<dimen name="text_display_medium">28sp</dimen>
<dimen name="text_headline_large">24sp</dimen>
<dimen name="text_headline_medium">20sp</dimen>
<dimen name="text_title_large">18sp</dimen>
<dimen name="text_body_large">16sp</dimen>
<dimen name="text_body_medium">14sp</dimen>
<dimen name="text_label_large">14sp</dimen>
<dimen name="text_label_small">11sp</dimen>
</resources>Material Design Type Scale ประกอบด้วย 13 ระดับขนาด — ตั้งแต่ 11sp สำหรับป้ายกำกับขนาดเล็กถึง 57sp สำหรับหัวเรื่องแสดงผลขนาดใหญ่ โปรเจ็กต์ Android โดยทั่วไปใช้ 8–10 ระดับ ค่าทั้งหมดควรเป็นทวีคูณของ 1sp — ค่าเศษส่วน (15.5sp) ทำให้การเรนเดอร์ลดลงเนื่องจากการจัดตำแหน่งซับพิกเซล
ในการพัฒนา Android สมัยใหม่ ข้อความไม่ค่อยถูกกำหนดโดยตรงผ่าน textSize ใน sp แต่จะใช้ระบบ MaterialTheme.typography แทน ซึ่งแต่ละสไตล์ถูกกำหนดไว้ล่วงหน้าและรวมถึงขนาด น้ำหนัก ความสูงของบรรทัด และระยะห่างระหว่างตัวอักษร
// การพิมพ์แบบกำหนดเองผ่าน Typography API
val AppTypography = Typography(
displayLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 34.sp,
lineHeight = 40.sp,
letterSpacing = 0.sp
),
headlineLarge = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 24.sp,
lineHeight = 32.sp
),
titleLarge = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 18.sp,
lineHeight = 24.sp
),
bodyLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
labelSmall = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)ฟังก์ชันส่วนขยาย .sp ใน Jetpack Compose จะแปลงเป็นพิกเซลที่ไม่ขึ้นกับมาตราส่วนโดยอัตโนมัติ เมื่อขนาดฟอนต์ของระบบเปลี่ยนแปลง Compose จะคำนวณ fontSize lineHeight และ spacing ใหม่ตาม scaledDensity ใหม่ TextUnit (คลาสที่แทน sp) รับประกันว่าข้อความปรับขนาดในขณะที่ระยะห่างไม่ปรับขนาด
ใน Jetpack Compose sp ถูกแสดงด้วยชนิด TextUnit ซึ่งไม่สามารถใช้โดยไม่ได้ตั้งใจสำหรับมิติขององค์ประกอบ คอมไพเลอร์ Kotlin จะแสดงข้อผิดพลาดหากส่ง TextUnit ไปยังที่ที่คาดหวัง Dp สิ่งนี้กำจัดคลาสของบักทั้งหมดที่เกี่ยวข้องกับการปรับขนาดที่ไม่ถูกต้อง
// TextUnit ใน Compose — การทำงานที่ปลอดภัยกับ sp
@Composable
fun ArticleCard(title: String, description: String) {
Card(modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = description,
style = MaterialTheme.typography.bodyMedium
)
}
}
// การตั้งค่า fontSize อย่างชัดเจนเป็น sp
Text(
text = "ขนาดที่กำหนดเอง",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)Compose ไม่มีสิ่งที่เทียบเท่าโดยตรงของแอตทริบิวต์ XML android:textSize — ขนาดข้อความถูกกำหนดผ่าน TextStyle.fontSize ระบบ MaterialTheme.typography ให้สไตล์ที่กำหนดไว้ล่วงหน้าซึ่งสอดคล้องกับ Material Design Type Scale หากต้องการขนาดที่กำหนดเอง จะใช้ลิเทอรัล 20.sp — ภาษารับประกันว่านี่คือ TextUnit ไม่ใช่ Dp
การใช้ sp อย่างถูกต้องเป็นรากฐานของแอปพลิเคชัน Android ที่สามารถเข้าถึงได้ ด้านล่างนี้เป็นกฎตามคำแนะนำของ Google Material Design และ Android Developers Guide
กฎหลักของการเข้าถึง: ผู้ใช้ควรสามารถเพิ่มขนาดฟอนต์ได้โดยไม่สูญเสียฟังก์ชันการทำงาน หากแอปพลิเคชันทำงานถูกต้องที่มาตราส่วนฟอนต์ 1.3× แอปจะผ่านการทดสอบการเข้าถึงขั้นพื้นฐาน Google Play อาจปฏิเสธแอปพลิเคชันหาก textSize ถูกกำหนดใน dp และอินเทอร์เฟซไม่ปรับขนาด
คำถามที่พบบ่อย
sp (scale-independent pixel) ปรับขนาดเมื่อขนาดฟอนต์ของระบบเปลี่ยนแปลง ในขณะที่ dp ไม่ปรับ sp ใช้เฉพาะสำหรับข้อความ dp สำหรับมิติอื่น ๆ ทั้งหมดขององค์ประกอบ ปัจจัยการแปลง: sp = dp × FONT_SCALE
ไม่ได้ ระยะห่าง ความสูงของปุ่ม และความกว้างของการ์ดควรถูกกำหนดใน dp การใช้ sp สำหรับระยะห่างทำให้องค์ประกอบเติบโตอย่างไม่เป็นธรรมชาติเมื่อฟอนต์เพิ่มขึ้นและอาจเกินขอบเขตหน้าจอ
เปิด Settings > Display > Font size และตั้งค่าเป็นค่าสูงสุด (Largest) เปิดแอปพลิเคชันและตรวจสอบว่าข้อความทั้งหมดอ่านได้ องค์ประกอบไม่ทับซ้อนกัน และปุ่มไม่เกินขอบเขตหน้าจอ สำหรับระบบอัตโนมัติ ให้ใช้ Espresso กับ UiAutomator
Material Design แนะนำขนาดต่ำสุด 11sp สำหรับ labelSmall สำหรับข้อความเนื้อหา — ตั้งแต่ 14sp (bodyMedium) ถึง 16sp (bodyLarge) ขนาดที่น้อยกว่า 11sp ไม่แนะนำเนื่องจากอ่านไม่ได้บนอุปกรณ์ที่มีความหนาแน่นของพิกเซลสูง
ข้อความจะแสดงในขนาดคงที่โดยไม่ขึ้นกับการตั้งค่าฟอนต์ของระบบ ผู้ใช้ที่มีฟอนต์ขนาดใหญ่จะไม่สามารถอ่านข้อความนี้ได้อย่างสบายตา สิ่งนี้ละเมิดข้อกำหนดการเข้าถึง WCAG และอาจทำให้แอปพลิเคชันถูกปฏิเสธจาก Google Play
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ