1. Giới thiệu tổng quan

Trong quá trình phát triển ứng dụng Android, việc hiển thị dữ liệu dạng bảng (table) là một nhu cầu phổ biến, đặc biệt đối với các ứng dụng quản lý, thống kê, hoặc hiển thị danh sách có cấu trúc đa chiều. Tuy Android không cung cấp một widget “Table” mạnh mẽ như trong HTML, nhưng với TableLayout, TableRow, RecyclerView, GridLayout, và các thư viện bên thứ ba, bạn vẫn có thể tạo ra những bảng đẹp mắt, linh hoạt và hiệu năng cao.

Bài viết này sẽ:

  • Giải thích khái niệm và cách hoạt động của TableLayout và TableRow.
  • So sánh TableLayout với các giải pháp thay thế (RecyclerView + GridLayoutManager, GridLayout, FlexboxLayout, etc.).
  • Hướng dẫn chi tiết cách tạo bảng đơn giản, bảng có tiêu đề cố định, bảng cuộn ngang và dọc.
  • Đưa ra các mẹo tối ưu hoá hiệu suất và UI/UX.
  • Cung cấp ví dụ thực tế, source code và giải thích từng dòng.
  • Giới thiệu một số thư viện mở rộng giúp vẽ table nhanh hơn (TableView, Android-TableView, etc.).

Sau khi đọc bài này, bạn sẽ có thể tự tin xây dựng bất kỳ loại bảng nào trong Android, từ bảng tĩnh chỉ hiển thị dữ liệu cho đến bảng động, có pagination, sorting, và filter.

2. Cấu trúc cơ bản của TableLayout

2.1. TableLayout là gì?

TableLayout là một ViewGroup trong Android, cho phép bố trí các thành phần con (thường là TableRow) theo dạng lưới giống như bảng trong HTML. Các đặc điểm chính:

Cách Vẽ Table Trong Android
Cách Vẽ Table Trong Android
  • Mỗi hàng được biểu diễn bằng một TableRow.
  • Mỗi cột được xác định bằng vị trí của child view trong TableRow.
  • Chiều rộng cột được tính tự động dựa trên nội dung rộng nhất của cột, trừ khi bạn thiết lập android:layout_column hoặc android:layout_span.
  • Không có scroll mặc định; bạn cần đặt trong ScrollView hoặc HorizontalScrollView nếu muốn cuộn.

2.2. TableRow

TableRow là một LinearLayout ngang, mỗi child view trong TableRow sẽ chiếm một cột. Bạn có thể:

  • Đặt android:layout_span="2" để một ô chiếm 2 cột.
  • Đặt android:layout_column="3" để đặt ô vào cột thứ 3 (đối với API 21+).

2.3. Đặc điểm quan trọng

Thuộc tính Mô tả Giá trị thường dùng
android:stretchColumns Xác định cột sẽ kéo dài để lấp đầy không gian còn lại. "" (tất cả), "0,2" (cột 0 và 2)
android:shrinkColumns Cho phép cột co lại nếu không đủ không gian. ""
android:collapseColumns Ẩn cột (không hiển thị). "1"
android:layout_span (trong TableRow) Ô chiếm bao nhiêu cột. Số nguyên

3. Tạo bảng đơn giản bằng TableLayout

3.1. Bước 1: Định nghĩa layout XML

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="match_parent"> <TableLayout android:id="@+id/tableLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:stretchColumns="" android:shrinkColumns="" android:padding="8dp"> <!-- Header --> <TableRow android:background="#FFDDDDDD" android:padding="8dp"> <TextView android:text="STT" android:textStyle="bold" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text="Tên sản phẩm" android:textStyle="bold" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text="Giá" android:textStyle="bold" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </TableRow> <!-- Dòng dữ liệu mẫu --> <TableRow android:padding="8dp"> <TextView android:text="1" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text="Bánh mì" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text="15,000₫" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </TableRow> <!-- Thêm các TableRow khác ở đây --> </TableLayout>
</ScrollView>

Giải thích:

  • ScrollView giúp bảng cuộn dọc nếu nội dung vượt chiều cao màn hình.
  • android:stretchColumns="" làm cho các cột tự động mở rộng để lấp đầy chiều rộng còn lại.
  • Header được tô nền khác để dễ phân biệt.

3.2. Bước 2: Thêm dữ liệu động từ Java/Kotlin

class MainActivity : AppCompatActivity() { private lateinit var tableLayout: TableLayout data class Product(val id: Int, val name: String, val price: String) private val productList = listOf( Product(1, "Bánh mì", "15,000₫"), Product(2, "Cà phê", "20,000₫"), Product(3, "Trà sữa", "30,000₫") ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) tableLayout = findViewById(R.id.tableLayout) // Bắt đầu từ index 1 vì row 0 là header for (product in productList) { val row = layoutInflater.inflate(R.layout.table_row_item, null) as TableRow // Đặt dữ liệu (row.getChildAt(0) as TextView).text = product.id.toString() (row.getChildAt(1) as TextView).text = product.name (row.getChildAt(2) as TextView).text = product.price // Thêm vào TableLayout tableLayout.addView(row) } }
}

File table_row_item.xml (được tái sử dụng cho mỗi dòng):

<TableRow xmlns:android="http://schemas.android.com/apk/res/android" android:padding="8dp"> <TextView android:id="@+id/tvId" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/tvName" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/tvPrice" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</TableRow>

Lưu ý: Khi dữ liệu lớn (hàng > 50), việc sử dụng TableLayout sẽ gây lag vì mỗi hàng là một view riêng. Trong trường hợp này, chúng ta nên chuyển sang RecyclerView.

Cách Vẽ Table Trong Android
Cách Vẽ Table Trong Android

4. Khi nào nên dùng RecyclerView + GridLayoutManager thay vì TableLayout?

Tiêu chí TableLayout RecyclerView + GridLayoutManager
Số lượng hàng < 30 (tĩnh) > 30, dữ liệu động, pagination
Hiệu năng Tải toàn bộ view vào bộ nhớ Reuse view holder, chỉ tạo những hàng hiển thị
Tính năng nâng cao Giới hạn (sort, filter) Dễ dàng tích hợp sort, filter, animation
Cuộn ngang + dọc Cần lồng HorizontalScrollView + ScrollView (khó) LinearLayoutManager hoặc GridLayoutManager hỗ trợ cả 2 chiều
Độ phức tạp UI Đơn giản, ít tùy biến Linh hoạt, hỗ trợ item decoration, click listener, v.v.

Kết luận: Nếu bảng của bạn chỉ có vài chục dòng và không cần tính năng phức tạp, TableLayout là đủ. Ngược lại, hãy chuyển sang RecyclerView.

5. Tạo bảng cuộn ngang và dọc bằng RecyclerView

5.1. Layout XML

<androidx.recyclerview.widget.RecyclerView android:id="@+id/rvTable" android:layout_width="match_parent" android:layout_height="match_parent" android:overScrollMode="never"/>

5.2. Adapter

class TableAdapter( private val header: List<String>, private val data: List<List<String>>
) : RecyclerView.Adapter<TableAdapter.TableViewHolder>() { inner class TableViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val container: LinearLayout = itemView.findViewById(R.id.rowContainer) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TableViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_table_row, parent, false) return TableViewHolder(view) } override fun getItemCount(): Int = data.size + 1 // +1 cho header override fun onBindViewHolder(holder: TableViewHolder, position: Int) { val rowData = if (position == 0) header else dataposition - 1 holder.container.removeAllViews() for (cell in rowData) { val tv = TextView(holder.itemView.context).apply { text = cell gravity = Gravity.CENTER setPadding(8, 8, 8, 8) layoutParams = LinearLayout.LayoutParams( 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f // chia đều không gian ) // Định dạng header if (position == 0) { setBackgroundColor(Color.LTGRAY) setTypeface(null, Typeface.BOLD) } else { setBackgroundColor(Color.WHITE) } } holder.container.addView(tv) } }
}

File item_table_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rowContainer" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"/>

5.3. Cấu hình RecyclerView trong Activity/Fragment

Cách Vẽ Table Trong Android
Cách Vẽ Table Trong Android
class TableActivity : AppCompatActivity() { private lateinit var rvTable: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_table) rvTable = findViewById(R.id.rvTable) // Dữ liệu mẫu val header = listOf("STT", "Tên", "Giá", "Số lượng") val rows = mutableListOf<List<String>>() for (i in 1..200) { rows.add(listOf(i.toString(), "Sản phẩm $i", "${i 1000}₫", (i % 10 + 1).toString())) } rvTable.layoutManager = LinearLayoutManager(this) // cuộn dọc rvTable.adapter = TableAdapter(header, rows) rvTable.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL)) }
}

5.4. Cuộn ngang (nếu số cột quá nhiều)

Khi số cột lớn, mỗi TableRow có thể vượt chiều rộng màn hình. Ta có thể lồng RecyclerView trong một HorizontalScrollView, hoặc tạo một RecyclerView lồng nhau (nested RecyclerView). Dưới đây là cách nhanh nhất:

<HorizontalScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rvTable" android:layout_width="wrap_content" android:layout_height="match_parent"/>
</HorizontalScrollView>
  • android:layout_width="wrap_content" cho phép RecyclerView rộng hơn màn hình, và HorizontalScrollView sẽ cho phép người dùng kéo sang trái/phải.

6. Sử dụng GridLayout cho bảng cố định (không động)

Nếu bảng có số cột cố định và bạn muốn bố trí các ô như một lưới, GridLayout là lựa chọn thay thế.

6.1. Ví dụ GridLayout 4 cột

<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gridTable" android:layout_width="match_parent" android:layout_height="wrap_content" android:columnCount="4" android:rowCount="auto" android:padding="8dp"> <!-- Header --> <TextView android:text="STT" android:background="#FFCCCCCC" android:gravity="center" android:layout_columnWeight="1" android:layout_row="0" android:layout_column="0" android:padding="8dp"/> <TextView android:text="Tên" android:background="#FFCCCCCC" android:gravity="center" android:layout_columnWeight="1" android:layout_row="0" android:layout_column="1" android:padding="8dp"/> <TextView android:text="Giá" android:background="#FFCCCCCC" android:gravity="center" android:layout_columnWeight="1" android:layout_row="0" android:layout_column="2" android:padding="8dp"/> <TextView android:text="SL" android:background="#FFCCCCCC" android:gravity="center" android:layout_columnWeight="1" android:layout_row="0" android:layout_column="3" android:padding="8dp"/> <!-- Dòng dữ liệu --> <TextView android:text="1" android:gravity="center" android:layout_columnWeight="1" android:layout_row="1" android:layout_column="0" android:padding="8dp"/> <!-- Tiếp tục các TextView cho các cột còn lại... --> </GridLayout>
  • android:layout_columnWeight giúp các cột có cùng trọng số, tự động chia đều chiều rộng.
  • android:rowCount="auto" để Android tự tính số hàng dựa trên số phần tử.

Ưu điểm: Đơn giản cho bảng tĩnh, không cần adapter.

Nhược điểm: Khi dữ liệu lớn, việc thêm view thủ công không thực tế.

7. Thư viện bên thứ ba hỗ trợ vẽ Table

7.1. Android-TableView (github.com/evrencoskun/TableView)

Cách Vẽ Table Trong Android
Cách Vẽ Table Trong Android
  • Tính năng: Header cố định, sorting, filtering, custom cell view, drag‑and‑drop, pagination.
  • Cài đặt:
implementation 'com.evrencoskun.tableview:tableview:0.8.9.4'
  • Ví dụ nhanh:
val tableView = findViewById<TableView>(R.id.tableView)
val columnHeaderAdapter = MyColumnHeaderAdapter(columnHeaders)
val rowHeaderAdapter = MyRowHeaderAdapter(rowHeaders)
val cellAdapter = MyCellAdapter(cellData) tableView.setAdapter(columnHeaderAdapter, rowHeaderAdapter, cellAdapter)

Thư viện này sử dụng RecyclerView nội bộ, vì thế hiệu năng rất tốt ngay cả với hàng nghìn.

7.2. TableView (github.com/IsmaiL/TableView)

  • Đặc điểm: Dễ tích hợp, hỗ trợ scroll đồng thời cả chiều dọc và ngang, có thể lock cột/row.
  • Cài đặt:
implementation 'com.github.IsmaiL:TableView:1.3.0'

7.3. FlexboxLayout (Google)

Nếu bạn muốn bảng có kích thước linh hoạt, tự động xuống dòng khi không đủ chỗ, FlexboxLayout là công cụ hữu ích.

implementation 'com.google.android:flexbox:3.0.0'
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:flexbox="http://schemas.android.com/apk/res-auto" android:id="@+id/flexTable" android:layout_width="match_parent" android:layout_height="wrap_content" flexbox:flexWrap="wrap" flexbox:justifyContent="space_between"> <!-- Thêm TextView cho mỗi ô -->
</com.google.android.flexbox.FlexboxLayout>

Flexbox rất thích hợp cho các bảng có số cột thay đổi theo kích thước màn hình (responsive).

8. Tối ưu hoá UI/UX cho bảng

  1. Định dạng số và tiền tệ: Sử dụng NumberFormat.getCurrencyInstance(Locale("vi","VN")) để hiển thị giá.
  2. Đánh dấu hàng chẵn/lẻ: Thêm background màu xen kẽ để dễ đọc.
  3. Click/Long Click: Đặt setOnClickListener cho mỗi TableRow hoặc cell để thực hiện hành động (xem chi tiết, xóa, edit).
  4. Sticky Header: Khi dùng RecyclerView, có thể dùng RecyclerView.Adapter với 2 view type (header và item) hoặc sử dụng StickyHeaders library.
  5. Pagination: Khi dữ liệu quá lớn, tải từng trang (20, 50 hàng) và thêm Loading... footer.
  6. Accessibility: Đặt android:contentDescription cho mỗi ô, hỗ trợ TalkBack.
  7. Dark Mode: Định nghĩa màu trong colors.xml với night resource.

9. Xử lý sự kiện và tương tác nâng cao

9.1. Sort cột

Cách Vẽ Table Trong Android
Cách Vẽ Table Trong Android

Với RecyclerView, bạn có thể sort dữ liệu trong Adapter và gọi notifyDataSetChanged().

fun sortByColumn(columnIndex: Int, ascending: Boolean) { data = if (ascending) { data.sortedBy { itcolumnIndex } } else { data.sortedByDescending { itcolumnIndex } } notifyDataSetChanged()
}

Khi người dùng click vào header, gọi hàm này.

9.2. Filter (tìm kiếm)

fun filter(query: String) { val filtered = originalData.filter { row -> row.any { cell -> cell.contains(query, ignoreCase = true) } } data = filtered notifyDataSetChanged()
}

9.3. Drag‑and‑drop để sắp xếp lại hàng

Sử dụng ItemTouchHelper:

val callback = object : ItemTouchHelper.SimpleCallback( ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) { override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { val from = viewHolder.adapterPosition - 1 // -1 vì header val to = target.adapterPosition - 1 Collections.swap(data, from, to) adapter.notifyItemMoved(viewHolder.adapterPosition, target.adapterPosition) return true } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
}
ItemTouchHelper(callback).attachToRecyclerView(rvTable)

10. Kiểm tra và debug

  • Layout Inspector (Android Studio) để xem kích thước từng cell, phát hiện overflow.
  • Hierarchy Viewer để kiểm tra số lượng view khi dùng TableLayout (cố gắng giữ dưới 200 view).
  • Profiler: Kiểm tra FPS và memory khi cuộn nhanh.
  • Unit Test: Kiểm tra logic sort, filter bằng JUnit.

11. Tổng kết

Việc vẽ table trong Android không còn là thách thức khó khăn nếu bạn hiểu rõ các công cụ sẵn có:

Công cụ Khi nào dùng
TableLayout Bảng tĩnh, số hàng ít (<30), UI đơn giản.
RecyclerView + GridLayoutManager Bảng lớn, cần cuộn, sorting, pagination.
GridLayout Bảng cố định, không cần adapter, UI dạng lưới.
FlexboxLayout Bảng responsive, số cột thay đổi theo kích thước màn hình.
Thư viện TableView (Android-TableView, IsmaiL/TableView) Cần tính năng nâng cao (sticky header, drag‑drop, filter) mà không muốn tự xây dựng.

Bạn chỉ cần lựa chọn giải pháp phù hợp, áp dụng các mẫu code trên, và tùy biến thêm theo yêu cầu dự án. Hy vọng bài viết đã cung cấp đủ kiến thức và công cụ để bạn tự tin vẽ table trong Android một cách hiệu quả, tối ưu và đẹp mắt. Chúc bạn thành công!

Rate this post
Mục nhập này đã được đăng trong Blog. Đánh dấu trang permalink.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *