Mục tiêu xuất mặc định của figmascope là Jetpack Compose. Điều này không tùy tiện — mô hình bố cục của Compose (Column, Row, Box, Modifier) ánh xạ chặt chẽ với các loại node IR (stack, overlay, absolute, leaf). Một stack dọc trong Figma là Column trong Compose. Việc chuyển đổi mang tính cơ học, khiến nó phù hợp với codegen do agent điều khiển.
Hướng dẫn này trình bày chi tiết ánh xạ IR sang Compose, hiển thị một đoạn JSON thực tế với Composable tương ứng, và giải thích lớp ánh xạ token.
Tại sao Compose là mục tiêu mặc định
Ba lý do cấu trúc:
- Auto-layout ↔ Column/Row. Các khung auto-layout của Figma (phần lớn các thiết kế Figma hiện đại) xuất dưới dạng node
kind: "stack". Các node stack có thuộc tínhaxis—verticalánh xạ thànhColumn,horizontalánh xạ thànhRow. Đây là ánh xạ 1:1 không cần bước diễn giải. - Spacing tokens ↔ giá trị dp. Compose sử dụng
Dpcho tất cả các kích thước bố cục. Các giá trị token trongtokens.jsonlà số nguyên không có đơn vị (ví dụ:spacing.16 = 16) ánh xạ trực tiếp thành16.dp. Không cần chuyển đổi, không cần thu phóng. - Color tokens ↔ Color composable. Các giá trị hex Figma trong
tokens.jsonánh xạ thànhColor(0xFFrrggbb)với một phép biến đổi duy nhất. Khóa token trở thành tên biến ngữ nghĩa trong theme của bạn.
Các loại node IR và ánh xạ Compose tương ứng
| IR kind | Thuộc tính | Compose primitive |
|---|---|---|
stack | axis: "vertical" | Column |
stack | axis: "horizontal" | Row |
overlay | children xếp chồng | Box |
absolute | x, y, width, height | Box với Modifier.offset(x.dp, y.dp) |
leaf | type: "text" | Text với TextStyle |
leaf | type: "rectangle" với fill | Box(Modifier.background(Color(...))) |
Tất cả các loại node có thể mang thuộc tính spacing (khoảng cách giữa các children) và đối tượng padding (trên/phải/dưới/trái). Cả hai đều tham chiếu đến các khóa token.
Ánh xạ token chi tiết
File tokens.json trông như sau:
{
"spacing": {
"4": 4, "8": 8, "12": 12, "16": 16,
"20": 20, "24": 24, "32": 32, "48": 48
},
"radius": {
"4": 4, "8": 8, "12": 12, "16": 16, "full": 9999
},
"color": {
"7f5cfe": "#7F5CFE",
"1a1a2e": "#1A1A2E",
"f6f2ea": "#F6F2EA",
"ffffff": "#FFFFFF",
"e53935": "#E53935"
},
"typography": {
"heading.24": { "size": 24, "weight": 700, "lineHeight": 1.2 },
"body.14": { "size": 14, "weight": 400, "lineHeight": 1.5 },
"label.12": { "size": 12, "weight": 500, "lineHeight": 1.4 }
}
}
Các quy tắc ánh xạ:
spacing.16→16.dpradius.12→RoundedCornerShape(12.dp)color.7f5cfe→Color(0xFF7F5CFE)(thêm tiền tố0xFFcho alpha đầy đủ)typography.heading.24→TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold, lineHeight = 28.8.sp)
Các khóa token cố tình mang tính mờ đục (chuỗi hex cho màu sắc, chuỗi số cho khoảng cách) để không làm lệch mô hình theo bất kỳ quy ước đặt tên nào. Theme Compose của bạn có thể đặt bí danh cho chúng theo tên ngữ nghĩa — colorPrimary, spacingMd — độc lập với IR.
Ví dụ thực tế: JSON màn hình home sang Composable
Đây là IR màn hình home đơn giản hóa. Một stack dọc với leaf tiêu đề và danh sách thẻ:
{
"name": "home",
"kind": "stack",
"axis": "vertical",
"spacing": "spacing.24",
"padding": { "top": "spacing.16", "right": "spacing.16",
"bottom": "spacing.16", "left": "spacing.16" },
"fill": "color.f6f2ea",
"children": [
{
"kind": "leaf",
"type": "text",
"stringRef": "home.title",
"typography": "typography.heading.24",
"fill": "color.1a1a2e"
},
{
"kind": "stack",
"axis": "vertical",
"spacing": "spacing.12",
"children": [
{
"kind": "overlay",
"radius": "radius.12",
"fill": "color.ffffff",
"padding": { "top": "spacing.16", "right": "spacing.16",
"bottom": "spacing.16", "left": "spacing.16" },
"children": [
{
"kind": "leaf",
"type": "text",
"stringRef": "home.card.label",
"typography": "typography.label.12",
"fill": "color.7f5cfe"
}
]
}
]
}
]
}
Composable tương ứng — những gì agent nên tạo ra từ IR này:
@Composable
fun HomeScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF6F2EA))
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
text = stringResource(R.string.home_title),
style = TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
lineHeight = 28.8.sp,
color = Color(0xFF1A1A2E)
)
)
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFFFFFFF))
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_card_label),
style = TextStyle(
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
lineHeight = 16.8.sp,
color = Color(0xFF7F5CFE)
)
)
}
}
}
}
Mọi giá trị trong Composable đều có thể truy ngược về một khóa token trong IR. Không có gì hardcode — 16.dp đến từ spacing.16, 24.dp từ spacing.24, Color(0xFF7F5CFE) từ color.7f5cfe.
String refs — ánh xạ stringResource
Mỗi node văn bản trong IR mang một stringRef với khóa ký hiệu chấm. File strings.json ánh xạ các khóa sang giá trị hiển thị và fallback:
{
"home.title": { "value": "Good morning", "fallback": "Good morning" },
"home.card.label": { "value": "Today's summary", "fallback": "Summary" }
}
Ký hiệu chấm ánh xạ sang ID tài nguyên chuỗi Android với dấu chấm được thay bằng dấu gạch dưới: home.title → R.string.home_title. Trường fallback là những gì bạn hardcode làm chuỗi ký tự nếu tài nguyên chưa tồn tại trong strings.xml:
text = stringResource(R.string.home_title, "Good morning")
Hãy bảo agent luôn sử dụng trường fallback — không phải chuỗi rỗng — để màn hình có thể đọc được trong quá trình phát triển trước khi strings.xml được điền đầy đủ.
Định vị tuyệt đối
Các node với kind: "absolute" sử dụng trực tiếp tọa độ Figma. Chúng xuất hiện trong các thiết kế với các phần tử chồng chéo hoặc các phần tử neo đến các vị trí cụ thể. Ánh xạ Compose sử dụng Box làm parent và Modifier.offset trên children:
// IR: { "kind": "absolute", "x": 24, "y": 80, "width": 120, "height": 40 }
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.offset(x = 24.dp, y = 80.dp)
.size(width = 120.dp, height = 40.dp)
) {
// children
}
}
Định vị tuyệt đối không phổ biến trong các file Figma được cấu trúc tốt (hầu hết các file hiện đại sử dụng auto-layout). Khi bạn thấy nó, hãy kiểm tra xem mục đích thiết kế có thực sự là tuyệt đối hay nhà thiết kế chỉ không áp dụng ràng buộc auto-layout — IR không thể suy ra mục đích.
Những hạn chế thực tế
Bundle xử lý tốt các trường hợp phổ biến. Một số điều nó không làm:
- Gradient fill. IR xuất cảnh báo khi gặp gradient. Fill nền của node bị bỏ qua. Để lại
// TODO: gradientvà triển khai thủ công. - Hiệu ứng phức tạp. Drop shadow và blur không được biểu diễn trong IR. Chúng xuất hiện trong cảnh báo
_meta.jsonnếu có. - Vector icon. IR lưu ID tham chiếu cho các node icon, không phải dữ liệu đường dẫn. Bạn cần giải quyết icon thành drawable hoặc compose icon thực tế riêng biệt.
- Component lồng nhau. IR bao gồm
componentIdtrên các node instance.components/inventory.jsonánh xạ ID sang tên. Triển khai component riêng biệt và tham chiếu theo tên trong Composable parent.
Những hạn chế này là rõ ràng — chúng hiển thị trong cảnh báo _meta.json và CONTEXT.md. Agent không âm thầm đoán mò qua chúng.
Xuất context bundle từ ứng dụng figmascope, sau đó sử dụng với Claude Code hoặc Cursor để triển khai Composable trực tiếp từ IR. Không đoán mò từ screenshot, không có giá trị hardcode.