Hướng dẫn

Hướng dẫn về Jetpack Compose

Jetpack Compose là một bộ công cụ hiện đại giúp xây dựng giao diện người dùng gốc của Android. Jetpack Compose đơn giản hoá và tăng tốc độ phát triển giao diện người dùng trên Android nhờ mã ngắn hơn, các công cụ mạnh mẽ và API Kotlin trực quan.

Trong hướng dẫn này, bạn sẽ tạo một thành phần giao diện người dùng đơn giản có các hàm khai báo. Bạn sẽ không chỉnh sửa bất kỳ bố cục XML hoặc sử dụng Trình chỉnh sửa bố cục nào. Thay vào đó, bạn sẽ gọi các hàm có khả năng kết hợp để xác định phần tử mình muốn, và trình biên dịch sẽ làm phần việc còn lại.

Bản xem trước đầy đủ
Bản xem trước đầy đủ

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!")
        }
    }
}
  
hiện nội dung xem trước
ẩn nội dung xem trước

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

  
hiện nội dung xem trước
ẩn nội dung xem trước

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")
}
  
hiện nội dung xem trước
ẩn nội dung xem trước

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.

Xem trước hàm kết hợp trong Android Studio
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!")
        }
    }
}
  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...
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!")
}

  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
hiện nội dung xem trước
ẩn nội dung xem trước
Xem trước hàm kết hợp trong Android Studio

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 qua 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!")
    )
}

  
hiện nội dung xem trước
ẩn nội dung xem trước

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

Hàm Column cho phép bạn sắp xếp các phần tử theo chiều dọc. Thêm Column vào hàm MessageCard.
Bạn có thể sử dụng Row để sắp xếp các mục theo chiều ngang và Box để nhóm các phần tử.

// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

hiện nội dung xem trước
ẩn nội dung xem trước

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)
        }
  
    }
  
}
  
hiện nội dung xem trước
ẩn nội dung xem trước

Đị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)
        }
    }
}
  
hiện nội dung xem trước
ẩn nội dung 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!")
    )
}

  
hiện nội dung xem trước
ẩn nội dung xem trước
Xem trước 2 thành phần kết hợp của văn bản trùng lặp
// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

hiện nội dung xem trước
ẩn nội dung xem trước
// ...
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)
        }
  
    }
  
}
  
hiện nội dung xem trước
ẩn nội dung xem trướ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)
        }
    }
}
  
hiện nội dung xem trước
ẩn nội dung xem trước

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 gói hàm MessageCard bằng giao diện Material đã tạo trong dự án ComposeTutorialTheme của bạn cũng như Surface. Thực hiện việc này trong cả hàm @PreviewsetContent. 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, TypographyShape. 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 {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}

  
hiện nội dung xem trước
ẩn nội dung xem trước

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

  
hiện nội dung xem trước
ẩn nội dung xem trước

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

  
hiện nội dung xem trước
ẩn nội dung xem trước

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

  
hiện nội dung xem trước
ẩn nội dung xem trước

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 {
    Surface {
      MessageCard(
        msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
hiện nội dung xem trước
ẩn nội dung xem trước

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 {
    Surface {
      MessageCard(
        msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}

  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...
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)
       }
   }
}

  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...

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

  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...
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
               )
           }
       }
   }
}

  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...
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 {
    Surface {
      MessageCard(
        msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
hiện nội dung xem trước
ẩn nội dung xem trước
Hình 3. Bản xem trước hiển thị cả thành phần kết hợp theo chủ đề sáng và tối.

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

  
hiện nội dung xem trước
ẩn nội dung xem trước

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 remembermutableStateOf.

Các hàm có thể 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ư remembermutableStateOf, 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
                )
            }
        }
    }
}

  
hiện nội dung xem trước
ẩn nội dung xem trước

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

  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...
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)
    }
}

  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...
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
                )
            }
        }
    }
}

  
hiện nội dung xem trước
ẩn nội dung xem trước
// ...
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
                )
            }
        }
    }
}

  
hiện nội dung xem trước
ẩn nội dung xem trước

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.

Các bước tiếp theo

Thiết lập
Giờ đây, khi đã kết thúc phần hướng dẫn về tính năng Compose, bạn có thể bắt đầu tạo ứng dụng bằng Compose.
Lộ trình
Hãy khám phá lộ trình của chúng tôi gồm các lớp học lập trình và video giúp bạn tìm hiểu và nắm bắt thông tin về Jetpack Compose.