ViewStub คืออะไร ?
ViewStub คือ View ชนิดหนึ่ง มีการใช้งาน Memory น้อยมากกกกก (Lightweight View) เนื่องจากไม่มีการเขียนสิ่งไดๆลงบนจอเลย(Zero-sized View) ด้วยเหตุนี้ ViewStub จึงไม่ปรากฏบนจอ(Invisible)โดยที่ไม้เด็ดของ ViewStub คือ สามารถ Inflate View อื่นๆ เข้ามาแทนที่ตัวมันเอง ในขณะ Runtime ได้ เพียงแค่เรียกใช้คำสั่ง viewStub.inflate() หรือ viewStub.setVisibility(int) ซึ่งเป็นที่มาของหัวข้อเรื่องที่ว่า "เมื่อไรเขามา ฉันจะไป" นั่นเอง
ใช้งานเมื่อไร ?
ViewStub เหมาะสำหรับการใช้งานกับ View ที่ไม่ได้แสดงผลทุกครั้งในหน้าเดิมๆ หรือ View ที่นานๆทีจะแสดงผลสักครั้งนึง เช่น แถบคาดว่า User ถูกแบน, Progress bar แสดง % ในการ Download ข้อมูล เป็นต้นเนื่องจากว่า ViewStub ไม่มีการแสดงผลลงบนจอ และมี View Hierarchy เพียงชั้นเดียว จึงทำให้ใช้งาน Memory น้อยกว่ามาก เมื่อเทียบกับการที่ใส่ View ลงไปตรงๆใน XML แล้วสั่ง visibility="gone"(ลักไก่สุดๆ) เพราะถึงแม้ว่าจะซ่อนไปแล้ว View นั้นๆก็ยังจะโดน Inflate อยู่ดี ซึ่งก็หมายถึง Memory ที่ต้องเสียไปโดยเปล่าประโยชน์ นั่นเอง :(
การใช้งาน
หน้าตาของ ViewStub ในไฟล์ .xml<ViewStub android:id="@+id/stub"
android:inflatedId="@+id/layout_banned_user"
android:layout="@layout/banned_user"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
โดยที่ android:inflatedId คือ การกำหนด Id ให้กับ View ที่ Inflate มาแทน ViewStubandroid:layout คือ ไฟล์ layout .xml ที่ต้องการให้มาแทน ViewStub
และเมื่อต้องการให้ View อื่นมาแทนที่ ViewStub ก็เพียงเรียกคำสั่ง inflate() หรือ setVisibility(int) ดังตัวอย่าง
ViewStub viewStub = (ViewStub) findViewById(R.id.stub);
viewStub.setVisibility(View.VISIBLE);
// หรือ
View layoutBannedUser = viewStub.inflate();
การใช้งาน ViewStub ก็มีเพียงเท่านี้ แต่บางท่านอาจจะยังงงๆอยู่ เพื่อความเข้าใจมากขึ้นเราลองมาทำ Work Shop กันดีกว่าครับ :)
ลองทำ Work Shop ง่ายๆกันดีกว่า
A Bโจทย์คือ
- หาก User อยู่ในสถานะปกติ ก็ให้แสดง Avatar ของ User (A)
- แต่หาก User ถูกแบน ให้แสดง Overlay สีดำทับพร้อมกับแสดงข้อความ (B)
1. สร้าง Layout ของ User Avatar
ขอตั้งชื่อว่า activity_user.xml ก็จะได้ผลลัพธ์จะได้ดังรูป (A) ครับ<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageview_avatar"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_centerInParent="true"
android:src="@drawable/avatar"/>
</RelativeLayout>
2. ลองเพิ่ม Layout ของ Overlay สีดำและข้อความลงไปใน Layout เดิม
ผลลัพธ์จะได้ดังรูป (B)<RelativeLayout>
<ImageView />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#CC000000"
android:gravity="center"
android:orientation="vertical">
<TextView
style="@style/Base.TextAppearance.AppCompat.Large.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="YOU'VE BEEN BANNED"/>
<TextView
style="@style/TextAppearance.AppCompat.Medium.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Please contact administrator"/>
</LinearLayout>
</RelativeLayout>
จริงๆ แล้วทำเพียงเท่านี้ก็เพียงพอต่อความต้องการของโจทย์แล้ว แต่เดี๋ยวก่อน Layout Overlay สีดำไม่ได้แสดงทุกครั้งใช่ไหมครับ :) แล้วเราจะทำยังไงหละ ?? ....... ถูกต้องนะคร้าบบบ ViewStub ช่วยท่านได้ แล้วทำยังไงหละ ?? ลองมาดูกันดีกว่า
3. ย้าย Layout ของ Overlay สีดำและข้อความ ไปไว้อีกไฟล์นึง
ขอตั้งชื่อว่า banned_user.xml ละกันนะครับ<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#CC000000"
android:gravity="center"
android:orientation="vertical">
<TextView
style="@style/Base.TextAppearance.AppCompat.Large.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="YOU'VE BEEN BANNED"/>
<TextView
style="@style/TextAppearance.AppCompat.Medium.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Please contact administrator"/>
</LinearLayout>
*** หมายเหตุ : View ที่จะ Inflate เข้ามาแทน ViewStub ไม่รองรับการใช้งาน tag <merge /> นะครับ
4. กลับไปที่ activity_user.xml เพื่อเพิ่ม ViewStub
กำหนด View ที่จะมาแทน ViewStub ด้วย tag android:layout="@layout/banned_user"<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageview_avatar"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_centerInParent="true"
android:src="@drawable/avatar"/>
<!-- เพิ่ม ViewStub -->
<ViewStub
android:id="@+id/stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inflatedId="@+id/layout_banned_user"
android:layout="@layout/banned_user"/>
</RelativeLayout>
5. เมื่อ User ถูกแบน ให้เรียกใช้คำสั่ง ViewStuff.inflate()
public class UserActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
.
.
if(isBanned()) {
ViewStub viewStub = (ViewStub) findViewById(R.id.stub);
viewStub.inflate();
}
}
}
เพียงเท่านี้ก็จะได้ผลลัพธ์ตามที่โจทย์ต้องการ พร้อมทั้งยังได้ Application ที่ประสิทธิภาพอีกด้วยนะครับ
แอบดูเบื้องหลัง
ลองมาดู View Hierarchy ระหว่างตอนที่ยังเป็น ViewStub (C) และหลังจาก Inflate View (D) อื่นเข้ามาแทน กันหน่อย
C
D
สำหรับเรื่องของ ViewStub คงจบลงเท่านี้ สุดท้ายหากมีข้อสงสัยหรือแนะนำก็ฝากไว้ใน Comment ได้เลยนะครับ ขอบคุณครับ
ปล. ดูโค้ดทั้งหมดได้ที่ github.com
ลิงค์อ้างอิง
http://developer.android.com/reference/android/view/ViewStub.htmlhttp://developer.android.com/training/improving-layouts/loading-ondemand.html
http://android-developers.blogspot.in/2009/03/android-layout-tricks-3-optimize-with.html