Text và Typography trong Jetpack Compose
Hiển thị và xử lý text là một trong những tác vụ phổ biến nhất trong UI. Bài này hướng dẫn chi tiết cách làm việc với Text trong Compose.
1. Text cơ bản
Hiển thị Text đơn giản
@Composable
fun BasicTextExamples() {
Column {
Text("Hello World")
Text(
text = "Styled Text",
color = Color.Blue,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic,
letterSpacing = 2.sp
)
}
}Các properties của Text
| Property | Mô tả | Ví dụ |
|---|---|---|
text | Nội dung text | "Hello" |
color | Màu chữ | Color.Blue |
fontSize | Kích thước chữ | 16.sp |
fontWeight | Độ đậm | FontWeight.Bold |
fontStyle | Kiểu chữ | FontStyle.Italic |
fontFamily | Font family | FontFamily.Serif |
letterSpacing | Khoảng cách chữ | 2.sp |
textDecoration | Underline, line-through | TextDecoration.Underline |
textAlign | Căn lề | TextAlign.Center |
lineHeight | Chiều cao dòng | 24.sp |
maxLines | Số dòng tối đa | 2 |
overflow | Xử lý khi tràn | TextOverflow.Ellipsis |
2. TextStyle và Typography
Sử dụng TextStyle
@Composable
fun TextStyleExample() {
Text(
text = "Custom Style",
style = TextStyle(
color = Color.DarkGray,
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.SansSerif,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
)
}Sử dụng MaterialTheme typography
@Composable
fun MaterialTypography() {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("Display Large", style = MaterialTheme.typography.displayLarge)
Text("Headline Medium", style = MaterialTheme.typography.headlineMedium)
Text("Title Large", style = MaterialTheme.typography.titleLarge)
Text("Body Large", style = MaterialTheme.typography.bodyLarge)
Text("Label Medium", style = MaterialTheme.typography.labelMedium)
}
}Merge TextStyles
@Composable
fun MergedStyle() {
val baseStyle = MaterialTheme.typography.bodyLarge
Text(
text = "Custom merged style",
style = baseStyle.copy(
color = Color.Blue,
fontWeight = FontWeight.Bold
)
)
}3. AnnotatedString - Rich Text
Styled spans
@Composable
fun StyledSpans() {
Text(
buildAnnotatedString {
append("Normal text ")
withStyle(SpanStyle(color = Color.Red)) {
append("Red text ")
}
withStyle(SpanStyle(
fontWeight = FontWeight.Bold,
textDecoration = TextDecoration.Underline
)) {
append("Bold underlined")
}
}
)
}Paragraph styles
@Composable
fun ParagraphStyles() {
Text(
buildAnnotatedString {
withStyle(ParagraphStyle(textAlign = TextAlign.Center)) {
append("Centered paragraph\n")
}
withStyle(ParagraphStyle(
textIndent = TextIndent(firstLine = 16.sp),
lineHeight = 24.sp
)) {
append("This paragraph has first-line indentation and custom line height.")
}
}
)
}4. Clickable Text và Links
ClickableText
@Composable
fun ClickableTextExample() {
val annotatedString = buildAnnotatedString {
append("Click ")
pushStringAnnotation(tag = "URL", annotation = "https://google.com")
withStyle(SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) {
append("here")
}
pop()
append(" to visit Google")
}
ClickableText(
text = annotatedString,
onClick = { offset ->
annotatedString.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let { annotation ->
// Open URL
println("Clicked: ${annotation.item}")
}
}
)
}Sử dụng LinkAnnotation (Compose 1.7+)
@Composable
fun ModernLinkExample() {
val annotatedString = buildAnnotatedString {
append("Read our ")
withLink(
LinkAnnotation.Url(
url = "https://example.com/terms",
styles = TextLinkStyles(
style = SpanStyle(color = Color.Blue),
hoveredStyle = SpanStyle(textDecoration = TextDecoration.Underline)
)
)
) {
append("Terms of Service")
}
}
Text(annotatedString)
}5. TextField - Input text
Basic TextField
@Composable
fun BasicTextFieldExample() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Enter name") },
placeholder = { Text("John Doe") },
singleLine = true
)
}OutlinedTextField
@Composable
fun OutlinedExample() {
var email by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }
OutlinedTextField(
value = email,
onValueChange = {
email = it
isError = !it.contains("@")
},
label = { Text("Email") },
leadingIcon = { Icon(Icons.Default.Email, null) },
trailingIcon = {
if (isError) {
Icon(Icons.Default.Warning, null, tint = Color.Red)
}
},
isError = isError,
supportingText = {
if (isError) {
Text("Invalid email format", color = MaterialTheme.colorScheme.error)
}
}
)
}Password TextField
@Composable
fun PasswordTextField() {
var password by remember { mutableStateOf("") }
var visible by remember { mutableStateOf(false) }
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
singleLine = true,
visualTransformation = if (visible)
VisualTransformation.None
else
PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
IconButton(onClick = { visible = !visible }) {
Icon(
if (visible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
null
)
}
}
)
}6. Keyboard Options và Actions
KeyboardOptions
@Composable
fun KeyboardOptionsExample() {
var phone by remember { mutableStateOf("") }
TextField(
value = phone,
onValueChange = { phone = it },
label = { Text("Phone") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Phone, // Bàn phím số điện thoại
imeAction = ImeAction.Done // Nút Done
)
)
}Các KeyboardType
| Type | Mô tả |
|---|---|
Text | Default |
Number | Chỉ số |
Phone | Số điện thoại |
Email | |
Password | Password |
NumberPassword | PIN |
Decimal | Số thập phân |
Uri | URL |
KeyboardActions
@Composable
fun FormFields() {
val focusManager = LocalFocusManager.current
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
Column {
TextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) }
)
)
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = { focusManager.clearFocus() }
)
)
}
}7. Custom Fonts
Thêm font vào resources
Đặt font files vào res/font/:
res/
└── font/
├── roboto_regular.ttf
├── roboto_bold.ttf
└── roboto_italic.ttfTạo FontFamily
val RobotoFamily = FontFamily(
Font(R.font.roboto_regular, FontWeight.Normal),
Font(R.font.roboto_bold, FontWeight.Bold),
Font(R.font.roboto_italic, FontWeight.Normal, FontStyle.Italic)
)
@Composable
fun CustomFontText() {
Text(
text = "Custom Font",
fontFamily = RobotoFamily,
fontWeight = FontWeight.Bold
)
}Downloadable Fonts
val provider = GoogleFont.Provider(
providerAuthority = "com.google.android.gms.fonts",
providerPackage = "com.google.android.gms",
certificates = R.array.com_google_android_gms_fonts_certs
)
val fontFamily = FontFamily(
Font(
googleFont = GoogleFont("Lobster"),
fontProvider = provider
)
)8. Text Selection
SelectionContainer
@Composable
fun SelectableText() {
SelectionContainer {
Column {
Text("This text can be selected")
Text("This text can also be selected")
}
}
}DisableSelection
@Composable
fun MixedSelection() {
SelectionContainer {
Column {
Text("Selectable")
DisableSelection {
Text("Not selectable")
}
Text("Selectable again")
}
}
}9. Text overflow và max lines
@Composable
fun TextOverflowExamples() {
val longText = "This is a very long text that will overflow..."
Column {
Text(
text = longText,
maxLines = 1,
overflow = TextOverflow.Ellipsis // ...
)
Text(
text = longText,
maxLines = 2,
overflow = TextOverflow.Clip // Cắt bỏ
)
Text(
text = longText,
maxLines = 1,
overflow = TextOverflow.Visible // Hiển thị tràn
)
}
}10. BasicTextField - Custom Design
Khi cần tùy chỉnh hoàn toàn giao diện TextField:
@Composable
fun CustomTextField() {
var text by remember { mutableStateOf("") }
BasicTextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray, RoundedCornerShape(8.dp))
.padding(16.dp),
textStyle = TextStyle(fontSize = 16.sp),
decorationBox = { innerTextField ->
Box {
if (text.isEmpty()) {
Text("Placeholder...", color = Color.Gray)
}
innerTextField() // Actual text field
}
}
)
}📝 Tóm tắt
| Component | Mục đích |
|---|---|
Text | Hiển thị text |
TextField | Input với Material Design |
OutlinedTextField | Input với outline style |
BasicTextField | Input để custom hoàn toàn |
AnnotatedString | Rich text với multiple styles |
ClickableText | Text với click handlers |
SelectionContainer | Cho phép select text |
Best Practices
- Dùng MaterialTheme.typography cho consistency
- Sử dụng sp cho fontSize (scales with user preferences)
- Handle keyboard actions cho form fields
- Provide visual feedback cho TextField errors
- Dùng BasicTextField khi cần custom design hoàn toàn
Last updated on