Bài học 1: Hàm có khả năng kết hợp
Jetpack Compose được xây dựng xung quanh các hàm có khả năng kết hợp. Các hàm này cho phép bạn xác định giao diện người dùng của ứng dụng theo cách lập trình bằng cách mô tả giao diện của ứng dụng đó và cung cấp các phần phụ thuộc dữ liệu thay vì tập trung vào quy trình xây dựng giao diện người dùng (khởi chạy một phần tử, đính kèm phần tử đó vào phần tử mẹ, v.v.). Để tạo một hàm có khả năng kết hợp, chỉ cần thêm chú thích @Composable
vào tên hàm.
Thêm một thành phần văn bản
Để bắt đầu, hãy tải xuống phiên bản mới nhất của Android Studio và tạo một ứng dụng bằng cách chọn Dự án mới, sau đó trong danh mục Điện thoại và Máy tính bảng, chọn Empty Compose Activity (Hoạt động soạn thư trống). Đặt tên cho ứng dụng là ComposeTutoria rồi nhấp vào Hoàn tất. Mẫu mặc định đã chứa một số phần tử Compose, nhưng trong hướng dẫn này, bạn sẽ tạo các phần tử đó từng bước một.
Đầu tiên, hãy hiển thị dòng văn bản "Xin chào thế giới!" bằng cách thêm một phần tử văn bản vào phương thức onCreate
. Bạn thực hiện việc này bằng cách xác định một khối nội dung và gọi hàm có khả năng kết hợp Text
Khối setContent
xác định bố cục của hoạt động trong đó các hàm kết hợp có thể được gọi. Bạn chỉ có thể gọi hàm kết hợp từ các hàm kết hợp khác.
Jetpack Compose dùng một trình bổ trợ biên dịch Kotlin để biến những hàm có khả năng kết hợp này thành các thành phần trên giao diện người dùng của ứng dụng. Ví dụ: hàm kết hợp Text
do thư viện Giao diện người dùng trong Compose xác định sẽ hiển thị một nhãn văn bản trên màn hình.
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.Text class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text("Hello world!") } } }

Xác định hàm có khả năng kết hợp
Để một hàm có thể kết hợp, hãy thêm chú thích @Composable
.
Để thử, hãy xác định một hàm MessageCard
được
chuyển cho một tên và sử dụng hàm đó để định cấu hình thành phần văn bản.
// ... import androidx.compose.runtime.Composable class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard("Android") } } } @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") }

Xem trước hàm trong Android Studio
Chú thích @Preview
cho phép bạn xem trước các hàm kết hợp trong Android Studio mà không cần phải tạo và cài đặt ứng dụng trên thiết bị Android hoặc trình mô phỏng. Bạn phải dùng chú thích cho một hàm kết hợp không nhận các tham số. Vì lý do này, bạn không thể trực tiếp xem trước hàm MessageCard
. Thay vào đó, hãy tạo một hàm thứ hai có tên là PreviewMessageCard
. Hàm này sẽ gọi MessageCard
bằng một tham số thích hợp. Hãy thêm chú thích @Preview
trước @Composable
.
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }

Xây dựng lại dự án. Bản thân ứng dụng không thay đổi vì chức năng PreviewMessageCard
mới không được gọi ở bất cứ đâu. Tuy nhiên, Android Studio sẽ thêm một cửa sổ xem trước để bạn có thể mở rộng bằng cách nhấp vào phân tách (thiết kế/mã). Cửa sổ này hiển thị bản xem trước các thành phần trên giao diện người dùng do hàm kết hợp được đánh dấu bằng chú thích @Preview
tạo. Để cập nhật bản xem trước bất kỳ lúc nào, hãy nhấp vào nút làm mới ở đầu cửa sổ xem trước.

import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.Text class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text("Hello world!") } } }

// ... import androidx.compose.runtime.Composable class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard("Android") } } } @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") }

// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }


Bài 2: Bố cục
Các thành phần trên giao diện người dùng được phân cấp, thành phần này được chứa trong thành phần khác. Trong Compose, bạn xây dựng một hệ phân cấp giao diện người dùng bằng cách gọi hàm có khả năng kết hợp từ các hàm có khả năng kết hợp khác.
Thêm nhiều văn bản
Đến đây, bạn đã tạo được hàm kết hợp và bản xem trước đầu tiên! Để khám phá thêm các tính năng khác của Jetpack Compose, chúng ta sẽ xây dựng một màn hình nhắn tin đơn giản chứa trang thông tin những thông báo có thể mở rộng với một số ảnh động.
Hãy bắt đầu bằng việc khiến các thông báo trở nên phong phú hơn bằng cách hiển thị tên tác giả và nội dung thông báo. Trước tiên, bạn cần thay đổi tham số thành phần kết hợp để chấp nhận đối tượng Message
thay vì một String
, sau đó thêm thành phần kết hợp Text
vào bên trong MessageCard
. Đừng quên cập nhật cả bản xem trước.
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard(Message("Android", "Jetpack Compose")) } } } data class Message(val author: String, val body: String) @Composable fun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body) } @Preview @Composable fun PreviewMessageCard() { MessageCard( msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!") ) }

Mã này tạo hai phần tử văn bản bên trong chế độ xem nội dung. Tuy nhiên, vì bạn chưa cung cấp thông tin nào về cách sắp xếp các yếu tố đó nên các thành phần văn bản sẽ được sắp xếp chồng lên nhau, khiến văn bản không đọc được.
Sử dụng Cột
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }

Thêm một phần tử hình ảnh
Làm phong phú thẻ thông báo của bạn bằng cách thêm ảnh hồ sơ của người gửi. Sử dụng Trình quản lý tài nguyên để nhập hình ảnh từ thư viện ảnh của bạn hoặc sử dụng mã này. Hãy thêm thành phần kết hợp Row
để có thiết kế cấu trúc hợp lý và thành phần kết hợp Image
bên trong nó.
// ... import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Row import androidx.compose.ui.res.painterResource @Composable fun MessageCard(msg: Message) { Row { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", ) Column { Text(text = msg.author) Text(text = msg.body) } } }

Định cấu hình bố cục
Bố cục của thông báo có cấu trúc phù hợp nhưng các phần tử trong đó không được giãn cách hợp lý và hình ảnh quá lớn! Để trang trí hoặc định cấu hình một thành phần kết hợp, tính năng Compose sử dụng công cụ sửa đổi. Những công cụ sửa đổi này cho phép bạn thay đổi kích thước, bố cục, giao diện của yếu tố có thể kết hợp hoặc thêm các lượt tương tác cấp cao, chẳng hạn như tạo một thành phần có thể nhấp. Bạn có thể kết nối các chuỗi này để tạo thành phần kết hợp phong phú hơn. Bạn sẽ dùng một số mã để cải thiện bố cục.
// ... import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp @Composable fun MessageCard(msg: Message) { // Add padding around our message Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", modifier = Modifier // Set image size to 40 dp .size(40.dp) // Clip image to be shaped as a circle .clip(CircleShape) ) // Add a horizontal space between the image and the column Spacer(modifier = Modifier.width(8.dp)) Column { Text(text = msg.author) // Add a vertical space between the author and message texts Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }

// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard(Message("Android", "Jetpack Compose")) } } } data class Message(val author: String, val body: String) @Composable fun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body) } @Preview @Composable fun PreviewMessageCard() { MessageCard( msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!") ) }


// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }

// ... import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Row import androidx.compose.ui.res.painterResource @Composable fun MessageCard(msg: Message) { Row { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", ) Column { Text(text = msg.author) Text(text = msg.body) } } }

// ... import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp @Composable fun MessageCard(msg: Message) { // Add padding around our message Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", modifier = Modifier // Set image size to 40 dp .size(40.dp) // Clip image to be shaped as a circle .clip(CircleShape) ) // Add a horizontal space between the image and the column Spacer(modifier = Modifier.width(8.dp)) Column { Text(text = msg.author) // Add a vertical space between the author and message texts Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }

Bài học 3: Thiết kế Material Design
Compose được xây dựng để hỗ trợ các nguyên tắc thiết kế Material Design. Nhiều phần tử thành phần trên giao diện người dùng triển khai thiết kế Material Design ngay từ đầu. Trong bài học này, bạn sẽ định kiểu cho ứng dụng bằng các tiện ích Material Design.
Sử dụng Material Design
Thiết kế thông báo của bạn hiện đã có bố cục nhưng trông vẫn chưa ổn lắm.
Jetpack Compose triển khai thiết kế Material Design và các thành phần trên giao diện người dùng ngay từ đầu. Bạn sẽ cải thiện giao diện của thành phần kết hợp MessageCard
bằng cách sử dụng thiết kế Material Design.
Để bắt đầu, hãy chèn hàm MessageCard
bằng giao diện Material đã tạo trong dự án ComposeTutorialTheme
của bạn.
Thực hiện việc đó trong cả hàm @Preview
và setContent
. Thao tác này cho phép thành phần kết hợp kế thừa các định kiểu đã được xác định trong giao diện của ứng dụng, nhờ đó đảm bảo tính nhất quán trên ứng dụng.
Material Design được dựng xoay quanh ba trụ cột: Color
, Typography
và Shape
.
Bạn sẽ thêm từng cái một.
Lưu ý: Mẫu Hoạt động Compose trống sẽ tạo một giao diện mặc định cho dự án để bạn có thể tùy chỉnh MaterialTheme
.
Nếu đặt tên cho dự án khác với tên trong ComposeTutoria, bạn có thể tìm thấy giao diện tùy chỉnh của mình ở tệp Theme.kt
trong gói con ui.theme
.
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { MessageCard(Message("Android", "Jetpack Compose")) } } } } @Preview @Composable fun PreviewMessageCard() { ComposeTutorialTheme { MessageCard( msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!") ) } }

Màu
Sử dụng MaterialTheme.colors
để tạo kiểu với các màu trong giao diện được gói vào. Bạn có thể sử dụng các giá trị này trong giao diện ở bất kỳ nơi nào cần tô màu.
Định kiểu cho tiêu đề và thêm đường viền cho hình ảnh.
// ... import androidx.compose.foundation.border import androidx.compose.material.MaterialTheme @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant ) Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }

Kiểu chữ
Các kiểu chữ Material Typography có trong MaterialTheme
, chỉ cần thêm chúng vào các thành phần kết hợp Text
.
// ... @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(4.dp)) Text( text = msg.body, style = MaterialTheme.typography.body2 ) } } }

Hình dạng
Với Shape
, bạn có thể thêm những chi tiết hoàn thiện. Trước tiên, hãy gói nội dung thông báo bằng văn bản vào xung quanh thành phần kết hợp Surface
. Theo đó cho phép tùy chỉnh hình dạng và độ cao của nội dung thông báo. Khoảng đệm cũng được thêm vào thông báo để có bố cục tốt hơn.
// ... import androidx.compose.material.Surface @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(4.dp)) Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.body2 ) } } } }

Bật giao diện tối
Bạn có thể bật giao diện tối (hoặc chế độ ban đêm) để tránh hiển thị màn hình sáng vào ban đêm hoặc để tiết kiệm pin của thiết bị. Nhờ sự hỗ trợ Material Design, Jetpack Compose có thể xử lý giao diện tối theo mặc định. Việc sử dụng màu, văn bản và nền của Material Design sẽ tự động thích ứng với nền tối.
Bạn có thể tạo nhiều bản xem trước trong tệp dưới dạng các hàm riêng biệt hoặc thêm nhiều chú thích vào cùng một hàm.
Thêm chú thích xem trước mới và bật chế độ ban đêm.
// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { MessageCard( msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!") ) } }

Lựa chọn màu cho giao diện sáng và tối được xác định trong tệp Theme.kt
do IDE tạo.
Đến đây, bạn đã tạo thành phần trên giao diện người dùng cho thông báo hiển thị một hình ảnh và hai văn bản với kiểu dáng khác nhau. Và mọi thứ trông thật tuyệt trong cả giao diện sáng lẫn tối!
// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { MessageCard( msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!") ) } }

// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { MessageCard(Message("Android", "Jetpack Compose")) } } } } @Preview @Composable fun PreviewMessageCard() { ComposeTutorialTheme { MessageCard( msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!") ) } }

// ... import androidx.compose.foundation.border import androidx.compose.material.MaterialTheme @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant ) Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }

// ... @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(4.dp)) Text( text = msg.body, style = MaterialTheme.typography.body2 ) } } }

// ... import androidx.compose.material.Surface @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(4.dp)) Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.body2 ) } } } }

// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { MessageCard( msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!") ) } }


Bài 4: Trang thông tin và ảnh động
Danh sách và ảnh động xuất hiện ở mọi nơi trong ứng dụng. Trong bài học này, bạn sẽ tìm hiểu cách ứng dụng Compose giúp dễ dàng tạo danh sách và thêm ảnh động thú vị.
Tạo trang thông tin tin nhắn
Cuộc trò chuyện chỉ có một tin nhắn cho cảm giác hơi cô đơn, vì vậy hãy thay đổi cuộc trò chuyện của bạn để có nhiều tin nhắn hơn. Bạn cần tạo một hàm Conversation
để hiển thị nhiều thông báo. Đối với trường hợp này, hãy sử dụng LazyColumn
và LazyRow
của Compose. Các thành phần kết hợp này chỉ hiển thị các phần tử hiển thị trên màn hình, vì vậy chúng được thiết kế để có hiệu quả với những trang thông tin dài.
Trong đoạn mã này, bạn có thể thấy LazyColumn
có một mục con items
. Cần phải có List
làm tham số và hàm lambda của tham số này sẽ nhận được một tham số mà chúng ta đã đặt tên là message
(chúng ta có quyền tùy ý đặt tên cho tham số này), một bản sao của Message
.
Nói ngắn gọn, hàm lambda này được gọi cho mỗi mục của List
được cung cấp. Nhập tập dữ liệu mẫu này vào dự án để giúp nhanh chóng điều chỉnh cuộc trò chuyện.
// ... import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @Composable fun Conversation(messages: List<Message>) { LazyColumn { items(messages) { message -> MessageCard(message) } } } @Preview @Composable fun PreviewConversation() { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } }

Tạo ảnh động cho tin nhắn trong khi mở rộng
Cuộc trò chuyện ngày càng thú vị hơn. Đã đến lúc phát ảnh động! Bạn sẽ bổ sung khả năng mở rộng thông báo để hiển thị một thông báo dài hơn, giúp hoạt hóa cả kích thước nội dung lẫn màu nền. Để lưu trữ trạng thái giao diện người dùng cục bộ này, bạn cần theo dõi xem thông báo có được mở rộng hay không. Để theo dõi những thay đổi về trạng thái này, bạn cần sử dụng các hàm remember
và mutableStateOf
.
Các hàm kết hợp có thể lưu trữ trạng thái cục bộ trong bộ nhớ bằng cách sử dụng remember
và theo dõi các thay đổi đối với giá trị được truyền đến mutableStateOf
. Thành phần kết hợp (và các yếu tố có thể kết hợp con) sử dụng trạng thái này sẽ được tự động vẽ lại khi giá trị này được cập nhật. Hành động này được gọi là tái kết hợp.
Khi bạn sử dụng các API trạng thái của Compose như remember
và mutableStateOf
, mọi thay đổi về trạng thái sẽ tự động cập nhật giao diện người dùng.
Lưu ý: Bạn sẽ cần thêm các mục nhập sau để sử dụng đúng by
. Alt+Enter hoặc Option+Enter sẽ thêm các mục nhập đó cho bạn.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
// ... import androidx.compose.foundation.clickable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } } } } @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, elevation = 1.dp, ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.body2 ) } } } }
Giờ đây, bạn có thể thay đổi nền của nội dung thông báo dựa trên isExpanded
khi nhấp vào một thông báo. Bạn sẽ sử dụng công cụ sửa đổi clickable
để xử lý các sự kiện nhấp chuột trên thành phần kết hợp. Thay vì chỉ chuyển đổi màu nền của Surface
, bạn sẽ hoạt hoá màu nền bằng cách sửa đổi dần giá trị của nền từ MaterialTheme.colors.surface
thành MaterialTheme.colors.primary
và ngược lại. Để thực hiện việc này, bạn cần sử dụng hàm animateColorAsState
. Cuối cùng, bạn sử dụng công cụ sửa đổi animateContentSize
để tạo hình ảnh động cho kích thước vùng chứa thông báo:
// ... import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // surfaceColor will be updated gradually from one color to the other val surfaceColor by animateColorAsState( if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface, ) // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, elevation = 1.dp, // surfaceColor color will be changing gradually from primary to surface color = surfaceColor, // animateContentSize will change the Surface size gradually modifier = Modifier.animateContentSize().padding(1.dp) ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.body2 ) } } } }
// ... import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @Composable fun Conversation(messages: List<Message>) { LazyColumn { items(messages) { message -> MessageCard(message) } } } @Preview @Composable fun PreviewConversation() { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } }

// ... import androidx.compose.foundation.clickable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } } } } @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, elevation = 1.dp, ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.body2 ) } } } }
// ... import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // surfaceColor will be updated gradually from one color to the other val surfaceColor by animateColorAsState( if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface, ) // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, elevation = 1.dp, // surfaceColor color will be changing gradually from primary to surface color = surfaceColor, // animateContentSize will change the Surface size gradually modifier = Modifier.animateContentSize().padding(1.dp) ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.body2 ) } } } }
Các bước tiếp theo
Xin chúc mừng, bạn đã hoàn tất hướng dẫn Compose! Bạn đã xây dựng một màn hình trò chuyện đơn giản, hiển thị hiệu quả một danh sách tin nhắn có thể mở rộng và ảnh động chứa hình ảnh và văn bản, được thiết kế bằng nguyên tắc Material Design với giao diện tối và bản xem trước — tất cả trong dưới 100 dòng mã!
Sau đây là những điều bạn đã tìm hiểu cho đến thời điểm này:
- Xác định các hàm có khả năng kết hợp
- Thêm các thành phần trong nội dung kết hợp
- Định cấu trúc thành phần giao diện người dùng bằng cách sử dụng kết hợp bố cục
- Mở rộng nội dung kết hợp bằng cách sử dụng công cụ sửa đổi
- Tạo danh sách hiệu quả
- Theo dõi trạng thái và sửa đổi trạng thái
- Thêm lượt tương tác của người dùng trên một nội dung kết hợp
- Tạo ảnh động cho tin nhắn trong khi mở rộng
Nếu bạn muốn tìm hiểu sâu hơn về một số bước này, hãy khám phá các tài nguyên dưới đây.