Popular
- Get link
- X
- Other Apps
1. Phương pháp cũ trong việc tạo UI trên Android
Trước đây các ứng dụng Android đều có phần giao diện sử dụng XML là chính. Với phương pháp này sẽ giúp tách biệt được phần code giao diện và code logic một cách rõ ràng, nhưng nó cũng có rất nhiều vấn đề khi so sánh với các phương pháp mới hiện nay.
Một số nhược điểm dễ thấy khi sử dụng XML:
Giao diện XML thường khó quản lý khi cần thực hiện các thay đổi động theo thời gian thực, như thay đổi layout hoặc thêm/bớt các view trong quá trình chạy ứng dụng. Điều này thường đòi hỏi phải viết thêm mã trong Java/Kotlin để điều chỉnh.<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" ></LinearLayout>
Phải viết thêm code Java/Kotlin như bên dưới
LinearLayout linearLayout = findViewById(R.id.linearLayout);
TextView newTextView = new TextView(this);
newTextView.setText("New TextView");
newTextView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
linearLayout.addView(newTextView);
Khi ứng dụng phát triển lớn, các file XML có thể trở nên rất phức tạp và khó duy trì, đặc biệt nếu không có cách tổ chức hợp lý. Sự phức tạp này có thể làm tăng khả năng xảy ra lỗi và khó khăn trong việc gỡ rối.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView 1" />
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter something" />
<!-- Nhiều view khác, có thể lồng vào nhau -->
</LinearLayout>
XML có thể gây ra một số vấn đề về hiệu suất nếu giao diện phức tạp với nhiều lớp lồng nhau (nested layouts). Việc phân tích cú pháp (parsing) XML và tạo ra các đối tượng UI tương ứng có thể tốn thời gian và tài nguyên hệ thống.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView 3" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView 4" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
Đối với những giao diện có yêu cầu cao về thiết kế hoặc giao diện tùy chỉnh, XML có thể hạn chế khả năng sáng tạo và yêu cầu bạn phải sử dụng nhiều mã Java/Kotlin hoặc thậm chí phải tạo các view tùy chỉnh.
CustomView customView = new CustomView(context);
customView.setCustomAttribute(...); // Tùy chỉnh đặc biệt
layout.addView(customView);
XML không hỗ trợ tốt cho việc tái sử dụng các thành phần giao diện. Mặc dù có thể tạo các styles và themes để áp dụng cho nhiều view, nhưng việc tái sử dụng các thành phần giao diện phức tạp vẫn cần nhiều nỗ lực hơn.
<!-- Không có cách dễ dàng để tái sử dụng đoạn mã dưới đây trong nhiều layout -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/commonTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reusable TextView" />
<Button
android:id="@+id/commonButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reusable Button" />
</LinearLayout>
Jetpack Compose được thiết kế để tối ưu hóa hiệu suất. Nó giảm thiểu việc phân tích cú pháp và xây dựng giao diện vì tất cả đều được thực hiện trực tiếp trong mã Kotlin, không cần phải chuyển đổi từ XML sang đối tượng Java. Ngoài ra, các tối ưu hóa như chỉ cập nhật lại các phần cần thiết của giao diện (recomposition) giúp cải thiện hiệu suất đáng kể.
Compose cung cấp khả năng tùy chỉnh linh hoạt và mạnh mẽ. Dễ dàng tạo các thành phần UI tùy chỉnh mà không cần phải tạo lớp kế thừa từ View như trong XML. Việc tạo các UI phức tạp trở nên dễ dàng hơn, và việc quản lý các thành phần này cũng đơn giản hơn. CustomButton có thể được dễ dàng tạo ra mà không cần tạo subclass
Để khắc phục những hạn chế này, chúng ta có thể sử dụng kết hợp XML với code Java/Kotlin hoặc sử dụng các công cụ và phương pháp thiết kế giao diện hiện đại hơn, như Jetpack Compose mà chúng ta sẽ tìm hiểu trong bài viết này.
2. Cách mà Jetpack compose tiếp cận
Jetpack Compose là một công cụ hiện đại của Android để xây dựng giao diện người dùng, và nó cải thiện nhiều vấn đề mà XML gặp phải trong việc thiết kế giao diện.
Jetpack Compose sử dụng một mô hình lập trình khai báo, mô tả giao diện dưới dạng các hàm Kotlin. Điều này giúp việc cập nhật giao diện trở nên dễ dàng và tự nhiên hơn. Chỉ cần thay đổi trạng thái, và giao diện sẽ tự động được cập nhật theo. Điều này cải thiện rất nhiều so với việc phải thao tác thủ công với các view trong XML. Trong ví dụ bên dưới, khi count
thay đổi, Text và Button sẽ được cập nhật tự động mà không cần gọi các phương thức như findViewById()
hay setText()
như trong XML.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
@Composable
fun UserProfile(user: User) {
Column {
Text("Name: ${user.name}")
Text("Age: ${user.age}")
}
}
Jetpack Compose được thiết kế để tối ưu hóa hiệu suất. Nó giảm thiểu việc phân tích cú pháp và xây dựng giao diện vì tất cả đều được thực hiện trực tiếp trong mã Kotlin, không cần phải chuyển đổi từ XML sang đối tượng Java. Ngoài ra, các tối ưu hóa như chỉ cập nhật lại các phần cần thiết của giao diện (recomposition) giúp cải thiện hiệu suất đáng kể.
Compose cung cấp khả năng tùy chỉnh linh hoạt và mạnh mẽ. Dễ dàng tạo các thành phần UI tùy chỉnh mà không cần phải tạo lớp kế thừa từ View như trong XML. Việc tạo các UI phức tạp trở nên dễ dàng hơn, và việc quản lý các thành phần này cũng đơn giản hơn. CustomButton có thể được dễ dàng tạo ra mà không cần tạo subclass
CustomButton
:@Composable
fun CustomButton(onClick: () -> Unit, label: String) {
Button(onClick = onClick) {
Text(label)
}
}
Với Jetpack Compose, dễ dàng tái sử dụng các thành phần giao diện bằng cách tạo các hàm Compose đơn giản và có thể sử dụng lại trong nhiều phần của ứng dụng. Việc tái sử dụng mã này trở nên dễ dàng và hiệu quả hơn rất nhiều so với XML. Trong ví dụ phía trên, chúng ta có thể dễ dàng tái sử dụng CustomButton một cách dễ dàng ở nhiều nơi trong giao diện.
3. Một số thành phần UI
Text
là thành phần dùng để hiển thị văn bản trên màn hình.
@Composablefun MyText() {Text(text = "Hello, Jetpack Compose!")}
Button
là thành phần dùng để hiển thị một nút bấm có thể tương tác.
@Composablefun MyButton() {Button(onClick = { /* TODO: Handle button click */ }) {Text("Click Me")}}
TextField
là thành phần dùng để nhận đầu vào văn bản từ người dùng.
@Composablefun MyTextField() {var text by remember { mutableStateOf("") }TextField(value = text,onValueChange = { newText -> text = newText },label = { Text("Enter your name") })}
Image
là thành phần dùng để hiển thị hình ảnh.
@Composablefun MyImage() {Image(painter = painterResource(id = R.drawable.my_image),contentDescription = "My Image")}
Row
là thành phần dùng để sắp xếp các phần tử con theo chiều ngang.
@Composablefun MyColumn() {Column {Text("First")Text("Second")Text("Third")}}
Card
là thành phần dùng để hiển thị một khối giao diện với hiệu ứng nổi bật.
@Composablefun MyCard() {Card(elevation = 8.dp,shape = RoundedCornerShape(8.dp),) {Text("This is a card")}}
Scaffold
là thành phần bố cục cơ bản cung cấp một khung làm việc cho các màn hình ứng dụng, bao gồm các thành phần nhưAppBar
,BottomNavigation
,FAB
, v.v.
@Composablefun MyScaffold() {Scaffold(topBar = {TopAppBar(title = { Text("My App") })},floatingActionButton = {FloatingActionButton(onClick = { /* TODO: Handle FAB click */ }) {Text("+")}}) {}}
LazyColumn
là thành phần dùng để hiển thị một danh sách cuộn theo chiều dọc. Nó chỉ dựng các phần tử hiển thị trên màn hình, giúp tiết kiệm tài nguyên.
@Composablefun MyLazyColumn() {LazyColumn {items(100) { index ->Text("Item #$index")}}}
Surface
là thành phần dùng để bao bọc các phần tử UI và thêm các thuộc tính như màu nền, hình dạng, hoặc độ nổi.
@Composablefun MySurface() {Surface(color = Color.Gray,shape = RoundedCornerShape(8.dp)) {Text("This is inside a surface")}}
4. Ví dụ về sử dụng Jetpack compose
Tạo một màn hình hiện danh sách các phương thức thanh toán. Button `continue` có thể được kích hoạt khi người dùng nhấn chọn một phương thức thanh toán bất kỳ.
class PaymentMethodListActivity.kt
Class này là một activity chứa danh sách và button `continue`
class PaymentMethodListActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {MyActivityScreen() // Set your composable function as the content}}}interface ChangePaymentMethodCallback {fun onPaymentMethodChanged(paymentMethod: String)}@OptIn(ExperimentalMaterial3Api::class)@Composablefun MyActivityScreen() {var selectedMethod by rememberSaveable { mutableStateOf("") }val changePaymentMethodCallback = object : ChangePaymentMethodCallback {override fun onPaymentMethodChanged(paymentMethod: String) {selectedMethod = paymentMethod}}Column (modifier = Modifier.fillMaxSize().padding(all = 20.dp),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){PaymentMethodList(changePaymentMethodCallback)Spacer(modifier = Modifier.height(20.dp))Button(onClick = { /* Handle something */ },enabled = selectedMethod != "") {Text("Continue",modifier = Modifier.padding(all = 10.dp).fillMaxWidth(),textAlign = TextAlign.Center)}}}
class PaymentMethodList.kt
Class này là một custom view để hiện danh sách các phương thức thanh toán và trả về kết quả chọn thông qua một callback để update UI.
data class PaymentMethod(val name: String,val icon: Int // Replace with ImageVector if using vector icons)@Composablefun PaymentMethodList(changePaymentMethodCallback: ChangePaymentMethodCallback,) {val paymentMethods = listOf(PaymentMethod("Visa", R.drawable.visa), // Replace with actual drawable resourcesPaymentMethod("PayPal", R.drawable.paypal),PaymentMethod("Alipay", R.drawable.alipay),// Add more payment methods as needed)var selectedMethod by remember { mutableStateOf<PaymentMethod?>(null) }LazyColumn(modifier = Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp)) {items(paymentMethods) { method ->PaymentMethodItem(method = method,isSelected = selectedMethod == method,onSelected = {selectedMethod = itselectedMethod?.name?.let { methodName ->changePaymentMethodCallback.onPaymentMethodChanged(methodName)}})}}}@Composablefun PaymentMethodItem(method: PaymentMethod,isSelected: Boolean,onSelected: (PaymentMethod) -> Unit) {Card(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp).clickable { onSelected(method) },elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),colors = CardDefaults.cardColors(containerColor = if (isSelected) Color.LightGray else Color.White)) {Row(modifier = Modifier.fillMaxWidth().padding(16.dp),verticalAlignment = Alignment.CenterVertically) {// Use Image if you have drawable resourcesImage(painter = painterResource(id = method.icon),contentDescription = method.name,modifier = Modifier.size(32.dp))Spacer(modifier = Modifier.width(16.dp))Text(text = method.name)Spacer(Modifier.weight(1f))if (isSelected) {Icon(imageVector = Icons.Filled.CheckCircle,contentDescription = "Selected",tint = Color.Green)}}}}
Trong ví dụ trên, có thể thay thế remember
và mutableStateOf bằng việc sử dụng ViewModel.
Class SelectedMethodViewModel.kt
class SelectedMethodViewModel : ViewModel() {
private val _selectedMethod = mutableStateOf("")
val count get() = _selectedMethod
fun changePaymentMethod(paymentMethod: String) {
_selectedMethod.value = paymentMethod;
}
}
Change function MyActivityScreen to use SelectedMethodViewModel class.
@Composable
fun MyActivityScreen() {
// var selectedMethod by rememberSaveable { mutableStateOf("") }
var selectedMethodViewModel = SelectedMethodViewModel()
val changePaymentMethodCallback = object : ChangePaymentMethodCallback {
override fun onPaymentMethodChanged(paymentMethod: String) {
selectedMethodViewModel.changePaymentMethod(paymentMethod)
}
}
Column (
modifier = Modifier
.fillMaxSize()
.padding(all = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
){
PaymentMethodList(changePaymentMethodCallback)
Spacer(modifier = Modifier.height(20.dp))
Button(
onClick = { /* Handle something */ },
enabled = selectedMethodViewModel.selectedMethod.value != ""
) {
Text("Continue",
modifier = Modifier.padding(all = 10.dp)
.fillMaxWidth(),
textAlign = TextAlign.Center
)
}
}
}
Sử dụngLiveData
hoặcFlow
class SelectedMethodViewModel : ViewModel() {
private val _selectedMethod = MutableLiveData("")
val selectedMethod get() = _selectedMethod
fun changePaymentMethod(paymentMethod: String) {
_selectedMethod.value = paymentMethod;
}
}
Khả năng tích hợp với dự án sử dụng XML cũ
Vì có thể sử dụng được ViewModel, nên rất dễ tích hợp những ViewModel có sẵn cho các
view mới sử dụng Jetpack Compose, từ đó chuyển dần các dự án cũ sang sử dụng Jetpack
compose mà không cần phải viết lại toàn bộ ứng dụng.
Để sử dụng Jetpack Compose trong một ứng dụng Android hiện có, bạn chỉ cần thêm các thư
viện Jetpack compose vào build.gradle như dưới đây:
implementation "androidx.activity:activity-compose:1.7.2"
implementation "androidx.compose.ui:ui:1.4.3"
implementation "androidx.compose.material:material:1.4.3"
implementation "androidx.compose.ui:ui-tooling-preview:1.4.3"
Bạn có thể tạo các compose view cho activity và fragment hoặc sử dụng compose view
trong xml file.
Tổng Kết
So sánh giữa XML và Jetpack Compose
Jetpack Compose là một bước tiến lớn trong việc phát triển giao diện người dùng Android, giúp các nhà phát triển dễ dàng hơn trong việc xây dựng các ứng dụng hiện đại, đẹp mắt, và có hiệu suất cao. Sự linh hoạt, khả năng tùy biến, và tích hợp chặt chẽ với Kotlin là những điểm mạnh nổi bật khiến Jetpack Compose trở thành lựa chọn hấp dẫn cho cả các dự án mới và các dự án hiện có đang xem xét việc chuyển đổi từ giao diện XML truyền thống. Tuy nhiên, như với bất kỳ công nghệ mới nào, việc tìm hiểu và làm quen với Jetpack Compose đòi hỏi thời gian và nỗ lực từ phía các nhà phát triển.
Đăng ký kênh Thạch Phạm Dev hoặc Jade Engineer để theo dõi nhiều nội dung thú vị nhé.
- Get link
- X
- Other Apps
Comments
Post a Comment