Hướng dẫn này sẽ dạy cho bạn một số khái niệm và thuật ngữ cơ bản liên quan đến Thư viện giao diện người dùng Jetpack Compose trên Android.
Mặc dù đây là hướng dẫn dành cho người mới bắt đầu về Soạn, nhưng nó sẽ không phải là hướng dẫn dành cho người mới bắt đầu về Android - vì vậy bạn nên tạo ít nhất một hoặc hai ứng dụng (mặc dù không nhất thiết phải có trong Soạn).
Trước khi chúng ta bắt đầu, ban đầu tôi dự định viết một bài tiếp theo hướng đến các nhà phát triển cấp cao hơn cho đến khi tôi xem qua loạt bài viết gồm hai phần của Leland Richardson. Leland không chỉ là một Kỹ sư phần mềm làm việc trong nhóm Jetpack Compose, mà tôi thấy rằng anh ấy còn là một nhà văn tuyệt vời.
Mặc dù tôi cảm thấy bài viết của mình sẽ tự giới thiệu về những điều cơ bản của Jetpack Compose, nhưng tôi thực sự đề xuất bạn đọc các bài viết của anh ấy khi bạn đã có được một số kinh nghiệm thực tế về Soạn (hoặc ngay lập tức nếu bạn muốn học theo cách đó).
Các thuật ngữ / khái niệm chính được giải thích trong bài viết này:
- Đánh giá ngắn gọn về Hệ thống Chế độ xem cũ và Hệ thống phân cấp
- Các tổng hợp và cách chúng đứng trong mối quan hệ với Chế độ xem
- Tính năng tổng hợp lại và cách tránh làm điều đó rất kém!
Composable là gì?
Trong phần này, chúng ta sẽ thảo luận về phần cơ bản nhất của thư viện Jetpack Compose. Nếu bạn là một nhà phát triển Android dày dạn kinh nghiệm, bạn có thể muốn bỏ qua phần phụ có tiêu đề “Chế độ xem có phải là tổng hợp không?”
Nếu bạn chưa quen với hệ thống View, bạn nên đọc phần tiếp theo vì nó cần thiết để thúc đẩy và hiểu Composable là gì.
Chế độ xem
Trong ngữ cảnh của Android SDK (các thư viện chúng tôi sử dụng để tạo giao diện người dùng trên nền tảng này), Chế độ xem là thứ chúng tôi sử dụng để cung cấp cấu trúc và phong cách cho các ứng dụng của mình.
Đây là loại khối hoặc phần tử cơ bản nhất của giao diện người dùng (UI) nhất định và mỗi khối xây dựng này sẽ chứa các loại thông tin sau (trong số những thứ khác):
- Vị trí bắt đầu và kết thúc X và Y cho máy tính biết vị trí vẽ chế độ xem trên màn hình thiết bị
- Các giá trị màu và alpha (độ trong suốt)
- Thông tin phông chữ, văn bản, ký hiệu và hình ảnh
- Hành vi dựa trên các sự kiện như tương tác của người dùng (nhấp chuột) hoặc các thay đổi trong dữ liệu của ứng dụng (sẽ tìm hiểu thêm về điều đó sau)
Điều quan trọng là phải hiểu rằng Chế độ xem có thể giống như một nút (thường được gọi là “tiện ích con”), nhưng nó cũng có thể là vùng chứa toàn bộ màn hình, một phần của màn hình hoặc cho các Chế độ xem con khác .
vùng chứa như vậy thường được gọi là Bố cục hoặc Nhóm xem tùy thuộc vào ngữ cảnh. Và, trong khi chia sẻ hầu hết các loại thông tin giống như một tiện ích con, chúng cũng chứa thông tin về cách sắp xếp và hiển thị các Chế độ xem khác được lồng nhau bên trong chúng.
Với ý nghĩ đó, chúng tôi đi đến phần quan trọng của bài đánh giá này về hệ thống Chế độ xem: Hệ thống phân cấp chế độ xem . Đối với Nhà phát triển web, Cấu trúc phân cấp chế độ xem về cơ bản là phiên bản của Android của Mô hình đối tượng tài liệu (DOM).
Đối với các Nhà phát triển Android, bạn có thể coi Hệ thống phân cấp Chế độ xem như một đại diện ảo của tất cả các Chế độ xem mà bạn đã xác định trong tệp XML hoặc được lập trình trong Java hoặc Kotlin.
Để minh họa điều này, chúng ta hãy xem một tệp XML như vậy (không cần nghiên cứu kỹ, chỉ cần lưu ý các tên). Sau đó, sử dụng công cụ gỡ lỗi / bước, chúng ta sẽ xem nó trông như thế nào trong không gian bộ nhớ của Fragment làm phồng tệp này:
gment_hour_view.xml:
<?xml version=”1.0" encoding=”utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=”https://schemas.android.com/apk/res/android"
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:id=”@+id/root_hour_view_fragment”
xmlns:app=”https://schemas.android.com/apk/res-auto"
>
<androidx.compose.ui.platform.ComposeView
android:id=”@+id/tlb_hour_view”
//...
/>
<com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView
android:id=”@+id/vqht_one”
//...
/>
<com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView
android:id=”@+id/vqht_two”
//...
/>
<com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView
android:id=”@+id/vqht_three”
//...
/>
<com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView
android:id=”@+id/vqht_four”
//...
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Dung lượng bộ nhớ của (Fragment) HourView.kt:
Công cụ gỡ lỗi và công cụ bước là một số cách yêu thích của tôi để tìm hiểu về những gì đang diễn ra dưới lớp mã mà tôi sử dụng từ các thư viện khác nhau. Hãy thử một chút thời gian!
Mục đích của việc hiển thị cho bạn tệp XML này và nó biến thành gì trong một quy trình (một quá trình chỉ đơn giản là một chương trình đang chạy trên một thiết bị), là để chứng minh cách các Chế độ xem lồng nhau trong tệp XML chuyển thành Cấu trúc Phân cấp Chế độ xem lồng nhau trong thời gian chạy.
Hy vọng rằng với một mô hình đơn giản nhưng cụ thể về cách thức hoạt động của hệ thống cũ, chúng ta có thể so sánh nó với hệ thống mới.
Có phải Chế độ xem Tổng hợp không?
Đây là một trong những câu hỏi đầu tiên tôi hỏi khi bắt đầu làm việc với tính năng Soạn thư và câu trả lời tôi đã đến là cả có và không .
Có , theo nghĩa là Chế độ xem có thể thực hiện được vai trò khái niệm giống như Chế độ xem trong hệ thống cũ. Composable có thể là một tiện ích con như một nút hoặc một vùng chứa chẳng hạn như ConstraintLayout (cần lưu ý rằng có sẵn một triển khai Composable của ConstraintLayout).
Không , theo nghĩa là giao diện người dùng không còn được biểu diễn ảo trong Hệ thống phân cấp chế độ xem (ngoại trừ các tình huống liên quan đến khả năng tương tác). Như đã nói, tính năng soạn thảo không sử dụng phép thuật để đại diện và theo dõi hầu hết các giao diện người dùng. Điều này có nghĩa là nó phải có thứ riêng tương tự về mặt khái niệm với Hệ thống phân cấp chế độ xem.
Hãy để chúng tôi xem xét rất ngắn gọn về điều này. Ở đây, chúng tôi có một Hoạt động sử dụng setContent {…}
chức năng để liên kết một Composable với chính nó:
ActiveGameActivity.kt:
class ActiveGameActivity : AppCompatActivity(), ActiveGameContainer {
private lateinit var logic: ActiveGameLogic
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ActiveGameViewModel()
setContent {
ActiveGameScreen(
onEventHandler = {
logic.onEvent(it)
},
viewModel
)
}
logic = buildActiveGameLogic(this, viewModel, applicationContext)
}
//…
}
ActiveGameScreen.kt:
@Composable
fun ActiveGameScreen(
onEventHandler: ((ActiveGameEvent) -> Unit),
viewModel: ActiveGameViewModel
) {
//...
GraphSudokuTheme {
Column(
Modifier
.background(MaterialTheme.colors.primary)
.fillMaxHeight()
) {
ActiveGameToolbar(
clickHandler = {
onEventHandler.invoke(
ActiveGameEvent.OnNewGameClicked
)
}
)
Box {
//content
}
}
}
}
Trong Soạn thảo, Hệ thống phân cấp chế độ xem được thay thế bằng thứ gì đó mà chúng tôi có thể xác định được nếu chúng tôi thực sự tìm hiểu sâu về mWindow lĩnh vực của Hoạt động này. Trong trường đó là sự thay thế khái niệm của Chế độ xem thứ bậc: Cái Composer
và của nó slotTable
.
Tại thời điểm này, nếu bạn muốn có tổng quan chi tiết về Composer
và slotTable
của nó , Tôi một lần nữa phải đề nghị bạn đọc bài báo của Leland (anh ấy đi vào chi tiết trong phần 2). Có nhiều thứ hơn đối với Hệ thống phân cấp soạn thảo hơn là Trình soạn nhạc và slotTable của nó, nhưng điều đó là đủ để chúng ta bắt đầu.
Nói chung, Jetpack Compose sử dụng cái mà chúng ta có thể gọi là Hệ thống phân cấp soạn thảo của nó (được cấu tạo và quản lý bởi những thứ như Composer và slotTable của nó).
Một lần nữa, đây là ý tưởng khái niệm tương tự như hệ thống phân cấp Chế độ xem, một loạt các đối tượng trong không gian bộ nhớ đại diện chung cho giao diện người dùng, nhưng nó được triển khai rất khác.
Tuy nhiên, có một sự khác biệt quan trọng, khó hiểu về mặt kỹ thuật, nhưng dễ hiểu về nguyên tắc. Đây là cách mà tính năng Soạn xử lý các cập nhật đối với Hệ thống phân cấp soạn thảo: Bản tổng hợp .
Bản tổng hợp:Cách cập nhật giao diện người dùng trong bản soạn thư
Đối với những người bạn ESL của tôi, từ Soạn xuất phát từ thành phần trong tiếng latin , đại khái có nghĩa là "tập hợp lại với nhau." Một người viết nhạc thường được gọi là “Nhà soạn nhạc”, có thể được coi là người kết hợp các nốt nhạc đến từ một hoặc nhiều nhạc cụ thành một sáng tác (bài hát).
Ghép lại với nhau ngụ ý rằng có những phần riêng lẻ. Điều quan trọng là phải hiểu rằng hầu hết bất kỳ nhà phát triển phần mềm giỏi nào cũng cố gắng ít nhất để chia nhỏ mã của họ thành phần hợp lý nhỏ nhất .
Tôi đề cập đến hợp lý , bởi vì tôi nghĩ rằng các nguyên tắc như KHÔ (Không lặp lại bản thân) chỉ nên được tuân thủ trong phạm vi chúng giải quyết được nhiều vấn đề hơn những gì chúng tạo ra.
Có rất nhiều lợi ích khi áp dụng khái niệm này, thường được gọi là tính mô-đun, (hoặc theo tôi thích, Phân tách các mối quan tâm, hoặc SOC). Tôi biết rằng một số bạn đọc bài này có thể nghĩ rằng tôi chỉ đang sao chép những gì Leland đã nói trong bài viết của anh ấy, nhưng tôi đã nói về SOC như một Nguyên tắc Vàng của Kiến trúc Phần mềm trong nhiều năm rồi.
Nơi điều này đóng vai trò Soạn thảo, cũng giống như nguyên tắc mà chúng ta thấy trong thư viện Javascript phổ biến React . Khi được thực hiện đúng cách, Soạn sẽ chỉ “biên soạn lại” (vẽ lại, kết xuất lại, cập nhật, bất cứ điều gì) các Phần có thể soạn (các phần / phần tử của giao diện người dùng) cần được biên soạn lại.
Điều này rất quan trọng khi nói đến hiệu suất của một ứng dụng. Điều này là do việc vẽ lại giao diện người dùng, cho dù trong hệ thống Chế độ xem cũ hay trong Soạn thư, sẽ tốn kém tài nguyên hệ thống.
Trong trường hợp bạn không biết, toàn bộ mục đích của RecyclerView cũ (đó là điều đầu tiên tôi từng làm hướng dẫn vào năm 2016!) Là sử dụng mẫu ViewHolder vào danh sách dữ liệu. Điều này tránh được sự cần thiết phải liên tục tăng (tạo) Lượt xem mới cho mỗi mục danh sách.
Mục tiêu của tôi trong bài viết này là tập trung chủ yếu vào lý thuyết, vì tôi sẽ viết nhiều nội dung thực tế trong vài tháng tới. Tuy nhiên, tôi sẽ kết thúc bài viết bằng một câu chuyện từ kinh nghiệm trực tiếp của tôi, điều này sẽ giúp bạn hiểu thêm về cách hoạt động của bố cục lại và cách tránh làm điều đó quá kém!
Ví dụ về đồng hồ bấm giờ
Đối với ứng dụng Soạn đầy đủ đầu tiên của tôi, tôi quyết định tạo Sudoku. Có một số lý do tại sao, bao gồm cả thực tế là tôi muốn một dự án không có giao diện người dùng quá phức tạp. Tôi cũng muốn có cơ hội tìm hiểu sâu hơn về Graph DS và Algos, những thứ khá phù hợp với câu đố Sudoku.
Một thứ tôi muốn là Đồng hồ bấm giờ sẽ theo dõi người dùng mất bao lâu để hoàn thành một câu đố:
Như thường lệ trong nghề nghiệp của tôi, tôi mong đợi bộ đếm thời gian này sẽ dễ dàng hơn nhiều so với thực tế. Tôi đã gặp rắc rối với lớp Chronometer của Android cũng như lớp Bộ hẹn giờ của Java và cả hai lớp này đều gặp phải các vấn đề khác nhau nhưng vẫn còn lỗi ứng dụng.
Cuối cùng, tôi lùi lại một bước và nhận ra rằng tôi đang viết bằng Kotlin. Vì vậy, tôi đã thiết lập một bộ đếm thời gian dựa trên Coroutine trong lớp logic bản trình bày của mình (nó kết thúc hợp lý nhất khi đặt nó ở đó), nó sẽ cập nhật mô hình xem của tôi mỗi giây:
Class ActiveGameLogic(…):…{
//…
inline fun startCoroutineTimer(
delayMillis: Long = 0,
repeatMillis: Long = 1000,
crossinline action: () -> Unit
) = launch {
delay(delayMillis)
if (repeatMillis > 0) {
while (true) {
action()
delay(repeatMillis)
}
} else {
action()
}
}
private fun onStart() =
launch {
gameRepo.getCurrentGame(
{ puzzle, isComplete ->
viewModel.initializeBoardState(
puzzle,
isComplete
)
if (!isComplete) timerTracker = startCoroutineTimer {
viewModel.updateTimerState()
}
},{
container?.onNewGameClick()
})
}
//…
}
ViewModel (không phải từ AAC - tôi viết các máy ảo của riêng mình. Nhưng Soạn đã có khả năng tương tác tốt với các máy ảo AAC từ những gì tôi có thể thấy.) Hiển thị các tham chiếu đến các hàm gọi lại, đó là những gì tôi sẽ sử dụng để cập nhật các tệp Composable của mình:
class ActiveGameViewModel {
//…
internal var subTimerState: ((Long) -> Unit)? = null
internal var timerState: Long = 0L
//…
internal fun updateTimerState(){
timerState++
subTimerState?.invoke(timerState)
}
//…
}
Bây giờ đến phần quan trọng! Chúng tôi có thể kích hoạt bố cục lại Hệ thống phân cấp soạn thư bằng cách sử dụng một số tính năng nhất định của tính năng soạn thư, chẳng hạn như remember
chức năng:
var timerState by remember {
mutableStateOf(“”)
}
Nếu bạn phải biết, các tính năng này lưu trữ trạng thái của bất kỳ thứ gì bạn đang nhớ trong slotTable
. Nói tóm lại, từ state ở đây có nghĩa là “trạng thái” hiện tại của dữ liệu, bắt đầu chỉ là một Chuỗi trống.
Đây là nơi tôi đã hoàn thiện mọi thứ . Tôi đã kéo bộ đếm thời gian đơn giản có thể ghép thành chức năng riêng của nó (áp dụng SOC) và tôi đang chuyển vào timerState
như một tham số cho có thể tổng hợp đó.
Tuy nhiên, các đoạn mã ở trên nằm trong bộ đếm thời gian mẹ có thể ghép lại, đây là vùng chứa cho phần phức tạp nhất của giao diện người dùng (Sudoku 9x9 yêu cầu một số lượng lớn các tiện ích con):
@Composable
fun GameContent(
onEventHandler: (ActiveGameEvent) -> Unit,
viewModel: ActiveGameViewModel
) {
Surface(
Modifier
.wrapContentHeight()
.fillMaxWidth()
) {
BoxWithConstraints(Modifier.background(MaterialTheme.colors.primary)) {
//…
ConstraintLayout {
val (board, timer, diff, inputs) = createRefs()
var isComplete by remember {
mutableStateOf(false)
}
var timerState by remember {
mutableStateOf("")
}
viewModel.subTimerState = {
timerState = it.toTime()
}
viewModel.subIsCompleteState = { isComplete = it }
//…Sudoku board
//Timer
Box(Modifier
.wrapContentSize()
.constrainAs(timer) {
top.linkTo(board.bottom)
start.linkTo(parent.start)
}
.padding(start = 16.dp))
{
TimerText(timerState)
}
//…difficulty display
//…Input buttons
}
}
}
}
@Composable
fun TimerText(timerState: String) {
Text(
text = timerState,
style = activeGameSubtitle.copy(color = MaterialTheme.colors.secondary)
)
}
Điều này đã gây ra một số độ trễ đáng kể và không phản hồi. Bằng cách sử dụng nhiều trình gỡ lỗi, tôi có thể tìm ra lý do. Bởi vì timerState
của tôi biến đã được tạo và cập nhật bên trong Composable chính, nó đang kích hoạt một cấu trúc lại của toàn bộ phần đó của giao diện người dùng. Mọi. Duy nhất. Đánh dấu.
Sau khi chuyển mã thích hợp vào TimerText
có thể kết hợp, mọi thứ hoạt động rất trơn tru:
@Composable
fun TimerText(viewModel: ActiveGameViewModel) {
var timerState by remember {
mutableStateOf("")
}
viewModel.subTimerState = {
timerState = it.toTime()
}
Text(
text = timerState,
style = activeGameSubtitle.copy(color = MaterialTheme.colors.secondary)
)
}
Hy vọng rằng tôi đã cung cấp cho bạn sự hiểu biết về cách sắp xếp lại và một trong những cách lớn nhất để làm điều đó không chính xác.
Tránh cải tiến không cần thiết là cực kỳ quan trọng đối với hiệu suất. Và cho đến nay, có vẻ như việc áp dụng SOC một cách nghiêm ngặt, thậm chí đến mức duy trì trạng thái ghi nhớ trong các vật liệu tổng hợp riêng biệt, nên trở thành thông lệ tiêu chuẩn.
Tài nguyên &Hỗ trợ
Nếu bạn thích bài viết này, hãy chia sẻ nó trên phương tiện truyền thông xã hội và xem các bài viết khác của tôi trên freeCodeCamp tại đây. Tôi cũng có một kênh YouTube với hàng trăm hướng dẫn và tôi là một nhà văn tích cực trên nhiều nền tảng khác nhau.
Kết nối với tôi trên mạng xã hội
Bạn có thể tìm thấy tôi trên Instagram tại đây và trên Twitter tại đây.
Ngoài ra, tôi muốn chỉ ra một tài nguyên duy nhất mà tôi đã sử dụng để bắt đầu với Jetpack Compose:Các mẫu mã làm việc từ các nhà phát triển giỏi.