Chuyển đến nội dung chính

Exploring Jetpack Compose Canvas: the power of drawing


 

Trong bài viết này, tôi sẽ chia sẻ kinh nghiệm sử dụng Canvas với Jetpack Compose, đây là bộ công cụ giao diện người dùng mới của Google. Thử thách dành cho nhà phát triển Android #2 đã cho tôi cơ hội tìm hiểu rất nhiều điều về Canvas và cách tận dụng nó để vẽ và tạo hoạt ảnh cho các hình dạng hoặc văn bản theo cách rất hay.


Hầu hết các mẫu mã dựa trên dự án dưới đây: https://github.com/Oleur/TimePack-CountPose


Tuyên bố từ chối trách nhiệm: các mẫu mã dựa trên Soạn 1.0.0-beta02. Các phương thức API có thể thay đổi trong tương lai gần.


1. First steps with Canvas


Nếu bạn đã quen thuộc với các phương pháp canvas của Android View, bạn sẽ không bị thất vọng với phương pháp từ Jetpack Compose. Tất cả các tên hàm đều giống nhau và một số trong số chúng thậm chí còn rõ ràng hơn khi xử lý Path API , ví dụ: relQuadraticBezierTo() thay vì rQuadto() để làm cong một đoạn của đường dẫn.


Nếu bạn chưa quen với Android Canvas bản địa, tôi thực sự khuyên bạn nên xem qua bài viết này của Rebecca Franks để có một giới thiệu tuyệt vời về Android Canvas.

​​https://trunghieu-it.blogspot.com/2021/08/getting-started-with-android-canvas.html


Với Jetpack Compose, có một Composable là một phần của thư viện thành phần UI được gọi là Canvas để giải phóng sức mạnh của việc vẽ trong ứng dụng của bạn. Chúng ta sẽ vẽ một khuôn mặt cười với các hình dạng đơn giản (hình tròn, vòng cung và hình chữ nhật) để thể hiện khả năng của nó.


@Composable
fun SmileyFaceCanvas(
modifier: Modifier
) {
Canvas(
modifier = modifier.size(300.dp),
onDraw = {
// Head
drawCircle(
Brush.linearGradient(
colors = listOf(greenLight700, green700)
),
radius = size.width / 2,
center = center,
style = Stroke(width = size.width * 0.075f)
)

// Smile
val smilePadding = size.width * 0.15f
drawArc(
color = red700,
startAngle = 0f,
sweepAngle = 180f,
useCenter = true,
topLeft = Offset(smilePadding, smilePadding),
size = Size(size.width - (smilePadding * 2f), size.height - (smilePadding * 2f))
)

// Left eye
drawRect(
color = dark,
topLeft = Offset(size.width * 0.25f, size.height / 4),
size = Size(smilePadding, smilePadding)
)
// Right eye
drawRect(
color = dark,
topLeft = Offset((size.width * 0.75f) - smilePadding, size.height / 4),
size = Size(smilePadding, smilePadding)
)
}
)
}

Lambda onDraw trên Canvas cho phép chúng ta truy cập vào DrawScope. Phạm vi này cho phép chúng tôi vẽ mọi thứ chúng tôi muốn trong Canvas. Hãy nhớ rằng điểm gốc (x = 0, y = 0) của Canvas nằm ở trên cùng bên trái.


Để vẽ đầu của khuôn mặt cười, chúng ta sẽ vẽ một hình tròn với kiểu nét. Nếu chúng ta để kiểu trống, nó sẽ được lấp đầy theo mặc định. Tất cả các phương pháp vẽ đều chấp nhận Color hoặc Brush (được sử dụng để thêm các gradient với danh sách các màu). Để đặt bán kính, chúng tôi có quyền truy cập vào kích thước của môi trường vẽ hiện tại với kích thước do DrawScope cung cấp để tính toán bán kính có thể mở rộng tùy thuộc vào kích thước của thành phần. Thuộc tính center chấp nhận Offset để đặt vị trí của hình dạng trong canvas.


Sau đó, chúng ta vẽ vòng cung cho miệng với một màu duy nhất và chúng ta làm tương tự với các hình chữ nhật cho mắt. Bây giờ, khuôn mặt cười của chúng ta đã sẵn sàng để hiển thị trên màn hình:




Có nhiều phương pháp có sẵn trong DrawScope để vẽ trên Canvas, đây là ví dụ về các hàm hiện tại:


  • drawCircle() // vẽ một đường tròn tại các tọa độ đã cho

  • drawArc() // vẽ một cung tròn được chia tỷ lệ để vừa với bên trong một hình chữ nhật nhất định

  • drawImage() // vẽ một ImageBitmap trong canvas

  • drawPoints() // vẽ một chuỗi các điểm

  • drawPath() // vẽ một đường dẫn với một màu cho trước


và nhiều hơn nữa… Bây giờ, hãy xem cách chúng ta có thể tạo hiệu ứng cho các bản vẽ mà chúng ta đã thêm vào Canvas của mình.


2. Animations with Canvas


Bây giờ chúng ta đã có những kiến thức cơ bản, hãy xem cách chúng ta có thể tiến xa hơn và triển khai cũng như tạo hiệu ứng cho các bản vẽ phức tạp hơn. Trong Thử thách dành cho nhà phát triển Android# 2, tôi đã quyết định phát triển hoạt ảnh sóng dịch từ từ xuống đáy với độ rộng sóng động sẽ không đổi vào cuối thời gian.


DrawScope cung cấp một API thực sự tuyệt vời để trực tiếp tạo hoạt ảnh cho các bản vẽ của Canvas. Bạn có thể áp dụng các phép dịch, phép quay hoặc phép biến đổi tỷ lệ. Đối với hoạt ảnh sóng bộ hẹn giờ, chúng ta sẽ chuyển đổi hoạt động.


Đầu tiên, chúng tôi xác định hai loại AnimationState để đạt được hoạt ảnh được nhắm mục tiêu. Đầu tiên, chúng ta đặt một hoạt ảnh vô hạn trên float từ 0 đến 1 để có hiệu ứng sóng vô hạn nhờ vào memoryInfiniteTransition(). Sau đó, chúng tôi hiển thị giá trị của hoạt ảnh bằng cách sử dụng animateFloat() và đặc tả liên quan.


Đối với tất cả các hoạt ảnh hữu hạn, chúng tôi sẽ làm việc trực tiếp với các hàm AnimationState như animateFloatAsState(), animateColorAsState()… nơi có thể đặt giá trị đích và xác định các đặc tả hoạt ảnh.


val deltaXAnim = rememberInfiniteTransition()
val dx by deltaXAnim.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(500, easing = LinearEasing)
)
)

val screenWidthPx = with(LocalDensity.current) {
(LocalConfiguration.current.screenHeightDp * density)
}
val animTranslate by animateFloatAsState(
targetValue = screenWidthPx,
animationSpec = TweenSpec(10000, easing = LinearEasing)
)

val waveHeight by animateFloatAsState(
targetValue = 0f,
animationSpec = TweenSpec(10000, easing = LinearEasing)
)

Bây giờ chúng ta đã xác định các trạng thái hoạt ảnh, chúng ta có thể triển khai hoạt ảnh sóng. Để vẽ sóng chính nó, chúng ta sẽ sử dụng một Path cho phép chúng ta thêm các đoạn đường cong Bezier vào đường dẫn của chúng ta giống như một hàm hình sin. Sau đó, khi bản vẽ được hoàn thành, chúng ta cần bọc nó bằng lambda translate () được cung cấp bởi DrawScope và chuyển giá trị của AnimateState để tạo hoạt ảnh cho các pixel trên cùng.

Canvas(
modifier = modifier.size(300.dp),
onDraw = {
translate(top = animTranslate) {
drawPath(path = path, color = animColor)
path.reset()
val halfWaveWidth = waveWidth / 2
path.moveTo(-waveWidth + (waveWidth * dx), originalY.dp.toPx())

for (i in -waveWidth..(size.width.toInt() + waveWidth) step waveWidth) {
path.relativeQuadraticBezierTo(
halfWaveWidth.toFloat() / 2,
-waveHeight,
halfWaveWidth.toFloat(),
0f
)
path.relativeQuadraticBezierTo(
halfWaveWidth.toFloat() / 2,
waveHeight,
halfWaveWidth.toFloat(),
0f
)
}

path.lineTo(size.width, size.height)
path.lineTo(0f, size.height)
path.close()
}
}
)

3. Use native Canvas to draw text

Hiện tại, bạn không thể vẽ văn bản trực tiếp trên canvas Jetpack Compose. Để làm như vậy, bạn phải truy cập canvas gốc từ Android framework để vẽ một số văn bản trên đó. Trên lambda onDraw của thành phần Canvas, bạn có thể gọi hàm drawIntoCanvas để truy cập Canvas bên dưới với nativeCanvas (khá hữu ích nếu bạn có thể sử dụng lại một số logic vẽ mà bạn đã triển khai trong các ứng dụng Android trước đó). Sau đó, bạn có thể gọi tất cả các phương thức liên quan đến Canvas gốc như drawText() hoặc drawVertices() chẳng hạn.


Để áp dụng một kiểu cho văn bản, một đối tượng Paint phải được sử dụng. Vì chúng tôi đang sử dụng canvas gốc, chúng tôi không thể sử dụng Paint từ Jetpack Compose trực tiếp với hàm drawText(). Để có được một phiên bản Paint gốc, chúng ta có thể gọi phương thức asFrameworkPaint() để xử lý một android.graphics.Paint.


Đây là đoạn mã cho thấy cách vẽ một văn bản đơn giản trên canvas gốc:


val textPaint = Paint().asFrameworkPaint().apply {
isAntiAlias = true
textSize = 24.sp.toPx()
color = android.graphics.Color.BLUE
typeface = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD)
}
Canvas(
modifier = modifier.fillMaxSize(),
onDraw = {
drawIntoCanvas {
it.nativeCanvas.drawText(
"My Jetpack Compose Text"
0f, // x-coordinates of the origin (top left)
120.dp.toPx(), // y-coordinates of the origin (top left)
textPaint
)
}
}
)

Và đây là những gì nó trông giống như trên ứng dụng mẫu:



Bạn có thể sử dụng tất cả các chuyển đổi Jetpack Compose Canvas (dịch, xoay, chia tỷ lệ…) và quấn drawIntoCanvas để bạn có thể thêm các hình ảnh động trên những gì bạn đã vẽ.

Canvas(
modifier = modifier.fillMaxSize(),
onDraw = {
translate(top = animTranslate * 0.92f) {
scale(scale = if (timePackViewModel.alertState.value!!) animAlertScale else 1f) {
drawIntoCanvas {
it.nativeCanvas.drawText(
"My Jetpack Compose Text",
0f,
120.dp.toPx(),
textPaint
)
}
}
}
}
)

Vẽ trên canvas có thể mở ra nhiều khả năng thiết kế! Bạn có thể thêm đơn giản rất dễ dàng nhưng bạn có thể kết thúc việc triển khai các thuật toán toán học phức tạp nếu bạn muốn triển khai các bản vẽ đường dẫn phức tạp. Jetpack Compose Canvas có rất nhiều thứ để cung cấp, vì vậy hãy sử dụng nó một cách khôn ngoan.

Nhận xét

Bài đăng phổ biến từ blog này

Jetpack Compose VS SwiftUI !VS Flutter

  Việc phát triển Android đã trở nên dễ dàng hơn khi các bản cập nhật liên tục đến. Sau bản cập nhật 2020.3.1, rất nhiều thứ đã thay đổi. Nhưng thay đổi chính mà tôi nghĩ hầu hết các nhà phát triển phải chờ đợi là Jetpack Compose cho ứng dụng sản xuất. Và Kotlin là lựa chọn duy nhất cho jetpack Compose, cũng là ngôn ngữ được ưu tiên. Để biết thêm chi tiết hoặc các thay đổi trên Jetpack Compose, bạn có thể truy cập vào https://developer.android.com/jetpack/compose Tương tự, IOS Development cũng cung cấp một tùy chọn để phát triển khai báo, SwiftUI. Trong IDE, không có thay đổi nào do điều này. Nhưng khái niệm gần giống với Jetpack Compose. Thay vì bảng phân cảnh, chúng tôi tạo giao diện người dùng bằng Swift. Để biết thêm chi tiết hoặc các thay đổi trên SwiftUI, hãy truy cập https://developer.apple.com/xcode/swiftui/ Hãy xem cách cả hai hoạt động bằng cách sử dụng một dự án demo. Tôi đã lấy một số ví dụ về số lần chạm tương tự của Flutter. 1. Android Jetpack Compose Chúng tôi có thể tạo

Thiết kế giao diện với DotNetBar (Phần 1)

Đây là phiên bản DotNetBar hỗ trợ C# và Visual Basic https://www.dropbox.com/s/wx80jpvgnlrmtux/DotNetBar.rar  , phiên bản này hỗ trợ giao diện Metro cực kỳ “dễ thương” Các bạn load về và cài đặt, khi cài đặt xong sẽ có source code mẫu của tất cả các control. Để sử dụng được các control của DotNetBar các bạn nhớ add item vào controls box. Thiết kế giao diện với DotNetBar, giao diện sẽ rất đẹp. Link các video hướng dẫn chi tiết cách sử dụng và coding: http://www.devcomponents.com/dotnetbar/movies.aspx Hiện tại DotNetBar có rất nhiều công cụ cực mạnh, trong đó có 3 công cụ dưới đây: DotNetBar for Windows Forms Requires with Visual Studio 2003, 2005, 2008, 2010 or 2012.   DotNetBar for WPF Requires with Visual Studio 2010 or 2012 and Windows Presentation Foundation.   DotNetBar for Silverlight Requires with Visual Studio 2010 or 2012 and Silverlight. Dưới đây là một số hình ảnh về các control trong DotnetBar.   Metro User Interface  controls with Metro Tiles, toolbars, slide panels, forms,

Một số bài tập Winform C#

Một số bài tập: 1. Mô phỏng game đoán số. Luật chơi:         o Đúng số và đúng vị trí   +         o Đúng số mà sai vị trí      ?         o Sai số và sai vị trí          -         . . .         - Kết quả được tạo ngẫu nhiên từ các số có 4 chữ số.         - Các chữ số có giá trị từ 0-6.         - Người chơi có 6 lần đoán. Chương trình tham khảo: 2. In số điện tử Yêu cầu: người dùng nhập vào 1 số ( hoặc 1 chuỗi số) yêu cầu in ra số đó dưới dạng số điện tử. Chương trình tham khảo: 3. Mô phỏng game CARO  (update) 4. Mô phỏng game DÒ MÌN (update)