Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีใช้ ViewStub ใน Android เพื่อเพิ่มประสิทธิภาพ

ภาพรวม

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)

  1. inflate() คืนค่า View Root ของ Layout ที่โหลดมา → เก็บไว้เพื่อควบคุมภายหลัง
  2. inflate() เรียกได้แค่ครั้งเดียว ครั้งที่ 2 จะคืนค่า View เดิม (ไม่ Inflate ใหม่)
  3. หลัง Inflate: ViewStub หายไป ถูกแทนที่ด้วย Inflated View ใน Hierarchy
  4. ควบคุม Visibility ผ่าน inflatedView.setVisibility(VISIBLE/GONE)
  5. findViewById ภายใน Inflated Layout ต้องใช้ inflatedView.findViewById()

กรณีใช้งานเหมาะสม (Use Cases)

สถานการณ์คำอธิบาย
Error/Empty StateLayout แสดงเมื่อโหลดข้อมูลล้มเหลว/ว่างเปล่า
Complex Dialog/Bottom SheetUI ซับซ้อนที่ไม่ได้แสดงบ่อย
Ad Bannerโหลดเฉพาะเมื่อพร้อมแสดง
Advanced Settingsส่วนตั้งค่าขั้นสูงที่ผู้ใช้เข้าไม่บ่อย
Conditional UIUI ที่ขึ้นกับ 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