ภาพรวม
ViewStub เป็น View แบบ **Zero-sized, Invisible, Lazy-inflated** ใช้สำหรับ **Lazy Loading** Layout ที่ไม่จำเป็นต้องแสดงทันที (เช่น Error View, Loading View, Complex Layout ที่แสดงบางครั้ง) ช่วยลด Memory และเพิ่มความเร็ว Initial Inflation
คุณสมบัติหลัก
- ขนาด 0x0 dp (ไม่ใช้พื้นที่จนกว่า Inflate)
- Inflate แค่ครั้งเดียว (ครั้งถัดไปใช้ View ที่ Inflated แล้ว)
- แทนที่ตัวเองใน View Hierarchy หลัง Inflate
- ควบคุม Visibility ได้ (VISIBLE/GONE) หลัง Inflate
ขั้นตอนการใช้งาน
1. Layout หลัก (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="หน้าหลัก - เนื้อหาหลักโหลดเสร็จแล้ว"
android:textSize="18sp"
android:layout_marginBottom="24dp" />
<Button
android:id="@+id/btnShow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="แสดง ViewStub (โหลด Layout ย่อย)" />
<Button
android:id="@+id/btnHide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="ซ่อน ViewStub" />
<!-- ViewStub: android:layout = layout resource ที่จะ inflate -->
<ViewStub
android:id="@+id/viewStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/layout_child"
android:inflatedId="@+id/inflatedContent" />
</LinearLayout>
Attributes สำคัญ:
android:layout- Layout resource ที่จะ inflate (บังคับ)android:inflatedId- ID ใหม่ให้ View Root หลัง Inflate (Optional)
2. Layout ย่อย (res/layout/layout_child.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/childRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="#E3F2FD"
android:layout_marginTop="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="✅ Layout นี้โหลดแบบ Lazy (ViewStub)"
android:textSize="16sp"
android:textColor="#1565C0"
android:layout_marginBottom="8dp" />
<Button
android:id="@+id/btnInsideStub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ปุ่มภายใน ViewStub" />
</LinearLayout>
3. MainActivity.java
package com.example.myapplication;
import android.os.Bundle;
import android.view.View;
import android.view.ViewStub;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private ViewStub viewStub;
private View inflatedView; // เก็บ Reference หลัง Inflate
private Button btnInsideStub;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewStub = findViewById(R.id.viewStub);
Button btnShow = findViewById(R.id.btnShow);
Button btnHide = findViewById(R.id.btnHide);
btnShow.setOnClickListener(v -> {
if (inflatedView == null) {
// Inflate ครั้งแรก
inflatedView = viewStub.inflate(); // คืนค่า Root View ของ Layout ที่ Inflate
btnInsideStub = inflatedView.findViewById(R.id.btnInsideStub);
btnInsideStub.setOnClickListener(v2 ->
Toast.makeText(this, "กดปุ่มใน ViewStub แล้ว!", Toast.LENGTH_SHORT).show()
);
} else {
// ครั้งต่อไป: แค่แสดง
inflatedView.setVisibility(View.VISIBLE);
}
});
btnHide.setOnClickListener(v -> {
if (inflatedView != null) {
inflatedView.setVisibility(View.GONE);
}
});
}
}
จุดสำคัญ (Key Points)
- inflate() คืนค่า View Root ของ Layout ที่โหลดมา → เก็บไว้เพื่อควบคุมภายหลัง
- inflate() เรียกได้แค่ครั้งเดียว ครั้งที่ 2 จะคืนค่า View เดิม (ไม่ Inflate ใหม่)
- หลัง Inflate: ViewStub หายไป ถูกแทนที่ด้วย Inflated View ใน Hierarchy
- ควบคุม Visibility ผ่าน
inflatedView.setVisibility(VISIBLE/GONE) - findViewById ภายใน Inflated Layout ต้องใช้
inflatedView.findViewById()
กรณีใช้งานเหมาะสม (Use Cases)
| สถานการณ์ | คำอธิบาย |
|---|---|
| Error/Empty State | Layout แสดงเมื่อโหลดข้อมูลล้มเหลว/ว่างเปล่า |
| Complex Dialog/Bottom Sheet | UI ซับซ้อนที่ไม่ได้แสดงบ่อย |
| Ad Banner | โหลดเฉพาะเมื่อพร้อมแสดง |
| Advanced Settings | ส่วนตั้งค่าขั้นสูงที่ผู้ใช้เข้าไม่บ่อย |
| Conditional UI | UI ที่ขึ้นกับ Role/Permission/State |
ข้อจำกัด (Limitations)
- ไม่รองรับ
<merge>tag ใน Layout ที่ inflate - ไม่สามารถใช้
ViewStubซ้อนใน Layout ที่ Inflate โดย ViewStub อื่นได้ (Nested ViewStub) - Inflate แค่ครั้งเดียว → หากต้องการ Reset ให้ Inflate ใหม่ ไม่ได้ (ต้องสร้าง ViewStub ใหม่ด้วยโค้ด)
- ไม่สามารถเข้าถึง View ภายในได้ก่อน Inflate
ทางเลือกอื่น (Alternatives)
- View.GONE + include: ใช้
<include layout="@layout/..." android:visibility="gone" />แล้ว setVisibility(VISIBLE) - เรียบง่ายกว่า แต่ Inflate ตั้งแต่ต้น - Fragment + FragmentContainerView: สำหรับ UI ซับซ้อน มี Lifecycle แยก
- RecyclerView + ViewType: สำหรับรายการที่มี Item Type หลากหลาย
สรุป
ViewStub = **Lazy Inflation** สำหรับ Layout ที่ไม่ต้องแสดงทันที → ประหยัด Memory, เร่ง Initial Load ใช้ viewStub.inflate() ครั้งแรก → เก็บ View ที่คืนมา → ควบคุม Visibility ต่อไป เหมาะสำหรับ Error State, Complex Optional UI, Conditional Views