วันนี้ขอพูดถึง View สุดแปลกตัวนึงที่เพิ่มเข้ามาใน ConstraintLayout 1.1 ที่มีชื่อว่า Placeholder กัน

ทำไมถึงแปลกล่ะ?

Placeholder เป็น View จะต้องวางไว้ใน ConstraintLayout เท่านั้น จึงสามารถวางไว้ที่ไหนก็ได้เหมือนกับ View ตัวอื่น ๆ โดยหน้าที่ของ Placeholder คือเมื่อกำหนด View ID ใด ๆ ก็ตามที่อยู่ใน ConstraintLayout ตัวเดียวกัน จะทำให้ View ตัวนั้นถูกย้ายมาอยู่ในพื้นที่ของ Placeholder ในทันที

โดย Placeholder จะเรียก View ID นี้ว่า Content ID

ยกตัวอย่างเช่นเจ้าของบล็อกมี UI ที่สร้างขึ้นด้วย Constraint Layout แบบนี้

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Placeholder
        android:id="@+id/placeholder"
        android:layout_width="200dp"
        android:layout_height="100dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

แล้วกำหนด Content ID ด้วย View ID ของ TextView ให้กับ Placeholder แบบนี้

val placeholder: Placeholder = /* ... */
placeholder.setContentId(R.id.textView)

สิ่งที่เกิดขึ้นคือ TextView จะถูกย้ายมาอยู่ใน Placeholder ทันที

นอกจากย้ายตำแหน่งของ View ให้มาอยู่ใน Placeholder แล้ว ยังทำให้ View ตัวนั้นมีความกว้างและความสูงเป็น 0dp หรือ match_constraint โดยอัตโนมัติ ไม่ว่าจะกำหนด Dimension ไว้เท่าใดก็ตาม

และถ้าต้องการให้ View กลับไปอยู่ที่ตำแหน่งเดิม ก็ให้เคลียร์ค่า Content ID ใน Placeholder ทิ้งซะ

val placeholder: Placeholder = /* ... */
placeholder.setContentId(0)

ถ้ามีเส้น Constraint ของ View ตัวอื่นที่ Reference อยู่กับ View ตัวนั้นล่ะ?

สมมติว่าเจ้าของบล็อกสร้าง TextView ขึ้นมา 2 ตัว โดยให้ textView2 อ้างอิงตำแหน่งจาก textView1 แบบนี้

สิ่งที่เกิดขึ้นคือ textView2 จะไม่ได้ย้ายตามไปด้วย แต่จะมองว่า textView1 ถูกกำหนด Visibility เป็น Gone แทน ทำให้ในตัวอย่างที่ textView1 ย้ายไปอยู่ใน Placeholder ทำให้ textView2 เลื่อนไปชิดมุมซ้ายบนแทน

กำหนด Visibility สำหรับตอนที่ไม่มี Content ID ได้

Placeholder เป็น View ที่จะแสดงผลให้ผู้ใช้เห็น จึงทำให้นักพัฒนากำหนด XML Attribute ต่าง ๆ ที่มีผลต่อการแสดงผลได้ (เช่น สีพื้นหลัง) จึงทำให้ Placeholder มีคำสั่ง setEmptyVisibility(...) เพื่อกำหนดได้ว่าถ้าไม่มี Content ID จะให้ Placeholder มี Visibility เป็น Visibile, Invisible หรือ Gone ก็ได้

val placeholder: Placeholder = /* ... */
placeholder.emptyVisibility = View.GONE

Placeholder เหมาะกับการใช้งานแบบไหน?

ต้องบอกว่าจากที่ลองใช้งานดู เจ้าของบล็อกก็ยังนึกไม่ออกเหมือนกันว่าจะเอาไปใช้งานแบบไหนดี ดังนั้นเท่าที่นึกออกก็น่าจะเหมาะกับ UI ใด ๆ ก็ได้ที่ต้องการย้าย View แค่บางตัวไปอยู่ในอีกตำแหน่งหนึ่งได้ เมื่อเข้าเงื่อนไขที่ต้องการ

และอ้างอิงจากวีดีโอของงาน 360 AnDev ปี 2017 ในหัวข้อ Advanced ConstraintLayout ที่พูดโดย Nicolas Roard จาก Google ก็ยังไม่แน่ใจเหมือนกันว่า Placeholder จะมีประโยชน์หรือไม่ แต่สร้างขึ้นมาเพื่อเป็นตัวเลือกสำหรับนักพัฒนาที่ต้องการย้ายตำแหน่งของ View ในลักษณะแบบนี้

Advanced ConstraintLayout
Deeper look at ConstraintLayout: architecture, performance behavior, examples of complex UI, animation capabilities.