Bỏ qua để đến nội dung

Text và Typography

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.

@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
)
}
}
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

@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
)
)
}
@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)
}
}
@Composable
fun MergedStyle() {
val baseStyle = MaterialTheme.typography.bodyLarge
Text(
text = "Custom merged style",
style = baseStyle.copy(
color = Color.Blue,
fontWeight = FontWeight.Bold
)
)
}

@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")
}
}
)
}
@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.")
}
}
)
}

@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}")
}
}
)
}
@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)
}

@Composable
fun BasicTextFieldExample() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Enter name") },
placeholder = { Text("John Doe") },
singleLine = true
)
}
@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)
}
}
)
}
@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
)
}
}
)
}

@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
)
)
}
Type Mô tả
Text Default
Number Chỉ số
Phone Số điện thoại
Email Email
Password Password
NumberPassword PIN
Decimal Số thập phân
Uri URL
@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() }
)
)
}
}

Đặt font files vào res/font/:

res/
└── font/
├── roboto_regular.ttf
├── roboto_bold.ttf
└── roboto_italic.ttf
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
)
}
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
)
)

@Composable
fun SelectableText() {
SelectionContainer {
Column {
Text("This text can be selected")
Text("This text can also be selected")
}
}
}
@Composable
fun MixedSelection() {
SelectionContainer {
Column {
Text("Selectable")
DisableSelection {
Text("Not selectable")
}
Text("Selectable again")
}
}
}

@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
)
}
}

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
}
}
)
}

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