Đối tượng sửa đổi Compose

Đối tượng sửa đổi (modifier) cho phép bạn trang trí hoặc tăng cường hoạt động của thành phần kết hợp. Đối tượng sửa đổi cho phép bạn thực hiện những việc sau:

  • Thay đổi kích thước, bố cục, hành vi và giao diện của thành phần kết hợp
  • Thêm thông tin, như nhãn hỗ trợ tiếp cận
  • Xử lý dữ liệu do người dùng nhập
  • Thêm các lượt tương tác cấp cao, như làm cho một thành phần có thể nhấp vào, cuộn được, có thể kéo hoặc thu phóng

Đối tượng sửa đổi là các đối tượng Kotlin chuẩn. Tạo một đối tượng sửa đổi bằng cách gọi một trong các hàm lớp Modifier:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Hai dòng văn bản trên nền màu, có khoảng đệm xung quanh văn bản.

Bạn có thể liên kết các hàm này với nhau để kết hợp chúng:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Nền màu này phía sau văn bản hiện sẽ mở rộng ra toàn bộ chiều rộng của thiết bị.

Trong mã trên, hãy chú ý đến các hàm khác nhau của đối tượng sửa đổi được sử dụng cùng nhau.

  • padding đặt khoảng trống xung quanh một phần tử.
  • fillMaxWidth làm cho thành phần kết hợp lấp đầy chiều rộng tối đa mà thành phần mẹ đã cấp cho nó.

Phương pháp hay nhất là khiến tất cả các thành phần kết hợp đều chấp nhận tham số modifier và chuyển đối tượng sửa đổi đó đến phần tử con đầu tiên tạo ra giao diện người dùng. Khi làm như vậy, mã của bạn có thể sử dụng lại dễ dàng hơn và làm cho hành vi của mã dễ dự đoán và trực quan hơn. Để biết thêm thông tin, hãy xem nguyên tắc về API Compose, Các thành phần chấp nhận và tuân theo một tham số của Đối tượng sửa đổi.

Thứ tự của các đối tượng sửa đổi rất quan trọng

Thứ tự các hàm đối tượng sửa đổi là rất quan trọng. Do mỗi hàm thực hiện các thay đổi cho Modifier do hàm trước đó trả về nên trình tự sẽ ảnh hưởng đến kết quả cuối cùng. Chúng ta hãy cùng xem một ví dụ về cách này:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Toàn bộ khu vực, bao gồm cả khoảng đệm xung quanh các cạnh, phản hồi các lượt nhấp

Trong mã phía trên toàn bộ khu vực, có thể nhấp vào, bao gồm cả các khoảng đệm xung quanh, bởi vì đối tượng sửa đổi padding đã được áp dụng sau đối tượng sửa đổi clickable. Nếu thứ tự các đối tượng sửa đổi bị đảo ngược, thì dấu cách do padding thêm sẽ không phản ứng với thao tác nhập của người dùng:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Khoảng đệm quanh cạnh bố cục không còn phản hồi lượt nhấp

Đối tượng sửa đổi tích hợp

Jetpack Compose cung cấp một danh sách các đối tượng sửa đổi tích hợp để giúp bạn trang trí hoặc tích hợp một thành phần kết hợp. Dưới đây là một số đối tượng sửa đổi phổ biến mà bạn sẽ sử dụng để điều chỉnh bố cục của mình.

paddingsize

Theo mặc định, các bố cục có trong Compose sẽ gói phần tử con. Tuy nhiên, bạn có thể đặt kích thước bằng cách sử dụng đối tượng sửa đổi size:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

Lưu ý rằng kích thước bạn chỉ định có thể không được tuân thủ nếu kích thước không đáp ứng các ràng buộc đến từ nguồn gốc của bố cục. Nếu bạn yêu cầu cố định kích thước của thành phần kết hợp bất kể các hạn chế đến, hãy sử dụng đối tượng sửa đổi requiredSize:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

Hình ảnh con lớn hơn các giới hạn từ nguồn mẹ

Trong ví dụ này, ngay cả khi phần tử mẹ height được đặt thành 100.dp, chiều cao của Image sẽ là 150.dp, vì đối tượng sửa đổi requiredSize được ưu tiên.

Nếu bạn muốn bố cục con lấp đầy tất cả chiều cao mà phần tử mẹ cho phép, hãy thêm đối tượng sửa đổi fillMaxHeight (Compose cũng cung cấp fillMaxSizefillMaxWidth):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

Chiều cao của hình ảnh lớn bằng phần tử mẹ

Để thêm khoảng đệm xung quanh một phần tử, hãy đặt một đối tượng sửa đổi padding.

Nếu bạn muốn thêm khoảng đệm phía trên một đường cơ sở của văn bản để đạt được một khoảng cách cụ thể từ đầu bố cục đến đường cơ sở, hãy sử dụng đối tượng sửa đổi paddingFromBaseline.

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

Văn bản có khoảng đệm phía trên

Độ lệch

Để định vị một bố cục so với vị trí ban đầu, hãy thêm đối tượng sửa đổi offset và đặt độ lệch trong trục xy. Mức chênh lệch có thể mang giá trị dương hoặc không dương. Sự khác biệt giữa paddingoffset là việc thêm offset vào một thành phần kết hợp không thay đổi hoạt động đo lường:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Văn bản được chuyển sang bên phải vùng chứa gốc

Đối tượng sửa đổi offset được áp dụng theo chiều ngang theo hướng bố cục. Trong ngữ cảnh từ trái sang phải, một offset dương chuyển phần tử sang bên phải, trong khi trong ngữ cảnh từ phải sang trái, thuộc tính này dịch chuyển phần tử sang trái. Nếu bạn cần đặt một mức chênh lệch mà không xem xét đến hướng bố cục, hãy xem đối tượng sửa đổi absoluteOffset, trong đó giá trị chênh lệch dương luôn chuyển phần tử sang bên phải.

Đối tượng sửa đổi offset cung cấp 2 phương thức nạp chồng – offset lấy mức chênh lệch làm tham số và offset lấy giá trị lambda. Để biết thêm thông tin chuyên sâu về thời điểm sử dụng từng đối tượng này và cách tối ưu hoá hiệu suất, hãy đọc phần Hiệu suất của Compose – Trì hoãn việc đọc càng lâu càng tốt.

Phạm vi an toàn trong Compose

Trong Compose có các đối tượng sửa đổi chỉ có thể dùng khi áp dụng cho thành phần con của một số thành phần kết hợp cụ thể. Compose thực thi việc này bằng phạm vi tuỳ chỉnh.

Ví dụ: nếu bạn muốn cho thành phần con lớn bằng thành phần mẹ Box mà không ảnh hưởng đến kích thước của Box, hãy dùng đối tượng sửa đổi matchParentSize. matchParentSize chỉ có trong BoxScope. Do đó, bạn chỉ có thể dùng đối tượng này cho thành phần con trong thành phần mẹ Box.

Phạm vi an toàn ngăn bạn thêm đối tượng sửa đổi không hoạt động trong các thành phần kết hợp và phạm vi khác, đồng thời giúp tiết kiệm thời gian thử và sai.

Đối tượng sửa đổi có giới hạn thông báo cho yếu tố mẹ về một số thông tin mà thành phần mẹ cần biết về thành phần con. Đây thường được gọi là đối tượng sửa đổi dữ liệu gốc. Bên trong chúng khác với đối tượng sửa đổi mục đích chung, nhưng xét về góc độ sử dụng thì những khác biệt này không quan trọng.

matchParentSize trong Box

Như đã đề cập ở trên, nếu bạn muốn bố cục con có cùng kích thước với bố cục mẹ Box mà không ảnh hưởng đến kích thước Box, hãy sử dụng đối tượng sửa đổi matchParentSize.

Xin lưu ý rằng matchParentSize chỉ có trong phạm vi Box, có nghĩa là chỉ áp dụng cho phần tử con trực tiếp của thành phần kết hợp Box.

Trong ví dụ dưới đây, thành phần con Spacer có kích thước từ thành phần mẹ Box, điều này lấy kích thước từ thành phần con lớn nhất, ArtistCard trong trường hợp này.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

Nền xám lấp đầy vùng chứa

Nếu fillMaxSize được sử dụng thay cho matchParentSize, thì Spacer sẽ nhận tất cả không gian trống có sẵn cho mạng gốc, đến lượt thành phần mẹ sẽ mở rộng và lấp đầy tất cả không gian có sẵn.

Nền màu xám lấp đầy màn hình

weight trong RowColumn

Như bạn đã thấy trong phần trước về Khoảng đệm và kích thước, theo mặc định, kích thước của thành phần kết hợp được xác định bởi nội dung mà phần tử này gói lại. Bạn có thể đặt kích thước của thành phần kết hợp để linh hoạt trong phần tử gốc bằng cách sử dụng Đối tượng sửa đổi weight chỉ có trong RowScopeColumnScope.

Hãy lấy một Row chứa 2 thành phần kết hợp Box. Hộp đầu tiên được gán 2 lần weight của giây thứ hai, do đó, hộp này được gấp đôi chiều rộng. Vì Row rộng 210.dp, Box đầu tiên có chiều rộng 140.dp và giá trị thứ hai là 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

Chiều rộng của hình ảnh gấp đôi chiều rộng văn bản

Trích xuất và sử dụng lại đối tượng sửa đổi

Bạn có thể nhóm nhiều đối tượng sửa đổi với nhau để trang trí hoặc tích hợp một thành phần kết hợp. Chuỗi này được tạo thông qua giao diện Modifier, đại diện cho danh sách đơn lẻ không thể thay đổi của Modifier.Elements.

Mỗi Modifier.Element đại diện cho một hành vi riêng lẻ, như bố cục, vẽ và đồ hoạ, tất cả hành vi liên quan đến cử chỉ, tiêu điểm và ngữ nghĩa, cũng như các sự kiện nhập thiết bị. Thứ tự của các phần tử này rất quan trọng: các phần tử sửa đổi được thêm vào trước sẽ được áp dụng đầu tiên.

Đôi khi, việc sử dụng lại các thực thể chuỗi đối tượng sửa đổi trong nhiều thành phần kết hợp có thể mang lại lợi ích, bằng cách trích xuất các phiên bản đó thành các biến và di chuyển chúng lên các phạm vi cao hơn. Tính năng này có thể cải thiện khả năng đọc mã hoặc giúp cải thiện hiệu suất của ứng dụng vì một số lý do:

  • Quá trình phân bổ lại của đối tượng sửa đổi sẽ không được lặp lại khi quá trình kết hợp lại diễn ra cho các thành phần kết hợp có thể sử dụng
  • Các chuỗi đối tượng sửa đổi có thể rất dài và phức tạp, vì vậy, việc sử dụng lại cùng một phiên bản của một chuỗi có thể giảm bớt thời gian chạy khối lượng công việc mà Compose cần thực hiện khi so sánh các chuỗi đó
  • Hoạt động trích xuất này giúp tăng độ sạch sẽ, tính nhất quán và khả năng bảo trì của mã trên cơ sở mã

Các phương pháp hay nhất để sử dụng lại đối tượng sửa đổi

Tạo chuỗi Modifier của riêng bạn và trích xuất các chuỗi đó để sử dụng lại trên nhiều thành phần kết hợp. Bạn chỉ cần lưu một đối tượng sửa đổi, vì công cụ này là các đối tượng giống dữ liệu:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

Trích xuất và sử dụng lại đối tượng sửa đổi khi quan sát thấy trạng thái thường xuyên thay đổi

Khi bạn quan sát thấy các trạng thái thường xuyên thay đổi bên trong các thành phần kết hợp, như trạng thái động hoặc scrollState, có thể sẽ có một lượng tái cấu trúc đáng kể. Trong trường hợp này, đối tượng sửa đổi sẽ được phân bổ cho mọi quá trình kết hợp lại và có thể cho mọi khung:

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

Thay vào đó, bạn có thể tạo, trích xuất và sử dụng lại cùng một phiên bản của đối tượng sửa đổi và chuyển phiên bản đó cho thành phần kết hợp như sau:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

Trích xuất và sử dụng lại đối tượng sửa đổi không theo phạm vi

Các đối tượng sửa đổi có thể không theo phạm vi hoặc thuộc phạm vi của thành phần kết hợp cụ thể. Trong trường hợp đối tượng sửa đổi không theo phạm vi, bạn có thể dễ dàng trích xuất các đối tượng sửa đổi đó bên ngoài bất kỳ thành phần kết hợp nào dưới dạng biến đơn giản:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

Điều này đặc biệt có lợi khi kết hợp với bố cục Lazy (Tải từng phần). Trong hầu hết các trường hợp, bạn sẽ muốn tất cả các mục có khả năng quan trọng có cùng một đối tượng sửa đổi:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

Trích xuất và sử dụng lại đối tượng sửa đổi theo phạm vi

Khi xử lý các đối tượng sửa đổi trong phạm vi của một số thành phần kết hợp nhất định, bạn có thể trích xuất các đối tượng sửa đổi đó đến mức cao nhất có thể và sử dụng lại nếu thích hợp:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

Bạn chỉ nên truyền đối tượng sửa đổi được trích xuất và trong phạm vi đến thành phần con trực tiếp có cùng phạm vi. Hãy xem phần Phạm vi an toàn trong Compose để tham khảo thêm về lý do lý do tại sao điều này lại quan trọng:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

Nối dài chuỗi các đối tượng sửa đổi được trích xuất

Bạn có thể liên kết thêm hoặc thêm các chuỗi đối tượng sửa đổi được trích xuất bằng cách gọi hàm .then():

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

Bạn chỉ cần lưu ý rằng thứ tự của các đối tượng sửa đổi rất quan trọng!

Tìm hiểu thêm

Chúng tôi cung cấp một danh sách đầy đủ các đối tượng sửa đổi, cùng với các tham số và phạm vi của đối tượng sửa đổi.

Để thực hành thêm về cách sử dụng đối tượng sửa đổi, bạn cũng có thể xem Lớp học lập trình Bố cục cơ bản trong Compose và tham khảo Hiện có trong kho lưu trữ Android.

Để biết thêm thông tin về đối tượng sửa đổi tuỳ chỉnh và cách tạo chúng, hãy xem tài liệu về Bố cục tuỳ chỉnh – Sử dụng đối tượng sửa đổi bố cục.