Một vùng thông báo tạm thời biến mất nhưng bạn muốn các nút bên dưới giữ vị trí. Ở màn hình khác, bạn lại muốn nội dung phía dưới dồn lên để tránh khoảng trống. Cả hai đều là ẩn View, nhưng quyết định về layout khác nhau.
Bài đối chiếu Android View và LinearLayout API ngày 03/11/2026. Các hằng visibility được dùng có từ API 1. Kotlin đã được rà thủ công theo hợp đồng View; chưa build Android, chạy emulator hoặc đo vị trí trên thiết bị. Mọi mô tả chuyển vị trí bên dưới là tiêu chí cần kiểm tra trên demo, không phải số đo đã thu được.
Chốt hành vi trước khi chọn hằng
VISIBLE cho View hiển thị. INVISIBLE ẩn View nhưng giữ phần không gian dành cho nó trong layout. GONE ẩn View và không dành không gian layout cho chính View ấy. Không cần ghi trực tiếp các số nguyên 0, 4, 8 trong code; tên hằng thể hiện mục đích rõ hơn.
Tác động cuối cùng lên các phần tử còn lại vẫn phụ thuộc cấu trúc cha. Một container có height cố định có thể giữ khoảng trống riêng của nó ngay cả khi một child trở thành GONE. Ví dụ chọn LinearLayout dọc, các child cao theo nội dung, để quan sát khác biệt đơn giản nhất.
Tạo màn hình thử không cần layout XML
Đặt hàm dưới trong module Android dùng Kotlin. Nó chỉ phụ thuộc các widget platform, không cần AppCompat hay extension KTX:
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
fun visibilityDemo(context: Context): View {
val root = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
}
fun add(view: View) {
root.addView(
view,
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
),
)
}
add(TextView(context).apply { text = "Vùng phía trên" })
val message = TextView(context).apply {
text = "Thông báo minh họa đang chiếm một vùng trong layout."
}
add(message)
add(TextView(context).apply { text = "Mốc phía dưới thông báo" })
val choices = listOf(
"Ẩn và giữ chỗ" to View.INVISIBLE,
"Ẩn và bỏ chỗ" to View.GONE,
"Hiện lại" to View.VISIBLE,
)
choices.forEach { (label, targetVisibility) ->
add(Button(context).apply {
text = label
setOnClickListener { message.visibility = targetVisibility }
})
}
return root
}
Từ một Activity thử đã được project cấu hình, gọi setContentView(visibilityDemo(this)) trong onCreate sau phần khởi tạo cần thiết. Hàm không tự tạo manifest, theme hoặc Activity cho bạn. Các chuỗi literal chỉ để demo; giao diện sản phẩm nên dùng string resources theo quy trình localization của app.
Nút chỉ đổi visibility của message, không xóa View khỏi root. Tham chiếu tới message còn tồn tại nên thao tác “Hiện lại” dùng đúng View trước đó. Demo không triển khai animation, lưu state qua process recreation hoặc luồng dữ liệu bất đồng bộ.
Kiểm tra bằng mốc bên dưới
Trên app thử, quan sát vị trí dòng “Mốc phía dưới thông báo” trước khi bấm nút. Với INVISIBLE, vùng thông báo không còn nhìn thấy nhưng khoảng chỗ của nó được giữ. Với GONE trong cấu trúc demo này, mốc bên dưới dự kiến dồn lên. Chọn VISIBLE để kiểm tra nội dung xuất hiện lại.
Thử tăng cỡ chữ hệ thống và đổi chiều màn hình. Không cần kỳ vọng một khoảng cách pixel cố định, vì text và bố cục có thể thay đổi. Nếu đo bằng layout callback, chờ quá trình layout phù hợp thay vì đọc tọa độ ngay sau phép gán rồi coi đó là kết quả cuối.
Những cách ẩn dễ bị nhầm
Đặt text thành chuỗi rỗng chỉ đổi nội dung, không diễn đạt cùng yêu cầu visibility. Đặt alpha bằng 0 cũng không thay thế hợp đồng layout của GONE. Chọn kỹ thuật theo điều cần kiểm soát: nội dung, độ trong suốt hay sự tham gia vào bố cục.
Nếu UI vẫn còn khoảng trống sau khi đặt GONE, hãy kiểm tra container, padding, margin và sibling khác, thay vì kết luận hằng không hoạt động. Với màn hình thật, kiểm tra thêm focus và trải nghiệm TalkBack khi vùng thông tin xuất hiện hoặc biến mất.
Giữ một case demo nhỏ giúp bạn tách câu hỏi layout khỏi các yếu tố khác. Chọn INVISIBLE khi cần giữ chỗ, GONE khi không cần phần chỗ của View, rồi xác nhận trong đúng cây giao diện mà app đang dùng.


