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

Jetpack Compose: Calculator UI

 


Calculator UI của Android 10 được nhân bản bằng Jetpack Compose, vì đây là một trong

những cấu trúc giao diện người dùng phức tạp hơn.


Trước đây tôi đã đề cập đến bố cục kết hợp khác - bạn có thể xem bài viết tại đây:


https://trunghieu-it.blogspot.com/2021/08/jetpack-compose-lists.html

https://trunghieu-it.blogspot.com/2021/08/jetpack-compose-columns-and-rows.html

  1. The Calculator UI


Lưu ý: Tôi đang sử dụng Compose phiên bản 0.1.0-dev09 tại thời điểm viết bài này.


Được - vậy điều phức tạp về Calculator UI  là nó có nhiều bảng và lớp phủ có thể kéo.


Nó trông như thế nào:



2.The Code


Bạn có thể lấy mã nguồn tại đây:


https://github.com/ahmedrizwan/JetpackComposeCalculator/


Bắt đầu với cấp cao nhất có thể tổng hợp


setContent {
MaterialTheme(colors = lightThemeColors) {
WithConstraints { constraints, _ ->
val boxHeight = with(DensityAmbient.current) { constraints.maxHeight.toDp() }
val boxWidth = with(DensityAmbient.current) { constraints.maxWidth.toDp() }
Content(
constraints = constraints,
boxHeight = boxHeight,
boxWidth = boxWidth
)
}
}
}

Hệ thống phân cấp giống như: MaterialTheme → WithConstraints → Content


  • MaterialTheme


Nó là một thành phần cấp cao đặt màu sắc và kiểu dáng được xác định trước theo các nguyên tắc thiết kế Material.


  • WithConstraints


Để có được chiều cao và chiều rộng màn hình có sẵn — những ràng buộc này sau đó được sử dụng để cài đặt và xác định neo cho các bảng có thể kéo được.


  1. The Components


Chúng tôi có hai bảng có thể kéo được (TopView và SideView). Tôi sẽ tập trung vào những điều này (vì phần còn lại của ứng dụng khá dễ hiểu).




Side View


Hãy bắt đầu với chế độ xem bên. Đó là bảng màu xanh lam trượt từ phải sang trái. Nó ở trạng thái thu gọn hoặc được mở rộng.


Đây là giao diện của nó khi được mở rộng:



Bảng điều khiển này có thuộc tính kéo và ném. Để thêm hành vi trang chiếu này - chúng ta

phải sử dụng ba thứ:


  • AnimatedFloat

  • FlingConfig

  • Draggable Modifier


1.AnimatedFloat


Đối với các giá trị hoạt ảnh giữa các giới hạn. Kế thừa từ BaseAnimatedValue.


2.FlingConfig


Để thêm động tác ném - khi quá trình kéo kết thúc, nó sẽ tìm ra liệu có nên ném để bắt đầu

hay kết thúc vận tốc đã cho. AnchorsFlingConfig được sử dụng để di chuyển giữa một bộ

giá trị được xác định trước (neo).


3.Draggable Modifier


Modifier thêm tính năng kéo vào một chế độ xem. Được sử dụng khi chúng ta muốn

kéo một thành phần con. Nó có các thông số sau:


dragDirection direction in which drag should be happening

onDragDeltaConsumptionRequested callback to be invoked when drag

occurs. Users must update their state in this lambda and return

amount of delta consumed

onDragStarted callback that will be invoked when drag has been

started after touch slop has been passed, with starting position

provided

onDragStopped callback that will be invoked when drag stops, with

velocity provided

enabled whether or not drag is enabled

startDragImmediately when set to true, draggable will start dragging

immediately and prevent other gesture detectors from reacting to

"down" events (in order to block composed press-based gestures).

This is intended to allow end users to "catch" an animating widget

by pressing on it. It's useful to set it when value you're dragging

is settling / animating.


Tôi cũng đã sử dụng một lớp mô hình trợ giúp để truyền thông tin kéo / di chuyển xung quanh.


class Drag(
val position: AnimatedFloat,
val flingConfig: FlingConfig
)

Side View Drag




// Side drag
val sideMin = 90.dp
val sideMax = boxWidth - 30.dp
val (sideMinPx, sideMaxPx) = with(DensityAmbient.current) {
sideMin.toPx().value to sideMax.toPx().value
}
val sideFlingConfig = AnchorsFlingConfig(listOf(sideMinPx, sideMaxPx))
val sidePosition = animatedFloat(sideMaxPx)
sidePosition.setBounds(sideMinPx, sideMaxPx)

val sideDrag = Drag(
position = sidePosition,
flingConfig = sideFlingConfig
)

Chỉ định giá trị tối thiểu và giá trị tối đa (vị trí pixel được tính toán trên màn hình) làm giá trị hoạt ảnh và giá trị neo cho chuyển động. Và sau đó sử dụng thông tin này trong SideView có thể tổng hợp như thế này:


@Composable()
fun SideView(
boxHeight: Dp,
drag: Drag
) {
val position = drag.position
val flingConfig = drag.flingConfig
val yOffset = with(DensityAmbient.current) { position.value.toDp() }
val toggleAsset = state { R.drawable.ic_keyboard_arrow_left_24 }
Box(
Modifier.offset(x = yOffset, y = 0.dp)
.fillMaxWidth()
.draggable(
startDragImmediately = position.isRunning,
dragDirection = DragDirection.Horizontal,
onDragStopped = { position.fling(flingConfig, it) }
) { delta ->
position.snapTo(position.value + delta)
delta
}
.preferredHeight(boxHeight),
backgroundColor = AppState.theme.primary,
gravity = ContentGravity.CenterStart
) {
...
}
}

Trong phần có thể tổng hợp - vị trí kéo được truy xuất và độ lệch ngang được tính toán. FlingConfig được sử dụng trong lệnh gọi lại onDragStopped.


Và thế là xong - tính năng kéo và ném hoạt động!


Top View


Tương tự như Side View - nó cũng là một thành phần có thể kéo, nhưng từ trên

xuống dưới.




// Top drag
val topStart = -(constraints.maxHeight.value / 1.4f)
val topMax = 0.dp
val topMin = -(boxHeight / 1.4f)
val (topMinPx, topMaxPx) = with(DensityAmbient.current) {
topMin.toPx().value to topMax.toPx().value
}
val topFlingConfig = AnchorsFlingConfig(listOf(topMinPx, topMaxPx))
val topPosition = animatedFloat(topStart) // for dragging state
topPosition.setBounds(topMinPx, topMaxPx)

val topDrag = Drag(
position = topPosition,
flingConfig = topFlingConfig
)

Cũng giống như trước đây - chúng tôi tính toán các giá trị tối thiểu và tối đa dưới

dạng các giá trị float động và các neo cho fling. Và sau đó sử dụng thông tin này

trong TopView có thể tổng hợp như thế này:


@Composable
fun TopView(
boxHeight: Dp,
drag: Drag
) {
val position = drag.position
val flingConfig = drag.flingConfig
val yOffset = with(DensityAmbient.current) { position.value.toDp() }
val scrollerPosition = ScrollerPosition()
// scroll the history list to bottom when dragging the top panel
// 90dp history item height is an approximation
scrollerPosition.smoothScrollBy(operationsHistory.size * 90.dp.value)

Card(
Modifier.offset(y = yOffset, x = 0.dp).fillMaxWidth()
.draggable(
startDragImmediately = position.isRunning,
dragDirection = DragDirection.Vertical,
onDragStopped = { position.fling(flingConfig, it) }
) { delta ->
position.snapTo(position.value + delta)
// consume all delta no matter the bounds to avoid nested dragging (as example)
delta
}
.preferredHeight(boxHeight),
elevation = 4.dp,
shape = MaterialTheme.shapes.large,
color = Color.White
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Bottom,
horizontalGravity = ContentGravity.CenterHorizontally
) {
HistoryTopBar()
HistoryList(scrollerPosition)
Spacer(modifier = Modifier.preferredHeight(2.dp))
CollapsableContent(boxHeight, position)
RoundedDash()
}
}
}

Sử dụng Card cho độ cao và vị trí cuộn tùy chỉnh để cuộn vẫn được cuộn xuống dưới cùng khi kéo.


Bên trong tCard composable, chúng tôi chỉ đơn giản có một cột với một vài thành phần


Khi vị trí kéo thay đổi - tôi ẩn và thu nhỏ MainContent. Điều đó mang lại cho chúng tôi hiệu ứng trượt xuống thú vị cho HistoryList.


Overlays


Tôi đã tạo một thành phần hộp với màu nền, chuyển vào giá trị alpha.


@Composable
fun DimOverlay(alpha: Int) {
Box(modifier = Modifier.fillMaxSize(), backgroundColor = Color(50, 50, 50, alpha))
}

This overlay được đặt trên đầu Numbers Panel - và alpha được tính toán dựa trên lực kéo. Tôi đã sử dụng Stack composable, để nó được đặt trên các chế độ xem.


@Composable()
fun NumbersPanel(alpha: Int) {
Stack {
Row(modifier = Modifier.fillMaxSize()) {
numberColumns.forEach { numberColumn ->
Column(modifier = Modifier.weight(1f)) {
numberColumn.forEach { text ->
MainContentButton(text)
}
}
}
Divider(
modifier = Modifier.preferredWidth(1.dp).fillMaxHeight(),
color = Color(0xFFd3d3d3)
)
Column(modifier = Modifier.weight(1.3f)) {
operationsColumn.forEach { operation ->
OperationItem(text = operation)
}
}
Spacer(modifier = Modifier.preferredWidth(30.dp))
}
DimOverlay(alpha = alpha)
}
}



Đây là cách các con số mờ đi khi tôi trượt ra Side View.Khái niệm tương tự được

sử dụng cho Top View overlay.


Limitations


Cũng có một số hạn chế (vì tính năng Compose chưa sẵn sàng sản xuất).


AdapterList: Không có nhiều quyền kiểm soát trạng thái cuộn, vì vậy tôi phải chọn VerticalScroller thay vì hiển thị danh sách lịch sử.


TextField: Không có chế độ RTL và không thể tắt đầu vào mềm


Liên kết đến nguồn: https://github.com/ahmedrizwan/JetpackComposeCalculator


Tuyên bố từ chối trách nhiệm: Vì Jetpack Compose đang trong giai đoạn phát triển

- bất kỳ API nào trong số này đều có thể thay đổi bất kỳ lúc nào. Vì vậy, là nguồn

của sự thật, vui lòng luôn tham khảo tài liệu chính thức.


Cảm ơn bạn đã đọc - Tôi sẽ trình bày nhiều hơn về Compose khi nó trở nên ổn định hơn và sẵn sàng sản xuất, vì vậy hãy chú ý theo dõi.


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)