Giới thiệu
Android cung cấp 3 trạng thái hiển thị cho View (kể cả Button) thông qua setVisibility() hoặc XML attribute android:visibility:
- VISIBLE (0): Hiển thị bình thường, chiếm layout space.
- INVISIBLE (4): Ẩn nhưng **vẫn chiếm layout space** (khoảng trống vẫn còn).
- GONE (8): Ẩn và **không chiếm layout space** (các view khác co lại lấp đầy).
Các bước thực hiện
Bước 1: Tạo dự án mới
Android Studio → File → New Project → Empty View Activity.
Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Demo Button Visibility"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="24dp" />
<Button
android:id="@+id/btnVisible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1 (Luôn hiện)" />
<Button
android:id="@+id/btnToggleInvisible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Button 2 (Toggle INVISIBLE)" />
<Button
android:id="@+id/btnToggleGone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Button 3 (Toggle GONE)" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="@+id/btnShowAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:text="Hiện tất cả" />
<Button
android:id="@+id/btnHideAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ẩn tất cả (GONE)" />
</LinearLayout>
<TextView
android:id="@+id/tvStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:textSize="14sp"
android:textColor="#666"
tools:text="Status: Button 2=VISIBLE, Button 3=VISIBLE" />
</LinearLayout>
Bước 3: MainActivity.kt
package com.example.buttonvisibility
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.buttonvisibility.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnToggleInvisible.setOnClickListener { toggleInvisible() }
binding.btnToggleGone.setOnClickListener { toggleGone() }
binding.btnShowAll.setOnClickListener { showAll() }
binding.btnHideAll.setOnClickListener { hideAllGone() }
updateStatus()
}
private fun toggleInvisible() {
val btn = binding.btnToggleInvisible
btn.visibility = if (btn.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE
updateStatus()
}
private fun toggleGone() {
val btn = binding.btnToggleGone
btn.visibility = if (btn.visibility == View.VISIBLE) View.GONE else View.VISIBLE
updateStatus()
}
private fun showAll() {
binding.btnToggleInvisible.visibility = View.VISIBLE
binding.btnToggleGone.visibility = View.VISIBLE
updateStatus()
}
private fun hideAllGone() {
binding.btnToggleInvisible.visibility = View.GONE
binding.btnToggleGone.visibility = View.GONE
updateStatus()
}
private fun updateStatus() {
val s2 = when (binding.btnToggleInvisible.visibility) {
View.VISIBLE -> "VISIBLE"
View.INVISIBLE -> "INVISIBLE"
View.GONE -> "GONE"
else -> "UNKNOWN"
}
val s3 = when (binding.btnToggleGone.visibility) {
View.VISIBLE -> "VISIBLE"
View.INVISIBLE -> "INVISIBLE"
View.GONE -> "GONE"
else -> "UNKNOWN"
}
binding.tvStatus.text = "Status: Button 2=$s2, Button 3=$s3"
}
}
XML Attribute: android:visibility
<Button
android:id="@+id/btnInitiallyGone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ẩn ban đầu (GONE)"
android:visibility="gone" />
<Button
android:id="@+id/btnInitiallyInvisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ẩn ban đầu (INVISIBLE)"
android:visibility="invisible" />
Animation khi thay đổi Visibility
Mặc định visibility thay đổi tức thì. Để animate:
Cách 1: ViewPropertyAnimator (Đơn giản)
// Fade out + GONE
button.animate()
.alpha(0f)
.setDuration(300)
.withEndAction { button.visibility = View.GONE }
.start()
// Fade in từ GONE
button.visibility = View.VISIBLE
button.alpha = 0f
button.animate()
.alpha(1f)
.setDuration(300)
.start()
Cách 2: AnimatedVisibility (Jetpack Compose)
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(visible) {
Button(onClick = { visible = false }) { Text("Click to hide") }
}
Cách 3: Transition API (XML Layout)
// res/transition/visibility_transition.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="https://schemas.android.com/apk/res/android"
android:transitionOrdering="together">
<fade android:fadingMode="fade_out" />
<changeBounds />
</transitionSet>
// Layout container
<LinearLayout
android:id="@+id/container"
android:animateLayoutChanges="true"
... >
So sánh INVISIBLE vs GONE
| Trường hợp | Dùng INVISIBLE | Dùng GONE |
|---|---|---|
| Muốn giữ khoảng trống | ✅ Có | ❌ Không |
| Muốn view khác lấp đầy | ❌ Không | ✅ Có |
| Animation slide/collapse | Khó | Dễ (với animateLayoutChanges) |
| Accessibility (TalkBack) | Vẫn announce (nếu focusable) | Bỏ qua hoàn toàn |
| Touch event | Không nhận touch | Không nhận touch |
| Layout pass | Vẫn measure/layout | Bỏ qua measure/layout |
Best Practices
- Dùng GONE cho điều kiện ẩn/hiện thường xuyên (loading, error state, conditional UI).
- Dùng INVISIBLE khi cần giữ layout stable (placeholder, overlay, spacing cố định).
- Kết hợp với
animateLayoutChanges="true"trên parent ViewGroup để auto animate khi GONE. - Kiểm tra visibility trước khi thao tác:
if (view.visibility == View.VISIBLE). - Trong RecyclerView: Dùng
payloadhoặc đổi visibility trongonBindViewHolder- tránh force layout. - ConstraintLayout: GONE view được treat như 0 size, constraints tới nó có thể bị ignore → dùng
app:layout_goneMargin.
Lưu ý quan trọng
View.GONEvàView.INVISIBLEđều **không nhận touch event**.- Visibility change trigger
requestLayout()- tránh thay đổi liên tục trong loop/scroll. - Fragment/ViewPager: setVisibility trên root view của fragment không recommended - dùng
FragmentTransaction.hide()/show(). - Data Binding:
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}"(cần import View).