1. Trước khi bắt đầu
Giới thiệu
Vậy là bạn đã tìm hiểu mọi kiến thức về cách tạo ứng dụng Android bằng Compose. Điều đó thật tuyệt! Compose là một công cụ vô cùng mạnh mẽ giúp đơn giản hoá quá trình phát triển ứng dụng. Tuy nhiên, không phải lúc nào các ứng dụng Android cũng được tạo bằng giao diện người dùng mang tính khai báo. Compose là công cụ còn rất mới trong lịch sử phát triển Ứng dụng Android. Ban đầu, giao diện người dùng Android được tạo bằng Khung hiển thị. Do đó, rất có thể bạn sẽ gặp phải Khung hiển thị khi tiếp tục hành trình của một nhà phát triển Android. Trong lớp học lập trình này, bạn sẽ tìm hiểu kiến thức cơ bản về các cách để tạo ứng dụng Android trước khi Compose ra đời — bằng XML, Khung hiển thị, Liên kết khung hiển thị (View Binding) và Mảnh (Fragment).
Điều kiện tiên quyết:
- Hoàn thành tài liệu môn học trình bày Kiến thức cơ bản về tạo ứng dụng Android bằng Compose thông qua Bài 7.
Bạn cần có
- Máy tính có kết nối Internet và Android Studio.
- Một thiết bị hoặc trình mô phỏng
- Mã khởi đầu cho ứng dụng Juice Tracker
Sản phẩm bạn sẽ tạo ra
Trong lớp học lập trình này, bạn sẽ hoàn thiện ứng dụng Juice Tracker. Ứng dụng này cho phép bạn theo dõi các loại nước ép nổi bật bằng cách tạo một danh sách chứa các mục chi tiết. Bạn sẽ thêm và sửa đổi các Mảnh cũng như XML để hoàn tất giao diện người dùng và mã khởi đầu. Cụ thể, bạn sẽ tạo biểu mẫu nhập thông tin để tạo ra một loại nước ép mới, bao gồm giao diện người dùng và mọi logic hoặc thành phần điều hướng có liên quan. Kết quả thu được là một ứng dụng có danh sách trống mà bạn có thể thêm những loại nước ép của riêng mình.
2. Tải đoạn mã khởi đầu
- Trong Android Studio, hãy mở thư mục
basic-android-kotlin-compose-training-juice-tracker
. - Mở mã ứng dụng Juice Tracker trong Android Studio.
3. Tạo bố cục
Khi tạo ứng dụng bằng Views
, bạn sẽ tạo giao diện người dùng bên trong một Bố cục. Các bố cục thường được khai báo bằng XML. Các tệp bố cục XML này nằm trong thư mục tài nguyên trong phần res > layout (tài nguyên > bố cục). Bố cục chứa các thành phần tạo nên giao diện người dùng; các thành phần này được gọi là View
. Cú pháp XML bao gồm các thẻ, phần tử và thuộc tính. Để biết thêm thông tin về cú pháp XML, hãy tham khảo lớp học lập trình Tạo bố cục XML cho Android.
Trong phần này, bạn sẽ xây dựng bố cục XML cho hộp thoại nhập thông tin về "Loại nước ép" như trong hình.
- Tạo một Layout Resource File (Tệp tài nguyên bố cục) mới trong thư mục main > res > layout có tên là
fragment_entry_dialog
.
Bố cục fragment_entry_dialog.xml
chứa các thành phần giao diện người dùng mà ứng dụng hiển thị đến người dùng.
Lưu ý rằng Root element (Thành phần gốc) là một ConstraintLayout
. Loại bố cục này là một ViewGroup
cho phép bạn xác định vị trí và kích thước cho Thành phần hiển thị theo cách linh hoạt bằng cách dùng các quy tắc hạn chế. ViewGroup
là một loại View
chứa các View
khác, được gọi là View
con. Các bước sau đây sẽ trình bày chi tiết hơn về chủ đề này. Tuy nhiên, bạn có thể tìm hiểu thêm về ConstraintLayout
trong bài viết Tạo giao diện người dùng thích ứng bằng ConstraintLayout.
- Sau khi tạo tệp, hãy xác định vùng chứa tên ứng dụng trong
ConstraintLayout
.
fragment_entry_dialog.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
- Thêm các nguyên tắc sau vào
ConstraintLayout
.
fragment_entry_dialog.xml
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="16dp" />
Những Guideline
này là khoảng đệm cho các thành phần hiển thị khác. Các nguyên tắc này ràng buộc văn bản tiêu đề có tên "Type of juice" (Loại nước ép).
- Tạo một phần tử
TextView
.TextView
này đại diện cho tiêu đề của mảnh chi tiết.
- Thiết lập cho
TextView
mộtid
thuộcheader_title
. - Thiết lập
layout_width
thành0dp
. Cuối cùng, các điều kiện ràng buộc về bố cục sẽ xác định chiều rộng củaTextView
này. Do đó, việc xác định chiều rộng chỉ thêm các phép tính không cần thiết trong quá trình vẽ giao diện người dùng; việc đặt chiều rộng là0dp
sẽ tránh được nhiều phép tính thừa. - Đặt thuộc tính
TextView text
thành@string/juice_type
. - Đặt
textAppearance
thành@style/TextAppearance.MaterialComponents.Headline5
.
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />
Cuối cùng, bạn cần xác định các điều kiện ràng buộc. Không giống như Guideline
có sử dụng kích thước làm điều kiện ràng buộc, chính các nguyên tắc cũng ràng buộc TextView
này. Để có được kết quả này, bạn có thể tham chiếu mã nhận dạng của Guideline
mà bạn muốn ràng buộc khung hiển thị.
- Ràng buộc phần đầu của tiêu đề với phần cuối của
guideline_top
.
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintTop_toBottomOf="@+id/guideline_top" />
- Ràng buộc điểm kết thúc với điểm bắt đầu của
guideline_middle
và điểm bắt đầu với điểm bắt đầu củaguideline_left
để hoàn tất việc xác định vị trí củaTextView
. Xin lưu ý rằng cách bạn ràng buộc một khung hiển thị cụ thể phụ thuộc hoàn toàn vào giao diện người dùng mà bạn muốn.
fragment_entry_dialog.xml
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/juice_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintTop_toBottomOf="@+id/guideline_top"
app:layout_constraintEnd_toStartOf="@+id/guideline_middle"
app:layout_constraintStart_toStartOf="@+id/guideline_left" />
Thử xây dựng phần còn lại trên giao diện người dùng dựa trên các ảnh chụp màn hình. Bạn có thể tìm thấy tệp fragment_entry_dialog.xml
hoàn chỉnh trong phần giải pháp.
4. Tạo mảnh với Thành phần hiển thị
Trong Compose, bạn tạo các bố cục theo cách khai báo bằng Kotlin hoặc Java. Bạn có thể truy cập vào các "màn hình" khác nhau bằng cách di chuyển đến những Thành phần kết hợp khác nhau, thường là trong cùng một hoạt động. Khi tạo ứng dụng bằng Khung hiển thị, một Mảnh lưu trữ bố cục XML sẽ thay thế khái niệm của một "màn hình" Thành phần kết hợp.
Trong phần này, bạn sẽ tạo một Fragment
để lưu trữ bố cục fragment_entry_dialog
và cung cấp dữ liệu cho giao diện người dùng.
- Trong gói
juicetracker
, hãy tạo một lớp mới có tên làEntryDialogFragment
. - Làm cho
EntryDialogFragment
mở rộngBottomSheetDialogFragment
.
EntryDialogFragment.kt
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class EntryDialogFragment : BottomSheetDialogFragment() {
}
DialogFragment
là một Fragment
hiển thị hộp thoại nổi. BottomSheetDialogFragment
kế thừa từ lớp DialogFragment
, nhưng hiển thị một trang tính có chiều rộng màn hình được ghim vào cuối màn hình. Phương thức này phù hợp với giao diện thiết kế được thể hiện trước đây.
- Nếu bạn tạo lại dự án, các tệp Liên kết khung hiển thị dựa trên bố cục
fragment_entry_dialog
sẽ tự động được tạo. Liên kết khung hiển thị cho phép bạn truy cập và tương tác với cácView
được khai báo XML, bạn có thể đọc thêm về các liên kết này trong tài liệu Liên kết khung hiển thị. - Trong lớp
EntryDialogFragment
, hãy triển khai hàmonCreateView()
. Đúng như tên gọi, hàm này tạoView
choFragment
này.
EntryDialogFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
Hàm onCreateView()
trả về View
, nhưng hiện không trả về một View
hữu ích.
- Trả về
View
được tạo bằng cách mở rộngFragmentEntryDialogViewBinding
thay vì trả vềsuper.onCreateView()
.
EntryDialogFragment.kt
import com.example.juicetracker.databinding.FragmentEntryDialogBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return FragmentEntryDialogBinding.inflate(inflater, container, false).root
}
- Tạo một thực thể của
EntryViewModel
bên ngoài hàmonCreateView()
, nhưng bên trong lớpEntryDialogFragment
. - Triển khai hàm
onViewCreated()
.
Sau khi mở rộng Liên kết khung hiển thị, bạn có thể truy cập và sửa đổi View
trong bố cục. Phương thức onViewCreated()
được gọi sau onCreateView()
trong vòng đời. Bạn nên sử dụng phương thức onViewCreated()
để truy cập và sửa đổi các View
trong bố cục.
- Tạo một thực thể của liên kết khung hiển thị bằng cách gọi phương thức
bind()
trênFragmentEntryDialogBinding
.
Khi đó, đoạn mã của bạn sẽ trông giống như ví dụ sau:
EntryDialogFragment.kt
import androidx.fragment.app.viewModels
import com.example.juicetracker.ui.AppViewModelProvider
import com.example.juicetracker.ui.EntryViewModel
class EntryDialogFragment : BottomSheetDialogFragment() {
private val entryViewModel by viewModels<EntryViewModel> { AppViewModelProvider.Factory }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return FragmentEntryDialogBinding.inflate(inflater, container, false).root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
}
}
Bạn có thể truy cập và thiết lập các Khung hiển thị thông qua liên kết. Ví dụ: bạn có thể đặt TextView
thông qua phương thức setText()
.
binding.name.setText("Apple juice")
Giao diện người dùng của hộp thoại nhập thông tin có vai trò là nơi người dùng tạo một mục mới. Tuy nhiên, bạn cũng có thể dùng giao diện này để sửa đổi một mục hiện có. Do đó, Mảnh này cần truy xuất một mục đã nhấp. Thành phần điều hướng hỗ trợ việc di chuyển đến EntryDialogFragment
và truy xuất một mục đã nhấp.
EntryDialogFragment
chưa hoàn tất, nhưng bạn đừng lo lắng! Bây giờ, hãy chuyển sang phần tiếp theo để tìm hiểu thêm về cách sử dụng Thành phần điều hướng trong một ứng dụng bằng View
.
5. Sửa đổi Thành phần điều hướng
Trong phần này, bạn sẽ dùng thành phần điều hướng để mở hộp thoại nhập thông tin và truy xuất một mục, nếu có.
Compose tạo cơ hội để hiển thị nhiều thành phần kết hợp chỉ bằng cách gọi chúng. Tuy nhiên, các Mảnh hoạt động không giống nhau. Thành phần điều hướng điều phối "đích đến" của Mảnh. Nhờ đó, bạn có thể dễ dàng di chuyển giữa các Mảnh và Khung hiển thị có trong đó.
Sử dụng Thành phần điều hướng để điều hướng đến EntryDialogFragment
.
- Mở tệp
nav_graph.xml
và nhớ chọn thẻ Design (Thiết kế). - Nhấp vào biểu tượng để thêm một đích đến mới.
- Chọn đích
EntryDialogFragment
. Thao tác này sẽ khai báoentryDialogFragment
trong biểu đồ điều hướng, giúp bạn có thể truy cập vào các thao tác điều hướng.
Bạn cần khởi chạy EntryDialogFragment
từ TrackerFragment
. Do đó, một thao tác điều hướng cần hoàn thành tác vụ này.
- Kéo con trỏ qua
trackerFragment
. Chọn dấu chấm màu xám rồi kéo đường thẳng đó đếnentryDialogFragment
. - Khung hiển thị bản thiết kế nav_graph cho phép bạn khai báo các đối số cho một đích đến bằng cách chọn đích đó và nhấp vào biểu tượng bên cạnh trình đơn thả xuống Arguments (Đối số). Dùng tính năng này để thêm một đối số
itemId
thuộc loạiLong
vàoentryDialogFragment
; giá trị mặc định phải là0L
.
Xin lưu ý rằng TrackerFragment
chứa danh sách các mục Juice
. Nếu bạn nhấp vào một trong các mục này, EntryDialogFragment
sẽ khởi chạy.
- Tạo lại dự án. Giờ đây, bạn có thể truy cập vào đối số
itemId
trongEntryDialogFragment
.
6. Hoàn tất mảnh
Với dữ liệu từ các đối số điều hướng, hãy hoàn tất hộp thoại nhập thông tin.
- Truy xuất
navArgs()
trong phương thứconViewCreated()
củaEntryDialogFragment
. - Truy xuất
itemId
từnavArgs()
. - Triển khai
saveButton
để lưu loại nước ép mới/đã sửa đổi bằng cách sử dụngViewModel
.
Hãy nhớ lại giao diện hộp thoại nhập thông tin, giá trị màu sắc mặc định sẽ là màu đỏ. Giờ hãy truyền thông tin này dưới dạng một phần giữ chỗ.
Truyền mã mục từ đối số khi gọi saveJuice()
.
EntryDialogFragment.kt
import androidx.navigation.fragment.navArgs
import com.example.juicetracker.data.JuiceColor
class EntryDialogFragment : BottomSheetDialogFragment() {
//...
var selectedColor: JuiceColor = JuiceColor.Red
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
val args: EntryDialogFragmentArgs by navArgs()
val juiceId = args.itemId
binding.saveButton.setOnClickListener {
entryViewModel.saveJuice(
juiceId,
binding.name.text.toString(),
binding.description.text.toString(),
selectedColor.name,
binding.ratingBar.rating.toInt()
)
}
}
}
- Sau khi lưu dữ liệu, hãy loại bỏ hộp thoại bằng phương thức
dismiss()
.
EntryDialogFragment.kt
class EntryDialogFragment : BottomSheetDialogFragment() {
//...
var selectedColor: JuiceColor = JuiceColor.Red
//...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val binding = FragmentEntryDialogBinding.bind(view)
val args: EntryDialogFragmentArgs by navArgs()
binding.saveButton.setOnClickListener {
entryViewModel.saveJuice(
juiceId,
binding.name.text.toString(),
binding.description.text.toString(),
selectedColor.name,
binding.ratingBar.rating.toInt()
)
dismiss()
}
}
}
Xin lưu ý rằng mã ở trên không hoàn tất EntryDialogFragment
. Bạn vẫn cần triển khai một số hoạt động, chẳng hạn như điền vào các trường có dữ liệu Juice
hiện có (nếu thích hợp), chọn màu sắc từ colorSpinner
, triển khai cancelButton
, v.v. Tuy nhiên, mã này không phải là duy nhất đối với Fragment
. Bạn có thể triển khai mã này theo cách của mình. Hãy cố gắng triển khai các chức năng còn lại. Khi không còn cách nào khác, bạn có thể tham khảo mã nguồn giải pháp của lớp học lập trình này.
7. Khởi chạy hộp thoại nhập thông tin
Nhiệm vụ cuối cùng là khởi chạy hộp thoại nhập thông tin bằng Thành phần điều hướng. Hộp thoại nhập thông tin cần khởi chạy khi người dùng nhấp vào nút hành động nổi (FAB). Hộp thoại này cũng cần khởi chạy và truyền mã nhận dạng tương ứng khi người dùng nhấp vào một mục.
- Trong
onClickListener()
dành cho FAB, hãy gọinavigate()
trên trình điều khiển điều hướng.
TrackerFragment.kt
import androidx.navigation.findNavController
//...
binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(
)
}
//...
- Trong hàm điều hướng, hãy truyền thao tác điều hướng từ trình theo dõi đến hộp thoại nhập thông tin.
TrackerFragment.kt
//...
binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(
TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment()
)
}
//...
- Lặp lại thao tác này trong nội dung hàm lambda cho phương thức
onEdit()
trongJuiceListAdapter
, nhưng lần này, hãy truyềnid
củaJuice
.
TrackerFragment.kt
//...
onEdit = { drink ->
findNavController().navigate(
TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment(drink.id)
)
},
//...
8. Lấy mã nguồn giải pháp
Để tải mã này xuống khi lớp học lập trình đã kết thúc, bạn có thể sử dụng các lệnh git sau:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git $ cd basic-android-kotlin-compose-training-juice-tracker $ git checkout views
Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip rồi giải nén và mở trong Android Studio.
Nếu bạn muốn xem mã giải pháp, hãy xem mã đó trên GitHub.