12 มิถุนายน 2557

[Android Design] เฉลยโจทย์ Multiple Screen Support ในงาน Android Workshop [ข้อที่ 2]


        บทความนี้ขอเป็นบทความเฉลยโจทย์ที่เจ้าของบล็อกตั้งไว้ในงาน Android Workshop ที่ สสส ในวันที่ 31 พฤษภาคมที่ผ่านมานะครับ ซึ่งบทความนี้ก็เป็นบทความเฉลยโจทย์ข้อที่ 2 ที่จะยากกว่าข้อแรกที่เฉลยไปแล้วในบทความ [Android Design] เฉลยโจทย์ Multiple Screen Support ในงาน Android Workshop [ข้อที่ 1] แต่เชื่อว่าน่าจะตรงกับความต้องการของผู้ที่หลงเข้ามาอ่านที่อยากจะออกแบบ Layout คล้ายๆกับโจทย์นี้


        สำหรับโจทย์ในข้อสองนี้จะเป็นการทำ Layout สองแบบด้วยกันคือ แนวตั้งสำหรับอุปกรณ์แอนดรอยด์ที่เป็นแบบ Phone และหน้าจอแนวนอนสำหรับ Tablet โดยที่การวาง Layout ของทั้งสองแบบนี้จะมีความต่างกัน แต่ทว่ามีจำนวน View และ ID ของ View เหมือนกันทั้งหมด (ต่างกันแค่ตำแหน่งของ View)

        สำหรับ Layout แบบ Phone จะเป็นแนวตั้งที่มีรูปแบบตามนี้



        ในวันงานเจ้าของบล็อกแค่ให้ลง Layout คร่าวๆอย่างเดียวไม่ได้ให้ทำละเอียดย่อยขนาดนี้ แต่เนื่องจากบทความเฉลยทั้งทีก็ขอให้มันละเอียดๆเลยละกันเนอะ ดังนั้นจาก Layout ข้างบนนี้ก็จะมีรายละเอียดย่อยดังนี้



        ข้างบนสุดก็คือ Content สำหรับแสดงเนื้อหา ส่วนกลางก็คือปุ่มจำนวน 4 ปุ่ม แต่ทว่าถ้ามองดีๆจะเห็นว่าเจ้าของบล็อกกำหนดให้ปุ่มตัวนี้มีทั้ง Image View และ Text View ดังนั้นเจ้าของบล็อกจึงจำลองให้ใช้ Layout เป็นปุ่มแทน (เวลากดคือกดที่ Layout ส่วน Image View กับ Text View เป็นแค่ภาพแสดง)

        ทำไมไม่ใช้ Image Button แล้วใส่ภาพให้มันอยู่เหนือข้อความล่ะ? ไม่ง่ายกว่าหรอ? 

        ตอนแรกเจ้าของบล็อกกะว่าจะทำแบบนั้นอยู่หรอก แต่ทีนี้เจ้าของบล็อกอยากจะทำเสนอให้เห็นว่า ในบางครั้งการใช้ Image Button อาจจะไม่ได้ตอบโจทย์กับผู้ที่หลงเข้ามาอ่านเสมอไป (อย่างเช่น ต้องการ Text View มากกว่า 2 อันในปุ่มนั้นๆเป็นต้น) ดังนั้นจึงสามารถเอา Layout มาประยุกต์ใช้งานแบบนี้ก็ได้เช่นกัน


        ส่วนข้างล่างก็ Button ธรรมดาๆจำนวน 4 ตัวเรียงต่อกันนั่นเอง


        และ Layout สำหรับ Tablet จะมีหน้าตาดังนี้



        สังเกตดีๆจะเห็นว่า Layout คล้ายๆกันแค่ย้ายตำแหน่งเท่านั้นเอง



        สำหรับหลักการสร้าง Layout แยกกันก็คงจะรู้กันแล้วมั้ง?  ถ้ายังไม่รู้เรื่องเลยให้ไปอ่านบทความ [Android Design] การทำงานของโฟลเดอร์ใน Resource [res] ซะก่อน


        โดยเจ้าของบล็อกก็จะทำโฟลเดอร์ Layout แยกกันดังนี้


        ทั้งสองโฟลเดอร์จะมีชื่อไฟล์ Layout เหมือนกัน แต่ทว่า layout-port จะมีไว้สำหรับแสดงผลแนวตั้ง และ layout-sw540dp-land จะมีไว้แสดงผลสำหรับ Tablet ในแนวนอน

        ถ้าแบบนี้เวลาพลิก Tablet เป็นแนวตั้ง มันก็ไปเข้าที่โฟลเดอร์ layout-port สิ?

        ผู้ที่หลงเข้ามาอ่านคนใดคิดแบบนี้แปลว่าผู้ที่หลงเข้ามาอ่านคิดถูกต้องแล้วล่ะ XD แต่ทว่าเจ้าของบล็อกจะเขียนกำหนดโค๊ดไว้ใน Activity อีกทีหนึ่งว่า ถ้าเป็น Phone จะให้หน้าจอเป็นแนวตั้ง และถ้าเป็น Tablet จะให้หน้าจอเป็นแนวนอน ดังนั้นจึงทำให้ Tablet ไม่สามารถแสดงผลเป็นแนวตั้งได้นั่นเอง (เอากันง่ายๆแบบนี้เลย)

        สำหรับการเช็คจากโค๊ดว่าเป็น Phone หรือ Tablet ขอเกริ่นไว้คร่าวๆละกันเนอะ โดยปกติแล้วผู้ที่หลงเข้ามาอ่านสามารถรู้ได้อยู่แล้วว่าอุปกรณ์แอนดรอยด์นั้นๆเป็น Phone หรือ Tablet โดยดูจากค่า SW หรือ Shortest Width นั่นเอง ถ้ามากกว่า 540 dp ขึ้นไปก็จะเป็น Tablet และถ้าน้อยกว่านั้นก็จะเป็น Phone แต่โค๊ดอ่านค่า DP มันยืดยาวเกินไปหน่อย เจ้าของบล็อกเลยลองใช้วิธีเก็บ String ไว้ใน values แทน


        โดยที่แต่ละไฟล์จะกำหนดชื่อเดียวกันแต่ว่ามีค่า String ต่างกันดังนี้

res/values/device_type.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="device">phone</string> </resources>

res/values-sw540dp/device_type.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="device">tablet</string> </resources>

        เพิ่มเติม - ไม่จำเป็นต้องสร้างไฟล์ device_type.xml ก็ได้ ใส่ค่าไว้ใน strings.xml เลยก็ได้เช่นกัน ชื่อไฟล์หรือการเก็บแยกไฟล์นั้นไม่มีผล เพราะอิงจาก name ที่กำหนดไว้ในนั้นเท่านั้น แต่เจ้าของบล็อกทำแยกออกมาเพื่อให้ผู้ที่หลงเข้ามาอ่านมือใหม่ดูได้ง่ายขึ้น (พยายามเลี่ยงอันที่ไม่เกี่ยวข้องออก)

        เรียกได้ว่าวิธีนี้เป็นการประยุกต์เอาเรื่อง Resource มาใช้เล็กน้อยเพื่อจำแนกประเภทของอุปกรณ์แอนดรอยด์ โดยที่โค๊ดก็จะกลายเป็นแค่โค๊ดดึง String จาก Resource นั่นเอง

String deviceType = getResources().getString(R.string.device);

        ที่เหลือก็แค่เช็คจาก String ที่ชื่อว่า deviceType นี่ก็รู้ได้แล้วว่าเป็น Phone หรือ Tablet
String deviceType = getResources().getString(R.string.device);
if(deviceType.equals("phone")) { ... } else if(deviceType.equals("tablet")) { ... }

        อ๊ะๆๆ ว่าจะเกริ่นแค่คร่าวๆ แต่เอาเข้าจริงบอกเกือบจะหมดซะแล้ว ฮ่าๆ เอาเป็นว่ากลับมาที่เรื่อง Layout กันดีกว่า เริ่มจาก Layout สำหรับ Phone ก่อน


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


        สรุปรวบยอดตรงนี้ละกันเนอะ Dynamic คือส่วนที่ยืดหยุ่นได้ ขยายขนาดตามขนาดหน้าจอได้ ซึ่งหลักๆจะเป็นที่ Content นั่นเอง และ Fixed คือส่วนที่เอาไว้กำหนดขนาดตายตัว อย่างเช่นต้องการให้ปุ่มมีขนาดสมส่วนหรือสัดส่วนที่เหมาะสมเป็นต้น

        ส่วน Parent Layout ที่ใช้จะเป็น Linear Layout แบบ Vertical แทน Relative Layout เพราะในโจทย์ข้อแรกเจ้าของบล็อกใช้ตัวอย่างเป็น Relative Layout แต่ในตอนนั้นเจ้าของบล็อกบอกว่า Linear Layout ก็สามารถทำแบบนั้นได้เช่นกัน ดังนั้นก็เลยเอามาเป็นตัวอย่างในโจทย์ข้อสองซะเลย



        เพิ่มเติม - ที่กำหนดความสูงเป็น XX dp เพราะขึ้นอยู่กับว่าจะกำหนดเท่าไรก็ได้ตามใจชอบ ให้สัดส่วนเหมาะสมก็เพียงพอ

        สำหรับ Layout แรกที่จะพูดถึงก็คือในส่วนที่เป็น Content ที่เป็น Dynamic ตรงจุดนี้จะต้องเผื่อไว้หน่อยว่าเวลาที่ Content ถูกบีบพื้นที่ลง ดังนั้นควรมีการใช้ Scroll View เผื่อไว้ด้วยก็ดี ส่วนอื่นๆคงไม่ต้องอธิบายแล้วเนอะ เพราะในโจทย์ข้อที่ 1 ก็อธิบายไปแล้ว



        ทีนี้มาดูส่วนที่ยากสุดสำหรับ Layout ตัวนี้กันดีกว่า นั่นก็คือ Layout ที่เอามาทำเป็น Button หลอกๆที่อยู่ตรงกลางนั่นเอง ซึ่งตรงนี้จะ Fixed ความสูงไว้ตายตัว แล้วยัด Linear Layout เข้าไป 4 ตัวโดยกำหนด Weight ทุกตัวให้เป็น 1 ผลที่ได้ก็คือ Layout 4 ตัวที่มีความกว้างเท่าๆกัน และมีความสูงเท่ากับความสูงของ Parent Layout



        จากนั้นก็ให้ดูแยกย่อยลงลึกเข้าไปอีก ใน Linear Layout ที่เอามาทำเป็น Button จะประกอบไปด้วย Image View และ Text View ดังนั้นก็ลากทั้งสองเข้ามาใส่ซะ จัด Margin จัด Padding ให้เรียบร้อยสวยงามซะ


        อีกสามอันที่เหลือคงไม่ต้องให้บอกอะไรแล้วมั้งเนอะ.. เพราะงั้นข้ามมาดู Layout สุดท้ายเลย ซึ่งในนี้ติ๊ต่างไว้ว่าจะให้ใส่ Button 4 อันไว้ในนี้ ต่างกับก่อนหน้าคือไม่มีภาพเท่านั้นเอง


        ตรงจุดนี้เจ้าของบล็อกอยากจะให้ดูอะไรเล่นๆเสียหน่อย จะเห็นว่าจะมีช่องไฟระหว่าง Button แต่ละตัวอยู่ ถ้าคิดแบบง่ายๆ ใช้ Margin ก็น่าจะจบแล้ว แต่ทว่ามีสิ่งหนึ่งที่อยากจะให้ได้รู้กัน สมมติว่าติ๊ต่างให้ว่าหน้าจอมีความกว้างเพียง 320dp เท่านั้น ถ้าอยากใส่ Button ลงไป 4 ตัวก็จะได้ Button ที่กว้าง 80dp


        ถ้าอยากจะเว้นช่องไฟระหว่างกันและกันก็มักจะใช้ Margin กัน สมมติว่าใส่ Margin เป็น 8dp ละกัน โดยใส่ Margin ไปทางขวามือ โดยที่อันสุดท้ายไม่ต้องใส่ Margin เพราะต้องการให้ชิดริม


        จะเห็นว่า Button อันสุดท้ายใหญ่กว่าชาวบ้านนั่นเอง ทั้งนี้เพราะใช้ความกว้างเป็น match_parent แล้วกำหนด weight เป็น 1 ทุกอัน จึงทำให้ไม่สามารถขยายขนาดต่อไปได้แล้ว จึงลดขอบเขตของตัวเองลงเพื่อให้เกิดช่องไฟตามที่กำหนดไว้นั่นเอง

        แล้วทีนี้จะแก้ปัญหานี้ยังไงล่ะ? 

        สำหรับปัญหานี้แก้ด้วยวิธีที่ฮาสุดๆเลยนะ ก็ลาก Linear Layout ไปคั่นไว้ระหว่าง Button แล้วกำหนด 8dp ให้เป็นความกว้างของ Layout ดังกล่าวแทนก็ได้แล้ว


        เท่านี้ Button ทั้ง 4 ตัวก็จะมีความกว้างเท่ากันแล้ว แถมได้ช่องไฟขนาด 8dp อีกด้วย เพียงแต่ว่าวิธีนี้จะทำให้เปลือง View มากเกินไปจน Layout ทำงานเกินจำเป็น เจ้าของบล็อกจึงไม่แนะนำให้ใช้กับ Layout ที่มีจำนวน View เยอะมากๆ (แต่มันก็ง่ายดี) และแถบตรงกลางที่เป็น Button แบบมี Image View กับ Text View ถ้าสังเกตดีๆก็จะเห็นว่าเจ้าของบล็อกก็ใช้วิธีเอา Linear Layout คั่นไว้เช่นกัน
     

        พิมมาซะยืดยาว ทีนี้ก็มาดูกันว่า Layout สำหรับ Phone นั้นจะมีโค๊ด XML ยังไง (โค๊ดก็ยืดยาวเช่นกัน)

res/layout-port/layout_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/color_little_gray" android:orientation="vertical" > <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginLeft="@dimen/global_margin" android:layout_marginRight="@dimen/global_margin" android:layout_marginTop="@dimen/global_margin" android:layout_weight="1" android:background="@color/color_pink" > <LinearLayout android:id="@+id/layoutContent" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > </LinearLayout> </ScrollView> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/sample_2_layout_1_height" android:layout_margin="@dimen/global_margin" android:background="@color/color_light_gray" android:orientation="horizontal" android:padding="@dimen/global_padding" > <LinearLayout android:id="@+id/buttonSomething1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@color/color_pink" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/global_margin" android:background="@color/color_pink" android:gravity="center" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> <LinearLayout android:layout_width="@dimen/global_margin" android:layout_height="match_parent" android:orientation="vertical" /> <LinearLayout android:id="@+id/buttonSomething2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@color/color_pink" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/global_margin" android:background="@color/color_pink" android:gravity="center" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> <LinearLayout android:layout_width="@dimen/global_margin" android:layout_height="match_parent" android:orientation="vertical" /> <LinearLayout android:id="@+id/buttonSomething3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@color/color_pink" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/global_margin" android:background="@color/color_pink" android:gravity="center" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> <LinearLayout android:layout_width="@dimen/global_margin" android:layout_height="match_parent" android:orientation="vertical" /> <LinearLayout android:id="@+id/buttonSomething4" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@color/color_pink" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/global_margin" android:background="@color/color_pink" android:gravity="center" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/sample_2_layout_2_height" > <Button android:id="@+id/buttonMenu1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/selector_button" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_text_size" /> <LinearLayout android:layout_width="@dimen/global_half_margin" android:layout_height="match_parent" /> <Button android:id="@+id/buttonMenu2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/selector_button" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_text_size" /> <LinearLayout android:layout_width="@dimen/global_half_margin" android:layout_height="match_parent" /> <Button android:id="@+id/buttonMenu3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/selector_button" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_text_size" /> <LinearLayout android:layout_width="@dimen/global_half_margin" android:layout_height="match_parent" /> <Button android:id="@+id/buttonMenu4" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/selector_button" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_text_size" /> </LinearLayout> </LinearLayout>


        จบไปแล้วกับ Layout สำหรับ Phone ทีนี้ก็มาดู Layout ของ Tablet กันต่อ (ยังไม่จบอีกหรอเนี่ย T^T บทความทำเยอะจริง "OTL) งั้นย้อนความรูป Layout กันใหม่อีกรอบ เผื่อมีผู้ที่หลงเข้ามาอ่านกำลังมึนงงกับ Layout ตัวแรกจนลืมอีกตัวไป


        สำหรับ  Layout แบบนี้จะเห็นว่าปลีกย่อยก็เหมือนๆเดิมที่อธิบายไปใน Layout แรก แต่ทว่าถ้าถามว่าจะวาง Layout แบบหยาบๆล่ะ? ควรวางยังไง

        ก่อนอื่นให้มองเป็น 2 Layout แบบหยาบๆดังนี้ก่อน



        ต่อมาก็คือดูว่าตรงไหน Fixed และตรงไหน Dynamic สำหรับกรณีนี้จะต่างจากแบบ Phone ตรงที่ว่ามันเป็นแนวนอน ดังนั้นความสูงจึงใช้ Match Parent แล้วความกว้างกำหนดดังนี้



        แต่ Layout ตัวนี้ต้องมองให้ซับซ้อนมากขึ้นอีกเสียหน่อย เพราะว่า Layout ขวามือที่เป็น Dynamic ดูเหมือนจะลงตัวแล้ว แต่ทว่า Layout ทางซ้ายมือที่เป็น Fixed ยังจะต้องพิจารณาให้ละเอียดมากขึ้นไปอีก ดังนั้นเมื่อจัดแบบบหยาบๆเสร็จแล้ว ให้ดูที่ Layout ซ้ายมือก็จะเห็นว่าสามารถจัดแบบหยาบๆได้อีกชั้นเช่นกัน



        ทีนี้ก็ถามต่ออีกครั้งว่าอันไหนควรเป็น Fixed และอันไหนควรเป็น Dynamic ก็จะเห็นว่า Button แถวข้างล่างนั้นควรจะเป็น Fixed ส่วนข้างบนสามารถทำเป็น Dynamic ได้ เพื่อให้มันเต็มความสูง เพราะถ้า Fixed ทั้งคู่ มันจะมีช่องว่างในเวลาที่ไปแสดงบนหน้าจอที่มีความละเอียดต่างกันได้ ดังนั้นก็เลยทำให้อันนึงเป็น Fixed และอีกอันหนึ่งเป็น Dynamic



        สำหรับ Layout แถวล่างเจ้าของบล็อกไม่อธิบายซ้ำนะ เพราะเหมือนกับของ Layout สำหรับ Phone เป๊ะๆ (ที่บ้าจี้เอา Linear Layout คั่นกลางระหว่าง Button นั่นแหละ) แต่จะเน้น Layout แถวบนที่ทำเป็น Dynamic แทน เพราะตรงจุดนี้จะเกิดคำถามที่ว่า ทำยังไงให้มัน Dynamic ได้ตลอดเวลาโดยที่ขยายขนาดเต็มกรอบทั้งความสูงและความกว้าง (อันนี้แหละที่น่าจะยากสำหรับผู้ที่หลงเข้ามาอ่านหลายๆคน)

        อ่ะ ซูมภาพไปที่ตรงจุดนี้เสียหน่อย


        อย่างแรกเลยคือ.....มองแบบหยาบๆคร่าวๆเหมือนเดิม (ใช้มุขนี้ประจำ) ซึ่ง ณ จุดนี้ผู้ที่หลงเข้ามาอ่านสามารถแบ่งแบบคร่าวๆได้สองแบบคือใช้ Linear Layout เรียงกันในแนวตั้งหรือแนวนอนก็ได้


        ถ้างั้นก็คงเดาไม่ยากเนอะว่า Parent Layout ที่จะใช้คือ Linear Layout ไม่แนวนอนก็แนวตั้ง ขึ้นอยู่กับว่าจะเลือกจัดวางแบบไหน เจ้าของบล็อกขอเลือกเป็นแบบแรกละกัน เรียง Linear Layout ซ้อนกันในแนวตั้ง ทีนี้ข้างในก็ซอยย่อยลงไปอีก


        ขอบอกไว้ก่อนเลยนะว่า ในกรอบเหล่านี้จะใช้เป็น match_parent ทั้งหมด เพื่อให้ขยายเต็มขอบเขต แล้วใช้ weight ช่วยในการแบ่งขนาดของ Layout ให้เท่าๆกัน




        ทีนี้ให้มองรายละเอียดว่า Layout ในสุดของแต่ละตัวนั้นมีรายละเอียดอะไร ถ้ายังไม่ลืม ในนั้นจะเป็น Image View กับ Text View นั่นเอง


        อ๊ะๆ ตากล้องครับ ขอซูมไปที่อันเดียวพอครับ เห็นทั้งหมดแล้วมันชวนให้ลายตา


        วางยังไงให้เต็ม Layout เสมอ?

        ถึงจุดนี้ไม่น่าจะยากแล้วละมั้งเนอะ ก็จะให้ Image View ขยายเต็มกรอบ ส่วน Text View มีขนาดพอดีตัว



        จะเห็นว่าในการจัด Layout จริงๆจะมีการใช้ Margin และ Padding ช่วยให้มีช่องไฟทำให้ Layout ดูสวยงามมากขึ้น แต่จะเห็นว่า Text View มี Margin แล้วทำไมต้องมี Padding ด้วย? ที่มี Padding เอาไว้เวลาที่มีพื้นหลังเท่านั้นแหละ เพราะถ้าไม่มีพื้นหลังจะไม่เห็นความแตกต่างซักเท่าไรหรอก


        จะเห็นว่าถ้า Padding เป็น 0dp เลยจะทำให้พื้นหลังชิดกับขอบตัวหนังสือจนเกินไป (ความกว้างกำหนดไว้เป็น match_parent จึงไม่ชิดกับตัวหนังสือ) ดังนั้นการใส่ Padding เข้าไปจะทำให้พื้นหลังไม่ชิดตัวอักษรจนน่าเกลียด (แต่ก็อย่างที่บอก ถ้าไม่มีพื้นหลังก็ไม่จำเป็น เพราะมันมองไม่เห็น)


        เอ้า!! อธิบายครบหมดแล้วนี่นา (ดีใจมากจนน้ำตาจะไหล) ถ้างั้นก็ขอสรุปเป็น XML ให้ดูกันเลยดีกว่าาาา

res/layout-sw540dp-land.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/color_little_gray" > <LinearLayout android:id="@+id/linearLayout1" android:layout_width="@dimen/sample_2_side_layout_width" android:layout_height="match_parent" android:layout_margin="@dimen/global_margin" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="@dimen/global_margin" android:layout_weight="1" android:background="@color/color_light_gray" android:gravity="center" android:orientation="vertical" android:padding="@dimen/global_padding" > <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="@dimen/global_margin" android:layout_weight="1" > <LinearLayout android:id="@+id/buttonSomething1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginRight="@dimen/global_margin" android:layout_weight="1" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="@dimen/global_margin" android:layout_weight="1" android:background="@color/color_pink" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/color_pink" android:gravity="center" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> <LinearLayout android:id="@+id/buttonSomething2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="@dimen/global_margin" android:layout_weight="1" android:background="@color/color_pink" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/color_pink" android:gravity="center" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginTop="@dimen/global_margin" android:layout_weight="1" > <LinearLayout android:id="@+id/buttonSomething3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginRight="@dimen/global_margin" android:layout_weight="1" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="@dimen/global_margin" android:layout_weight="1" android:background="@color/color_pink" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/color_pink" android:gravity="center" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> <LinearLayout android:id="@+id/buttonSomething4" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="@dimen/global_margin" android:layout_weight="1" android:background="@color/color_pink" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/color_pink" android:gravity="center" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/sample_2_layout_2_height" android:background="@color/color_light_gray" android:orientation="horizontal" android:padding="@dimen/global_padding" > <Button android:id="@+id/buttonMenu1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/selector_button" android:padding="@dimen/global_padding" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_small_text_size" /> <LinearLayout android:layout_width="@dimen/global_margin" android:layout_height="match_parent" android:orientation="vertical" /> <Button android:id="@+id/buttonMenu2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/selector_button" android:padding="@dimen/global_padding" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_small_text_size" /> <LinearLayout android:layout_width="@dimen/global_margin" android:layout_height="match_parent" android:orientation="vertical" /> <Button android:id="@+id/buttonMenu3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/selector_button" android:padding="@dimen/global_padding" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_small_text_size" /> <LinearLayout android:layout_width="@dimen/global_margin" android:layout_height="match_parent" android:orientation="vertical" /> <Button android:id="@+id/buttonMenu4" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/selector_button" android:padding="@dimen/global_padding" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_small_text_size" /> </LinearLayout> </LinearLayout> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="@dimen/global_margin" android:layout_marginRight="@dimen/global_margin" android:layout_marginTop="@dimen/global_margin" android:background="@color/color_pink" > <LinearLayout android:id="@+id/layoutContent" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > </LinearLayout> </ScrollView> </LinearLayout>



        เพิ่มเติม - เวลาที่ใช้ weight ไม่ต้องกำหนด match_parent ให้กำหนดเป็น 0dip แต่ในตัวอย่างนี้ใส่ไว้เพื่อให้ผู้ที่หลงเข้ามาอ่านไม่งงเฉยๆ (ซึ่งไม่ใช่วิธีที่ถูกต้อง)


        ให้สังเกตดีๆว่า Button ต่างๆ (รวมไปถึง Layout ที่ทำเป็นปุ่มหลอกๆด้วย) เจ้าของบล็อกจะกำหนดชื่อ ID ของ View แต่ละอันเหมือนกันทั้ง Layout แบบ Phone และ Tablet เพื่อที่ว่าเวลาเรียกใช้งาน View เหล่านี้จะได้ใช้ชื่อร่วมกันได้เลย (ภาพเล็กจัด ขยายดูภาพแทนละกันเนอะ)



        ทีนี้เข้าสู่ในเรื่องของโค๊ดกันต่อเลย จากที่เจ้าของบล็อกเกริ่นไว้ (ซะเยอะ) ในตอนแรกว่าเจ้าของบล็อกใช้วิธีดึง String จาก Resource มาดูว่าเป็น Phone หรือ Tablet จากนั้นก็ทำการกำหนด Orientation ของหน้าจออุปกรณ์แอนดรอยด์ให้เป็นตามที่ต้องการซะ

public class Main extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String deviceType = getResources().getString(R.string.device); if(deviceType.equals("phone")) { setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); } else if(deviceType.equals("tablet")) { setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); } setContentView(R.layout.layout_main); } }

        จะเห็นว่าเจ้าของบล็อกตรวจสอบก่อนเลยว่าเป็น Phone หรือ Tablet จากนั้นก็ใช้คำสั่ง setRequestOrientation เพื่อกำหนดทิศทางของหน้าจอตามที่กำหนดไว้ จากนั้นจึงค่อยใช้คำสั่ง setContentView เพื่อกำหนด Layout ที่จะแสดงทีหลัง (ควรกำหนดทิศทางหน้าจอก่อนที่จะแสดงให้เห็น)

        เพิ่มเติม - เจ้าของบล็อกไม่ได้ใช้แค่ SCREEN_ORIENTATION_PORTRAIT หรือ SCREEN_ORIENTATION_LANDSCAPE ธรรมดาๆ เพราะอย่าลืมว่าผู้ที่หลงเข้ามาอ่านควรจะนึกถึงผู้ใช้งานที่ต้องการหมุนหน้าจอ เช่น Tablet ซึ่งรู้ๆกันอยู่แล้วว่าสามารถหมุนกลับด้านได้ ดังนั้นการแสดงผลในแนวนอนจึงไม่ใช่แค่แนวนอนด้านเดียว จะต้องเผื่อสำหรับผู้ใช้พลิกไปเป็นแนวนอนอีกด้านได้ด้วย จึงเป็นที่มาว่าทำไมเจ้าของบล็อกถึงใช้ SCREEN_ORIENTATION_SENSOR_LANDSCAPE แทน สำหรับ SCREEN_ORIENTATION_SENSOR_PORTRAIT นั้นไม่จำเป็นซักเท่าไรเพราะปกติ Phone ไม่ได้ออกแบบมาให้หมุนกลับหัวในแนว Portrait ได้อยู่แล้ว แต่ก็ใส่เผื่อๆไว้แบบนั้นแหละ

        ถ้าไม่รู้เรื่อง Screen Orientation ก็สามารถศึกษาได้ที่ [Android Dev Tips] เจาะลึกกับการล็อคหน้าจอแอปพลิเคชัน [Screen Orientation]


        เมื่อกำหนดทิศทางของหน้าจอและกำหนด Layout ที่แสดงผลเรียบร้อยแล้ว ก็กำหนด Object ต่างๆเพื่อใช้งานตามปกติได้เลย~♪


Main.java
package app.akexorcist.androidworkshop_layout2; import android.app.Activity; import android.content.pm.ActivityInfo; import android.os.Bundle; public class Main extends Activity { Button buttonMenu1, buttonMenu2, buttonMenu3, buttonMenu4; LinearLayout buttonSomething1, buttonSomething2 , buttonSomething3, buttonSomething4, layoutContent; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String deviceType = getResources().getString(R.string.device); if(deviceType.equals("phone")) { setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); } else if(deviceType.equals("tablet")) { setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); } setContentView(R.layout.layout_main); buttonMenu1 = (Button)findViewById(R.id.buttonMenu1); buttonMenu2 = (Button)findViewById(R.id.buttonMenu2); buttonMenu3 = (Button)findViewById(R.id.buttonMenu3); buttonMenu4 = (Button)findViewById(R.id.buttonMenu4); buttonSomething1 = (LinearLayout)findViewById(R.id.buttonSomething1); buttonSomething2 = (LinearLayout)findViewById(R.id.buttonSomething2); buttonSomething3 = (LinearLayout)findViewById(R.id.buttonSomething3); buttonSomething4 = (LinearLayout)findViewById(R.id.buttonSomething4); layoutContent = (LinearLayout)findViewById(R.id.layoutContent); } }

        สำหรับ Android Manifest ก็ไม่มีอะไรให้ต้องกำหนด ใช้ค่าที่กำหนดไว้ตอนเริ่มต้นเลยก็ได้ และอีกส่วนหนึ่งที่สำคัญก็คือ Dimension Resource ที่เอาไว้เก็บค่าต่างๆสำหรับกำหนดขนาด View ต่างๆใน Layout ซึ่งเจ้าของบล็อกได้แบ่งออกเป็นสามขนาดด้วยกันสำหรับ Phone, Tablet 7" และ Tablet 10"


res/values/dimens.xml
<resources> <dimen name="global_padding">8dp</dimen> <dimen name="global_margin">8dp</dimen> <dimen name="global_half_margin">4dp</dimen> <dimen name="global_text_size">16sp</dimen> <dimen name="global_small_text_size">10sp</dimen> <dimen name="global_round_corner">10dp</dimen> <!-- Phone Layout --> <dimen name="sample_2_layout_1_height">120dp</dimen> <dimen name="sample_2_layout_2_height">48dp</dimen> </resources>


res/values-sw540dp/dimens.xml
<resources> <dimen name="global_padding">10dp</dimen> <dimen name="global_margin">10dp</dimen> <dimen name="global_half_margin">4dp</dimen> <dimen name="global_text_size">22sp</dimen> <dimen name="global_small_text_size">18sp</dimen> <dimen name="global_round_corner">10dp</dimen> <!-- Phone Layout --> <dimen name="sample_2_layout_1_height">200dp</dimen> <dimen name="sample_2_layout_2_height">80dp</dimen> <dimen name="sample_2_side_layout_width">350dp</dimen> </resources>


res/values-sw720dp/dimens.xml
<resources> <dimen name="global_padding">10dp</dimen> <dimen name="global_margin">10dp</dimen> <dimen name="global_half_margin">4dp</dimen> <dimen name="global_text_size">25sp</dimen> <dimen name="global_small_text_size">20sp</dimen> <dimen name="global_round_corner">10dp</dimen> <!-- Phone Layout --> <dimen name="sample_2_layout_1_height">250dp</dimen> <dimen name="sample_2_layout_2_height">12dp</dimen> <dimen name="sample_2_side_layout_width">420dp</dimen> </resources>


        ส่วนนี้อาจจะไม่ค่อยสำคัญมากนักสำหรับผู้ที่หลงเข้ามาอ่านบางคน แต่สำหรับเจ้าของบล็อกถือว่าสำคัญไม่น้อยเลย นั่นก็คือ Color Resource ที่เอาไว้เก็บค่าสีตามต้องการนั่นเอง ข้อดีก็คือเวลาอยากเปลี่ยนสีก็เปลี่ยนในนี้ไม่ต้องไปนั่งไล่แก้นั่นเอง ฮ่าๆ

res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="color_pink">#e95374</color> <color name="color_dark_pink">#c42c54</color> <color name="color_light_gray">#d1d2d4</color> <color name="color_little_gray">#e6e7e8</color> <color name="color_dark_transparent">#37000000</color> <color name="color_text_normal">#ffffff</color> <color name="color_text_pressed">#070707</color> </resources>


        และนอกจากนี้ยังมี Shape Drawable ที่ทำไว้สำหรับทำ Selector ให้กับ Button แต่ไม่แปะโค๊ดนะ ถ้าอยากรู้ว่ามันคืออะไรให้เข้าไปอ่านศึกษาได้ที่ [Android Design] เปลี่ยนภาพ Button ได้ดั่งใจด้วย Selector [Custom Button]



        เมื่อทุกอย่างพร้อมแล้วก็ลองทดสอบบนอุปกรณ์แอนดรอยด์กันได้เลย~! โดยอย่าลิมลองหมุนหน้าจอด้วยว่ามันรองรับดีมั้ย (สำหรับมือถือหมุนไปก็เท่านั้น) และถ้าทดสอบบน Emulator อย่าง AVD หรือ Genymotion โปรดอย่าลืมว่ามันกลับหัวหน้าจอไม่ได้นะ



        วิธีแบบนี้จะเหมาะกับ Layout หลายแบบแต่มี ID ของ View ที่เหมือนกันทั้งหมด แต่ทว่าถ้าผู้ที่หลงเข้ามาอ่านต้องการทำ Layout สองแบบที่ไม่เหมือนกันเลย วิธีนี้ก็สามารถทำได้เหมือนกัน แต่ทว่าโค๊ดจะแบ่งเป็นสองชุดใหญ่ๆซึ่งดูไม่เหมาะสม แนะนำให้ใช้ Fragment แทนจะดีกว่า เพราะสามารถแยกโค๊ดสำหรับแต่ละ Layout ในแต่ละไฟล์ได้

        สำหรับผู้ที่หลงเข้ามาอ่านที่ต้องการไฟล์ตัวอย่างการจัดวาง Layout สุดหรรษาตัวนี้สามารถดาวน์โหลดได้ที่

                Android Workshop - Layout 2 [Google Drive]

                Android Workshop - Layout 2 [GitHub]

                Android Workshop - Layout 2 [SleepingForLess]

        โจทย์นี้อาจจะดูยากไปสำหรับผู้ที่หลงเข้ามาอ่านหลายๆคน แต่ทว่าการวาง Layout ในรูปแบบคล้ายๆกันนี้จะมีเยอะมาก สิ่งที่เจ้าของบล็อกอยากจะทำเสนอให้ก็เป็นแค่แนวทางในการออกแบบ Layout แต่สิ่งที่สำคัญกว่าก็คือการฝึกฝนของผู้ที่หลงเข้ามาอ่านนั่นแหละ ที่จะทำให้สามารถออกแบบ Layout ได้หลากหลายและครอบคลุมกับอุปกรณ์ต่างๆได้มากขึ้น ดังนั้นของแบบนี้ถ้าไม่ฝึกก็ไม่ชำนาญหรอกนะ XD

        ถ้าอยากจะฝึกและทำเวลาดู เจ้าของบล็อกให้เวลาจำกัดไว้ที่ 3 ชั่วโมงก็แล้วกัน โดยจะต้องออกแบบเสร็จทั้งแบบ Phone และ Tablet ภายใน 3 ชั่วโมงนะ ไม่ได้รางวัลหรืออะไรจากเจ้าของบล็อกหรอก แต่จะได้ประสบการณ์เข้าตัวเต็มๆ!!


        และขอทิ้งท้ายบทความเหมือนเคยซะหน่อย สำหรับวิธีที่นำเสนอในบทความนี้ไม่ได้เป็นวิธีที่ดีที่สุด เพียงแต่เป็นวิธีที่เจ้าของบล็อกสามารถนำเสนอให้ผู้ที่หลงเข้ามาอ่านเข้าใจได้ง่าย ดังนั้นถ้ามีวิธีนอกเหนือจากนี้ที่ดีอยู่แล้วก็ใช้ต่อไปนั่นแหละ แต่ถ้ายังไม่มีวิธีอื่นก็ให้ศึกษาจากวิธีดังกล่าวนี้แล้วนำไปประยุกต์ใช้ให้เหมาะสมกับความต้องการ



บทความที่เกี่ยวข้อง

        • เฉลยโจทย์ Multiple Screen Support ในงาน Android Workshop [ข้อที่ 1]




เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium