Đối tượng sửa đổi đồ hoạ

Ngoài thành phần kết hợp (composable) Canvas, Compose còn có một số Modifiers đồ hoạ hữu ích hỗ trợ vẽ nội dung tuỳ chỉnh. Các đối tượng sửa đổi này hữu ích vì có thể áp dụng cho bất kỳ thành phần kết hợp nào.

Đối tượng sửa đổi bản vẽ

Tất cả các lệnh vẽ đều được thực hiện bằng đối tượng sửa đổi bản vẽ trong Compose. Có 3 đối tượng sửa đổi bản vẽ chính trong Compose:

Đối tượng sửa đổi cơ sở cho bản vẽ là drawWithContent. Với đối tượng này, bạn có thể quyết định thứ tự vẽ của Thành phần kết hợp và các lệnh vẽ được đưa ra trong đối tượng sửa đổi. drawBehind là một trình bao bọc tiện lợi xung quanh drawWithContent, có thứ tự vẽ được đặt phía sau nội dung của thành phần kết hợp. drawWithCache gọi onDrawBehind hoặc onDrawWithContent bên trong nó, đồng thời cung cấp cơ chế để lưu các đối tượng được tạo trong đó vào bộ nhớ đệm.

Modifier.drawWithContent: Chọn thứ tự vẽ

Với Modifier.drawWithContent, bạn có thể thực hiện các thao tác DrawScope trước hoặc sau nội dung của thành phần kết hợp. Hãy nhớ gọi drawContent để hiển thị nội dung thực tế của thành phần kết hợp. Với đối tượng sửa đổi này, bạn có thể quyết định thứ tự của các thao tác, liệu bạn muốn vẽ nội dung trước hay sau các thao tác vẽ tuỳ chỉnh.

Ví dụ: nếu muốn hiển thị kiểu chuyển màu dạng hình tròn trên nội dung để tạo hiệu ứng đèn pin chiếu qua lỗ khoá trên giao diện người dùng, bạn có thể làm như sau:

var pointerOffset by remember {
    mutableStateOf(Offset(0f, 0f))
}
Column(
    modifier = Modifier
        .fillMaxSize()
        .pointerInput("dragging") {
            detectDragGestures { change, dragAmount ->
                pointerOffset += dragAmount
            }
        }
        .onSizeChanged {
            pointerOffset = Offset(it.width / 2f, it.height / 2f)
        }
        .drawWithContent {
            drawContent()
            // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI.
            drawRect(
                Brush.radialGradient(
                    listOf(Color.Transparent, Color.Black),
                    center = pointerOffset,
                    radius = 100.dp.toPx(),
                )
            )
        }
) {
    // Your composables here
}

Hình 1: Modifier.drawWithContent được sử dụng trên một Thành phần kết hợp để tạo ra trải nghiệm giao diện người dùng có hiệu ứng đèn pin.

Modifier.drawBehind: Vẽ sau một thành phần kết hợp

Modifier.drawBehind cho phép bạn thực hiện các thao tác DrawScope phía sau nội dung thành phần kết hợp được vẽ trên màn hình. Nếu xem cách triển khai Canvas, bạn có thể nhận thấy đây chỉ là một trình bao bọc tiện lợi xung quanh Modifier.drawBehind.

Cách vẽ một hình chữ nhật góc tròn phía sau Text:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawBehind {
            drawRoundRect(
                Color(0xFFBBAAEE),
                cornerRadius = CornerRadius(10.dp.toPx())
            )
        }
        .padding(4.dp)
)

Điều này dẫn đến kết quả sau:

Văn bản và nền được vẽ bằng Modifier.drawBehind
Hình 2: Văn bản và nền được vẽ bằng Modifier.drawBehind

Modifier.drawWithCache: Vẽ và lưu đối tượng vẽ vào bộ nhớ đệm

Modifier.drawWithCache lưu các đối tượng được tạo trong đó vào bộ nhớ đệm. Các đối tượng sẽ được lưu vào bộ nhớ đệm miễn là kích thước của vùng vẽ giống nhau hoặc bất kỳ đối tượng trạng thái nào được đọc không thay đổi. Đối tượng sửa đổi này rất hữu ích trong việc cải thiện hiệu suất của các hàm gọi vẽ, lý do là vì bạn không cần phân bổ lại các đối tượng (chẳng hạn như Brush, Shader, Path, v.v.) được tạo trên bản vẽ.

Ngoài ra, bạn cũng có thể lưu các đối tượng vào bộ nhớ đệm bằng remember bên ngoài đối tượng sửa đổi. Tuy nhiên, điều này không phải luôn khả thi vì không phải lúc nào bạn cũng có quyền truy cập vào thành phần kết hợp. Việc sử dụng drawWithCache có thể hiệu quả hơn nếu bạn chỉ dùng các đối tượng để vẽ.

Ví dụ: nếu bạn tạo một Brush để vẽ một vùng chuyển màu phía sau Text, thì việc sử dụng drawWithCache sẽ lưu đối tượng Brush vào bộ nhớ đệm cho đến khi kích thước của vùng vẽ thay đổi:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawWithCache {
            val brush = Brush.linearGradient(
                listOf(
                    Color(0xFF9E82F0),
                    Color(0xFF42A5F5)
                )
            )
            onDrawBehind {
                drawRoundRect(
                    brush,
                    cornerRadius = CornerRadius(10.dp.toPx())
                )
            }
        }
)

Lưu đối tượng Brush vào bộ nhớ đệm bằng drawWithCache
Hình 3: Lưu đối tượng Brush vào bộ nhớ đệm bằng drawWithCache

Đối tượng sửa đổi đồ hoạ

Modifier.graphicsLayer: Áp dụng phép biến đổi cho thành phần kết hợp

Modifier.graphicsLayer là đối tượng sửa đổi giúp nội dung của thành phần kết hợp được vẽ thành một lớp vẽ. Một lớp sẽ cung cấp vài loại chức năng, chẳng hạn như:

  • Tách biệt lệnh vẽ (tương tự với RenderNode). Lệnh vẽ được ghi dưới dạng một cấu phần của lớp có thể được thực hiện lại một cách hiệu quả bằng quy trình kết xuất mà không cần thực thi lại mã xử lý ứng dụng.
  • Các phép biến đổi áp dụng cho mọi lệnh vẽ có trong một lớp.
  • Tạo điểm ảnh cho các tính năng kết hợp. Khi tạo điểm ảnh cho một lớp, các lệnh vẽ của lớp đó sẽ được thực thi và đầu ra sẽ được ghi vào vùng đệm ngoài màn hình. Việc kết hợp vùng đệm như vậy cho các khung tiếp theo sẽ nhanh hơn so với việc thực thi riêng từng lệnh, nhưng lớp sẽ hoạt động như một bitmap khi áp dụng các phép biến đổi như điều chỉnh theo tỷ lệ hoặc xoay.

Phép biến đổi

Modifier.graphicsLayer cung cấp tính năng tách biệt lệnh vẽ; ví dụ: bạn có thể áp dụng nhiều phép biến đổi bằng Modifier.graphicsLayer. Bạn có thể tạo ảnh động hoặc chỉnh sửa các phép biến đổi này mà không cần thực thi lại hàm lambda vẽ.

Modifier.graphicsLayer không thay đổi kích thước hoặc vị trí được đo lường của thành phần kết hợp, vì đối tượng chỉnh sửa này chỉ ảnh hưởng đến giai đoạn vẽ. Điều này có nghĩa là thành phần kết hợp của bạn có thể trùng lặp với những thành phần kết hợp khác nếu hình vẽ vượt ra ngoài các giới hạn bố cục.

Bạn có thể áp dụng các phép biến đổi sau bằng đối tượng sửa đổi này:

Điều chỉnh theo tỷ lệ – tăng kích thước

scaleXscaleY lần lượt có chức năng là phóng to hoặc thu nhỏ nội dung theo chiều ngang hoặc chiều dọc. Giá trị 1.0f cho biết không có thay đổi về tỷ lệ, giá trị 0.5f có nghĩa là một nửa kích thước.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.scaleX = 1.2f
            this.scaleY = 0.8f
        }
)

Hình 4: scaleX và scaleY áp dụng cho thành phần kết hợp Image
Phép tịnh tiến

Bạn có thể thay đổi translationXtranslationY bằng graphicsLayer, translationX di chuyển thành phần kết hợp sang trái hoặc phải. translationY di chuyển thành phần kết hợp lên hoặc xuống.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.translationX = 100.dp.toPx()
            this.translationY = 10.dp.toPx()
        }
)

Hình 5: translationX và translationY áp dụng cho Hình ảnh với Modifier.graphicsLayer
Phép xoay

Đặt rotationX để xoay theo chiều ngang, rotationY để xoay theo chiều dọc và rotationZ để xoay trên trục Z (phép xoay chuẩn). Giá trị này được chỉ định bằng độ (0-360).

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

Hình 6: rotationX, rotationY và rotationZ được đặt trên Hình ảnh bằng Modifier.graphicsLayer
Điểm gốc

Bạn có thể chỉ định transformOrigin. Sau đó, giá trị này sẽ được dùng làm điểm thực hiện các phép biến đổi. Tất cả các ví dụ đã trình bày đều sử dụng TransformOrigin.Center, tại điểm (0.5f, 0.5f). Nếu bạn chỉ định điểm gốc tại (0f, 0f), các phép biến đổi sẽ bắt đầu từ góc trên cùng bên trái của thành phần kết hợp.

Nếu thay đổi điểm gốc bằng phép biến đổi rotationZ, bạn có thể thấy mục xoay quanh phía trên cùng bên trái của thành phần kết hợp:

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.transformOrigin = TransformOrigin(0f, 0f)
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

Hình 7: Phép xoay được áp dụng với TransformOrigin đặt thành 0f, 0f

Phần cắt và hình dạng

Hình dạng chỉ định đường viền mà nội dung được cắt khi clip = true. Trong ví dụ này, chúng ta đặt 2 hộp có 2 loại phần cắt – một hộp dùng biến cắt graphicsLayer và hộp còn lại sử dụng trình bao bọc tiện lợi Modifier.clip.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .size(200.dp)
            .graphicsLayer {
                clip = true
                shape = CircleShape
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }
    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(CircleShape)
            .background(Color(0xFF4DB6AC))
    )
}

Nội dung của hộp đầu tiên (văn bản có nội dung "Hello Compose") được cắt theo hình tròn:

Phần cắt áp dụng cho thành phần kết hợp Box
Hình 8: Phần cắt áp dụng cho thành phần kết hợp Box

Sau đó, nếu áp dụng translationY vào hình tròn màu hồng trên cùng, bạn sẽ thấy các ranh giới của Thành phần kết hợp vẫn giữ nguyên, nhưng hình tròn này sẽ được vẽ phía dưới hình tròn dưới cùng (và bên ngoài các ranh giới của nó).

Phần cắt được áp dụng với translationY và đường bao màu đỏ cho đường viền
Hình 9: Phần cắt được áp dụng với translationY và đường bao màu đỏ cho đường viền

Để cắt thành phần kết hợp theo vùng được vẽ, bạn có thể thêm một Modifier.clip(RectangleShape) khác ở đầu chuỗi đối tượng sửa đổi. Nội dung sẽ vẫn nằm trong các ranh giới ban đầu.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .clip(RectangleShape)
            .size(200.dp)
            .border(2.dp, Color.Black)
            .graphicsLayer {
                clip = true
                shape = CircleShape
                translationY = 50.dp.toPx()
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }

    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(RoundedCornerShape(500.dp))
            .background(Color(0xFF4DB6AC))
    )
}

Phần cắt được áp dụng trên phép biến đổi graphicsLayer
Hình 10: Phần cắt được áp dụng trên phép biến đổi graphicsLayer

Alpha

Bạn có thể dùng Modifier.graphicsLayer để đặt alpha (độ mờ) cho toàn bộ lớp. 1.0f là mờ hoàn toàn và 0.0f là không hiển thị.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "clock",
    modifier = Modifier
        .graphicsLayer {
            this.alpha = 0.5f
        }
)

Hình ảnh có áp dụng giá trị alpha
Hình 11: Hình ảnh có áp dụng alpha

Chiến lược kết hợp

Việc xử lý alpha và độ trong suốt có thể không đơn giản như việc thay đổi một giá trị alpha đơn lẻ. Ngoài việc thay đổi alpha, bạn cũng có thể thiết lập CompositingStrategy trên graphicsLayer. CompositingStrategy xác định cách kết hợp nội dung của thành phần kết hợp với nội dung khác đã được vẽ trên màn hình.

Có các chiến lược sau:

Tự động (mặc định)

Chiến lược kết hợp này do các tham số graphicsLayer còn lại xác định. Chiến lược này kết xuất lớp trong một vùng đệm ngoài màn hình nếu alpha nhỏ hơn 1.0f hoặc một RenderEffect được thiết lập. Bất cứ khi nào giá trị alpha nhỏ hơn 1f, một lớp kết hợp sẽ tự động được tạo để hiển thị nội dung, sau đó vẽ vùng đệm ngoài màn hình này vào đích đến bằng giá trị alpha tương ứng. Việc thiết lập RenderEffect hoặc hiệu ứng cuộn quá mức sẽ luôn kết xuất nội dung tại vùng đệm ngoài màn hình bất kể bạn thiết lập CompositingStrategy như thế nào.

Ngoài màn hình

Nội dung của thành phần kết hợp luôn được tạo điểm ảnh thành kết cấu hoặc bitmap ngoài màn hình trước khi hiển thị tại đích đến. Điều này hữu ích khi áp dụng các thao tác BlendMode để che nội dung, cũng như giúp cải thiện hiệu suất khi kết xuất các tập hợp lệnh vẽ phức tạp.

Một ví dụ về cách sử dụng CompositingStrategy.Offscreen là với BlendModes. Hãy xem ví dụ dưới đây. Giả sử bạn muốn xoá các phần của thành phần kết hợp Image bằng cách đưa ra một lệnh vẽ sử dụng BlendMode.Clear. Nếu bạn không thiết lập compositingStrategy thành CompositingStrategy.Offscreen, thì BlendMode sẽ tương tác với tất cả nội dung bên dưới nó.

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = "Dog",
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(120.dp)
        .aspectRatio(1f)
        .background(
            Brush.linearGradient(
                listOf(
                    Color(0xFFC5E1A5),
                    Color(0xFF80DEEA)
                )
            )
        )
        .padding(8.dp)
        .graphicsLayer {
            compositingStrategy = CompositingStrategy.Offscreen
        }
        .drawWithCache {
            val path = Path()
            path.addOval(
                Rect(
                    topLeft = Offset.Zero,
                    bottomRight = Offset(size.width, size.height)
                )
            )
            onDrawWithContent {
                clipPath(path) {
                    // this draws the actual image - if you don't call drawContent, it wont
                    // render anything
                    this@onDrawWithContent.drawContent()
                }
                val dotSize = size.width / 8f
                // Clip a white border for the content
                drawCircle(
                    Color.Black,
                    radius = dotSize,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    ),
                    blendMode = BlendMode.Clear
                )
                // draw the red circle indication
                drawCircle(
                    Color(0xFFEF5350), radius = dotSize * 0.8f,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    )
                )
            }
        }
)

Bằng cách đặt CompositingStrategy thành Offscreen, một kết cấu ngoài màn hình sẽ được tạo để thực thi các lệnh trên đó (chỉ áp dụng BlendMode cho nội dung của thành phần kết hợp này). Sau đó, hàm này sẽ hiển thị trên nội dung đã hiển thị trên màn hình mà không ảnh hưởng đến nội dung đã vẽ.

Modifier.drawWithContent trên Hình ảnh hiển thị chỉ báo vòng tròn, với BlendMode.Clear bên trong ứng dụng
Hình 12: Modifier.drawWithContent trên Hình ảnh hiển thị chỉ báo vòng tròn, với BlendMode.Clear và CompositingStrategy.Offscreen bên trong ứng dụng

Nếu bạn không sử dụng CompositingStrategy.Offscreen, kết quả áp dụng BlendMode.Clear sẽ xoá mọi pixel trong đích đến, bất kể bạn đã đặt giá trị nào, theo đó, vùng đệm hiển thị của cửa sổ (màu đen) sẽ xuất hiện. Nhiều BlendModes liên quan đến alpha sẽ không hoạt động như mong đợi nếu không có vùng đệm ngoài màn hình. Hãy lưu ý vòng tròn màu đen xung quanh chỉ báo vòng tròn màu đỏ:

Modifier.drawWithContent trên Hình ảnh cho thấy chỉ báo vòng tròn, với BlendMode.Clear và không đặt CompositingStrategy
Hình 13: Modifier.drawWithContent trên Hình ảnh cho thấy chỉ báo vòng tròn, với BlendMode.Clear và không đặt CompositingStrategy

Để hiểu rõ hơn về điều này: nếu ứng dụng có nền cửa sổ nửa trong suốt và bạn không sử dụng CompositingStrategy.Offscreen, thì BlendMode sẽ tương tác với toàn bộ ứng dụng. Chế độ này sẽ xoá mọi pixel để hiển thị ứng dụng hoặc hình nền phía dưới, như trong ví dụ này:

Không đặt CompositingStrategy và sử dụng BlendMode.Clear với một ứng dụng có nền cửa sổ nửa trong suốt. Nền màu hồng được hiển thị qua vùng xung quanh vòng tròn trạng thái màu đỏ.
Hình 14: Không đặt CompositingStrategy và sử dụng BlendMode.Clear với một ứng dụng có nền cửa sổ nửa trong suốt. Hãy lưu ý cách mà nền màu hồng được hiển thị trong vùng xung quanh vòng tròn trạng thái màu đỏ.

Điểm đáng chú ý là khi sử dụng CompositingStrategy.Offscreen, một kết cấu ngoài màn hình có kích thước của vùng vẽ sẽ được tạo và hiển thị lại trên màn hình. Theo mặc định, mọi lệnh vẽ được thực hiện bằng chiến lược này đều được cắt theo vùng này. Đoạn mã dưới đây minh hoạ các điểm khác biệt khi chuyển sang dùng kết cấu ngoài màn hình:

@Composable
fun CompositingStrategyExamples() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.Center)
    ) {
        // Does not clip content even with a graphics layer usage here. By default, graphicsLayer
        // does not allocate + rasterize content into a separate layer but instead is used
        // for isolation. That is draw invalidations made outside of this graphicsLayer will not
        // re-record the drawing instructions in this composable as they have not changed
        Canvas(
            modifier = Modifier
                .graphicsLayer()
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            // ... and drawing a size of 200 dp here outside the bounds
            drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }

        Spacer(modifier = Modifier.size(300.dp))

        /* Clips content as alpha usage here creates an offscreen buffer to rasterize content
        into first then draws to the original destination */
        Canvas(
            modifier = Modifier
                // force to an offscreen buffer
                .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the
            content gets clipped */
            drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }
    }
}

CompositingStrategy.Auto so với CompositingStrategy.Offscreen – chiến lược ngoài màn hình cắt theo vùng, còn chiến lược tự động thì không
Hình 15: CompositingStrategy.Auto so với CompositingStrategy.Offscreen – chiến lược ngoài màn hình cắt theo vùng, còn chiến lược tự động thì không
ModulateAlpha

Chiến lược kết hợp này điều chỉnh alpha cho mỗi lệnh vẽ được ghi trong graphicsLayer. Chiến lược này sẽ không tạo vùng đệm ngoài màn hình cho alpha dưới 1.0f trừ phi RenderEffect được đặt giá trị, do đó, việc kết xuất alpha có thể hiệu quả hơn. Tuy nhiên, chiến lược này có thể cung cấp nhiều kết quả cho nội dung chồng chéo. Đối với các trường hợp sử dụng mà biết trước rằng nội dung không chồng chéo, chiến lược này có thể mang đến hiệu suất tốt hơn CompositingStrategy.Auto với alpha nhỏ hơn 1.

Dưới đây là một ví dụ khác về nhiều chiến lược kết hợp: áp dụng nhiều giá trị alpha cho nhiều phần của các thành phần kết hợp và áp dụng chiến lược Modulate:

@Preview
@Composable
fun CompositingStrategy_ModulateAlpha() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp)
    ) {
        // Base drawing, no alpha applied
        Canvas(
            modifier = Modifier.size(200.dp)
        ) {
            drawSquares()
        }

        Spacer(modifier = Modifier.size(36.dp))

        // Alpha 0.5f applied to whole composable
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    alpha = 0.5f
                }
        ) {
            drawSquares()
        }
        Spacer(modifier = Modifier.size(36.dp))

        // 0.75f alpha applied to each draw call when using ModulateAlpha
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.ModulateAlpha
                    alpha = 0.75f
                }
        ) {
            drawSquares()
        }
    }
}

private fun DrawScope.drawSquares() {

    val size = Size(100.dp.toPx(), 100.dp.toPx())
    drawRect(color = Red, size = size)
    drawRect(
        color = Purple, size = size,
        topLeft = Offset(size.width / 4f, size.height / 4f)
    )
    drawRect(
        color = Yellow, size = size,
        topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f)
    )
}

val Purple = Color(0xFF7E57C2)
val Yellow = Color(0xFFFFCA28)
val Red = Color(0xFFEF5350)

ModulateAlpha áp dụng giá trị alpha đặt riêng cho từng lệnh vẽ
Hình 16: ModulateAlpha áp dụng giá trị alpha đặt riêng cho từng lệnh vẽ

Ghi nội dung của một thành phần kết hợp vào bitmap

Một trường hợp sử dụng phổ biến là tạo Bitmap từ một thành phần kết hợp. Để sao chép nội dung của thành phần kết hợp vào Bitmap, hãy tạo GraphicsLayer bằng rememberGraphicsLayer().

Chuyển hướng các lệnh vẽ đến lớp mới bằng cách sử dụng drawWithContent()graphicsLayer.record{}. Sau đó, vẽ lớp trong canvas hiển thị bằng cách sử dụng drawLayer:

val coroutineScope = rememberCoroutineScope()
val graphicsLayer = rememberGraphicsLayer()
Box(
    modifier = Modifier
        .drawWithContent {
            // call record to capture the content in the graphics layer
            graphicsLayer.record {
                // draw the contents of the composable into the graphics layer
                this@drawWithContent.drawContent()
            }
            // draw the graphics layer on the visible canvas
            drawLayer(graphicsLayer)
        }
        .clickable {
            coroutineScope.launch {
                val bitmap = graphicsLayer.toImageBitmap()
                // do something with the newly acquired bitmap
            }
        }
        .background(Color.White)
) {
    Text("Hello Android", fontSize = 26.sp)
}

Bạn có thể lưu bitmap vào ổ đĩa và chia sẻ bitmap đó. Để biết thêm thông tin, hãy xem mã nguồn ví dụ đầy đủ. Hãy nhớ kiểm tra quyền trên thiết bị trước khi cố gắng lưu vào ổ đĩa.

Đối tượng sửa đổi bản vẽ tuỳ chỉnh

Để tạo đối tượng sửa đổi tuỳ chỉnh của riêng bạn, hãy triển khai giao diện DrawModifier. Khi làm theo cách này, bạn có thể truy cập vào ContentDrawScope mà giống với nội dung sẽ hiển thị khi sử dụng Modifier.drawWithContent(). Sau đó, bạn có thể trích xuất các thao tác vẽ phổ biến cho đối tượng sửa đổi bản vẽ tuỳ chỉnh để dọn dẹp mã và cung cấp các trình bao bọc tiện lợi. Ví dụ: Modifier.background() là một DrawModifier tiện lợi.

Ví dụ: nếu muốn triển khai một Modifier lật nội dung theo chiều dọc, bạn có thể tạo đối tượng sửa đổi như sau:

class FlippedModifier : DrawModifier {
    override fun ContentDrawScope.draw() {
        scale(1f, -1f) {
            this@draw.drawContent()
        }
    }
}

fun Modifier.flipped() = this.then(FlippedModifier())

Sau đó, hãy dùng đối tượng sửa đổi đã lật này được áp dụng trên Text:

Text(
    "Hello Compose!",
    modifier = Modifier
        .flipped()
)

Đối tượng sửa đổi tuỳ chỉnh đã lật trên văn bản
Hình 17: Đối tượng sửa đổi tuỳ chỉnh đã lật trên văn bản

Tài nguyên khác

Để biết thêm ví dụ về cách sử dụng graphicsLayer và bản vẽ tuỳ chỉnh, hãy xem các tài nguyên sau: