Constraint Layout - Constrained Width/Height

ใน ConstraintLayout จะมี XML Attribute ให้ใช้งานค่อนข้างเยอะ เพื่อให้ตอบโจทย์กับการสร้าง UI ได้หลากหลายมากที่สุดเท่าที่ทำได้ ซึ่งมีทั้ง Attribute ที่มีให้ตั้งแต่แรกและ Attribute ที่เพิ่มเข้ามาในภายหลังเพื่อเพิ่มความสามารถของ ConstraintLayout ให้มากกว่าเดิม

และในบทความนี้ก็จะมาแนะนำให้รู้จักกับ Attribute ที่ชื่อว่า layout_constrainedWidth กับ layout_constrainedHeight ที่ถูกเพิ่มเข้ามาในเวอร์ชัน 1.1 และอาจจะไม่ค่อยคุ้นตากันซักเท่าไร

เกิดมาสำหรับ View ที่กำหนดขนาดด้วย Wrap Content

อย่างที่เรารู้กันว่าเวลากำหนดให้ View มีขนาดเป็น Wrap Content จะทำให้มีขนาดเท่ากับ Wrap Content เสมอ

แต่ถ้า Content นั้น ๆ เกิด Overflow ขึ้นมา ขนาดสูงสุดที่เป็นไปได้สำหรับ Wrap Content จะมีค่าเท่าไรกันนะ?

ซึ่งคำตอบนี้มีหลายคำตอบ โดยขึ้นอยู่กับว่า View ตัวนั้นอยู่ใน Layout ตัวไหน และคำตอบสำหรับ ConstraintLayout ก็คือ "ขนาดสูงสุดเป็นไปได้จะเท่ากับขนาดของ ConstraintLayout"

ยกตัวอย่างเช่น เจ้าของบล็อกสร้าง TextView ที่มีความกว้างเป็น Wrap Content และมีเส้น Constraint ที่เชื่อมกับ ImageView ด้านข้างทั้ง 2 ฝั่ง

จะเห็นว่าเมื่อเกิด Overflow ขึ้น จะทำให้ความกว้างของ TextView ขยายโดยไม่สนใจเส้น Constraint ทำให้ View ขยายความกว้างจน Overlap กับ ImageView

โดยปกติจะแก้ปัญหาแบบนี้ด้วยการใช้ความกว้างเป็น 0dp แทน แต่ถ้าจำเป็นต้องใช้ Wrap Content ก็ให้กำหนด layout_constainedWidth="true" เพิ่มเข้าไป ซึ่งจะได้ผลลัพธ์ที่เหมือนกัน

layout_constrainedWidth ใช้กับความกว้างของ View
layout_constrainedHeight ใช้กับแนวความสูงของ View

แล้วทำไมไม่ใช้เป็น 0dp ไปเลยล่ะ?

สำหรับ UI ที่ยกตัวอย่างในก่อนหน้านี้ ผู้ที่หลงเข้ามาอ่านส่วนใหญ่ก็คงตัดสินใจใช้ 0dp แทน เพราะดูเหมาะสมกว่า ดังนั้นมาดูกรณีที่จำเป็นต้องใช้ Wrap Content กัน

จากภาพตัวอย่างข้างบนจะมี TextView 2 ตัวที่เรียงต่อกันแบบชิดซ้ายมือ และมี ImageView อยู่ชิดขวามือตลอดเวลา โดยมีเงื่อนไขว่า ถ้าข้อความใน TextView ตัวแรกเกิด Overflow จะต้องตัดคำให้เป็นเครื่องหมายจุดแทน (Single Line) และ TextView ตัวที่ 2 จะต้องถูกดันจนชิดกับ ImageView

ซึ่งแน่นอนว่า UI ในรูปแบบนี้จะไม่สามารถกำหนดค่าด้วย 0dp ได้เลย เพราะโจทย์ต้องการให้ TextView ทั้ง 2 ตัวอยู่ชิดซ้ายตลอดเวลา จึงต้องใช้ Wrap Content แทน และใช้ layout_constrainedWidth เข้ามาช่วย โดยใช้ร่วมกับ layout_constraintHorizontal_chainStyle และ layout_constraintHorizontal_bias

<androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:padding="16dp">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:text="Mountain View"
            app:layout_constrainedWidth="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/textView2"
            app:layout_constraintHorizontal_bias="0"
            app:layout_constraintHorizontal_chainStyle="packed"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:text="Since 2004"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/space"
            app:layout_constraintStart_toEndOf="@id/textView1"
            app:layout_constraintTop_toTopOf="parent" />

        <Space
            android:id="@+id/space"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/imageView"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="60dp"
            android:layout_height="60dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
นอกจากนี้ยังต้องใช้ <space> เพื่อช่วยดันให้ TextView ทั้ง 2 ตัวชิดซ้ายด้วย

ถ้าไม่กำหนด layout_constrainWidth="true"  จะทำให้ TextView ตัวแรกขยายตามข้อความข้างในจนดัน TextView ตัวที่สองจน Overlap กับ ImageView และล้นออกขอบจอนั่นเอง

สรุป

การใช้ Wrap Content กับ View ที่อยู่ใน ConstraintLayout จะทำให้ View ตัวนั้นสามารถขยายขนาดได้จนเท่ากับขนาดของ ConstraintLayout โดยไม่สนใจว่ากำหนดเส้น Constraint แบบไหนอยู่

แต่การกำหนด layout_constrainedWidth และ layout_constrainedHeight เพิ่มเข้าไปจะช่วยให้ View ตัวนั้น ๆ ขยายขนาดโดยอ้างอิงจากการเชื่อมเส้น Constraint กับ View ตัวอื่น ๆ ด้วย ทำให้ขอบเขตในการขยายที่สามารถเกิดขึ้นได้จะเหมือนกับการใช้ 0dp

ในการใช้ Wrap Content ร่วมกับ layout_constrainedWidth และ layout_constrainedHeight จะเหมาะกับ UI บางรูปแบบเท่านั้น ดังนั้นกรณีทั่วไปก็แนะนำให้ใช้เป็น 0dp แทนเพื่อความง่ายและรวดเร็ว