07 มิถุนายน 2557

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


        สำหรับบทความนี้ทำขึ้นมาเพื่อเฉลยโจทย์คำถามที่เจ้าของบล็อกได้ตั้งไว้ในงาน Android Workshop ที่ สสส ในวันที่ 31 พฤษภาคมที่ผ่านมานะครับ เนื่องจากมีผู้ที่หลงเข้ามาอ่านหลายคนไม่ถนัดเรื่องการจัดเลย์เอาท์ เจ้าของบล็อกจึงทำเป็นบทความเฉลยขึ้นมาเพื่อให้ผู้ที่หลงเข้ามาอ่านทุกๆคนสามารถนำไปดู นำไปเรียนรู้จากตัวอย่างนี้ได้


        โดยโจทย์ที่ให้ในวันนี้จะมีสองโจทย์ด้วยกัน ซึ่งจะขอแบ่งเป็นสองบทความทำเป็นบทความละข้อละกันเนอะ (เฉลยข้อสองจะได้มีเวลาอู้หน่อย)

        สำหรับโจทย์แรกเป็น Layout ธรรมดาๆที่เป็นแนวตั้ง แต่จะให้รองรับกับหน้าจอทุกขนาด ยังไม่มีการแบ่งว่าเป็น Phone หรือ Tablet เพื่อให้ทดสอบฝีมือง่ายๆกันก่อน


        กรอบข้างบนให้นึกถึง ภาพที่เป็นพื้นหลังแล้วมีข้อความอยู่บนภาพดังนี้


        ส่วนกรอบตรงกลางจะเป็นพื้นที่สำหรับ Content ต่างๆ และข้างล่างสุดจะเป็น Button สองตัวด้วยกัน ซึ่งจะเห็นว่าเจ้าของบล็อกให้ใส่ขอบมนที่มุมด้วย ซึ่งหมายความว่าต้องใช้ Shape Drawable เข้ามาช่วยนั่นเอง (แต่ในวันนั้นอนุโลมให้ลาก Button ธรรมดาๆไปใส่แทน)

        สำหรับวิธีมองนั้นควรคิดอย่างไร?มีลำดับขั้นตอนยังไง?


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

        เมื่อแบ่งเป็นสามส่วนแล้ว ก็จะเห็นว่า Parent ก็จะต้องเป็น Linear Layout แนวตั้งหรือ Relative Layout เพราะทั้งสองสามารถจัดรูปแบบดังภาพได้เหมือนกันทั้งคู่

        ไหนๆก็พูดถึงละ ทำให้ดูเลยดีกว่า โดยเริ่มจากตัวอย่างแรกสุดคือใช้ Relative Layout ที่ผู้ที่หลงเข้ามาอ่านหลายๆคนน่าจะรู้กันดีว่าจัดยังไง
<RelativeLayout 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/layout1" android:layout_width="match_parent" android:layout_height="@dimen/sample_1_layout_1_height" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_margin="@dimen/global_margin" android:background="@color/color_pink" > </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/layout2" android:layout_below="@+id/layout1" android:layout_centerHorizontal="true" android:layout_margin="@dimen/global_margin" android:background="@color/color_pink" android:orientation="vertical" > </LinearLayout> <LinearLayout android:id="@+id/layout2" android:layout_width="match_parent" android:layout_height="@dimen/sample_1_layout_2_height" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_margin="@dimen/global_margin" android:background="@color/color_pink" android:orientation="horizontal" > </LinearLayout> </RelativeLayout>
        ซึ่งจะเป็นการกำหนด ID ให้กับ Layout บนสุดและล่างสุดที่จะ Fixed ความสูง แล้วสร้าง Layout ที่ตรงกลางโดยให้ Below กับ Layout ตัวบน และ Above กับ Layout ตัวล่าง


        โดยส่วนตัวแล้ว ถ้าไม่จำเป็นก็ไม่ค่อยอยากจะใช้ Relative Layout ซักเท่าไร เพราะว่าต้องประกาศ ID อยู่บ่อยๆ (ไม่งั้นจะใช้พวก Above หรือ Below ไม่ได้) ทีนี้มาดูกันบ้างว่า Linear Layout ทำยังไงถึงจะได้เป็นแบบนี้
<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:orientation="vertical" android:background="@color/color_little_gray" > <LinearLayout android:id="@+id/layout1" android:layout_width="match_parent" android:layout_height="@dimen/sample_1_layout_1_height" android:orientation="vertical" android:layout_margin="@dimen/global_margin" android:background="@color/color_pink" > </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:layout_marginLeft="@dimen/global_margin" android:layout_marginRight="@dimen/global_margin" android:background="@color/color_pink" android:orientation="vertical" > </LinearLayout> <LinearLayout android:id="@+id/layout2" android:layout_width="match_parent" android:layout_height="@dimen/sample_1_layout_2_height" android:layout_margin="@dimen/global_margin" android:background="@color/color_pink" android:orientation="horizontal" > </LinearLayout> </LinearLayout>
        จะเห็นว่าเจ้าของบล็อกให้ Layout ที่อยู่ตรงกลางมีความสูงเป็น match_parent แล้วมี layout_weight เป็น 1 จึงทำให้กลายเป็น Layout ที่มีขนาดเปลี่ยนไปตามขนาดหน้าจอได้


        นอกเรื่องหลักไปละ กลับเข้ามาต่อจากของเดิมกันดีกว่า

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


        สาเหตุที่ให้ Layout แรกเป็น Fixed ก็เพราะว่าจะใช้แสดงแค่ภาพกับชื่อภาพเท่านั้น ซึ่งตัวภาพมีขนาดตายตัว (เพราะจะใช้วิธี Crop ภาพให้พอดีกับกรอบ) ส่วนตรงกลางนิยมใช้แสดง Content ซึ่ง Content ส่วนใหญ่มักจะมีขนาดไม่ตายตัวอยู่แล้ว และสุดท้ายข้างล่างสุดเป็นแค่ปุ่มกดธรรมดาๆ ดังนั้นจึงใช้เป็น Fixed

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


        เมื่อกำหนดแบบหยาบๆได้แล้ว ก็ให้มาพิจารณากันทีละส่วนๆ โดยเริ่มจากส่วนแรกสุด


        ถ้าดูเผินๆแล้วก็เดาได้ไม่ยากมั้ง เนื่องจากจะให้พื้นหลังเป็นรูปภาพ โดยมีข้อความทับอยู่ข้างบนชิดขอบล่าง ดังนั้น Layout ที่ใช้ก็คงไม่พ้น Relative Layout เป็นแน่แท้

        แล้วพื้นหลังสีดำจางๆของ Text ล่ะ? ใช้อะไร? 

        ถ้าผู้ที่หลงเข้ามาอ่านที่ตอบว่าใช้ Linear Layout ไง แล้วเอา Text View ใส่ในนั้น จากนั้นก็ขยาย Linear Layout ให้เต็มความกว้าง ส่วนความสูงอิงจากขนาด Text View แล้วก็เทสีดำจางๆไงล่ะ!!!


        เกือบถูกล่ะครับ แต่ยังไม่ถูกที่สุด....

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


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

  

        นึกภาพว่าผู้ที่หลงเข้ามาอ่านจัด Content ไว้ประมาณหนึ่ง ซึ่งแสดงได้พอดีบนหน้าจอปกติและใหญ่กว่า แต่ทว่าเมื่อมันเปิดบนหน้าจอเล็กๆล่ะ? Content ท่อนล่างก็จะถูกตัดหายไปนั่นเอง ดังนั้นจึงควรใส่ Scroll View เผื่อไว้ให้สามารถเลื่อนขึ้นลงได้ซะ


       ต่อมาคือบรรทัดล่างสุดที่เอาไว้แสดงปุ่ม สำหรับกดเพื่อทำอะไรบางอย่าง จะเห็นว่าทั้งคู่มีขนาดไม่เท่ากัน แต่นั่นก็ไม่ใช่ปัญหา เพราะมันก็สามารถใช้ Weight ได้เหมือนเดิม โดยใช้อัตราส่วน 2 : 3 แทน



        เพิ่มเติม - ถ้าไม่เคยรู่จักกับ Weight มาก่อน ให้ไปอ่านศึกษาเพิ่มเติมได้ที่ [Android Design] Layout Weight ใช้อย่างไร ใช้ให้เป็น


        เมื่อจัดเลยเอาท์คร่าวๆกันแล้ว ก็มาดูกันต่อว่าจะทำยังไงให้เหมาะกับ Phone และ Tablet กันต่อ เพราะว่า Layout ที่เจ้าของบล็อกจัดจะอยู่ในไฟล์เดียวเท่านั้น เพราะกำหนดไว้ว่า Layout เดียวกันไม่ว่าจะเป็นบน Phone หรือ Tablet ดังนั้นจึงจัด Layout ในไฟล์เดียวซะ แล้วกำหนดค่าต่างๆสำหรับ Layout ไว้ในโฟลเดอร์ Values แทน



        ทีนี้มาดูกันก่อนว่าใน Layout มีตรงไหนที่ Fixed ไว้บ้าง

        สำหรับ Margin หรือ Padding พยายามให้ใช้ค่าเหมือนกันทั้งหมด เพื่อไม่ให้เกิด Dimen Resource เยอะมากเกินไป (Guideline ของ Android Developer กล่าวไว้ว่า 8dp นี่แหละกำลังเหมาะ!!)



        เจ้าของบล็อกได้สร้างโฟลเดอร์ขึ้นมาสามแบบคือ Phone, Tablet 7 และ Tablet 10 โดยทั้งสามโฟลเดอร์นี้มีไฟล์ dimens.xml ที่เก็บค่าที่เอาไว้กำหนดใน Layout แยกไปตามขนาดหน้าจอนั่นเอง

values/dimens.xml
<resources> <dimen name="global_padding">8dp</dimen> <dimen name="global_margin">8dp</dimen> <dimen name="global_text_size">18sp</dimen> <dimen name="global_round_corner">10dp</dimen> <!-- Sample 1 Layout --> <dimen name="sample_1_layout_1_height">120dp</dimen> <dimen name="sample_1_layout_2_height">80dp</dimen> </resources>

values-sw540dp/dimens.xml
<resources> <dimen name="global_padding">10dp</dimen> <dimen name="global_margin">10dp</dimen> <dimen name="global_text_size">25sp</dimen> <dimen name="global_round_corner">10dp</dimen> <!-- Sample 1 Layout --> <dimen name="sample_1_layout_1_height">250dp</dimen> <dimen name="sample_1_layout_2_height">150dp</dimen> </resources>

values-sw720dp/dimens.xml
<resources> <dimen name="global_padding">10dp</dimen> <dimen name="global_margin">10dp</dimen> <dimen name="global_text_size">25sp</dimen> <dimen name="global_round_corner">10dp</dimen> <!-- Sample 1 Layout --> <dimen name="sample_1_layout_1_height">350dp</dimen> <dimen name="sample_1_layout_2_height">180dp</dimen> </resources>

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

        และนอกจากนี้เจ้าของบล็อกยังกำหนดค่าสีเก็บไว้ใน colors.xml ไว้อีกด้วย จะได้เรียกใช้สีได้ง่ายๆ และในยามที่ต้องแก้ไขสีก็มาแก้ไขได้ง่ายๆที่ไฟล์นี้เช่นกัน

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>

        ทีนี้มาดู Layout ที่เจ้าของบล็อกพูดพล่ามมาทั้งหมดกันดีกว่าว่าเจ้าของบล็อกจัดยังไง

layout_main.xml
<RelativeLayout 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" > <RelativeLayout android:id="@+id/layout1" android:layout_width="match_parent" android:layout_height="@dimen/sample_1_layout_1_height" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_margin="@dimen/global_margin" android:background="@color/color_pink" > <ImageView android:id="@+id/imageView1" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/textView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:background="@color/color_dark_transparent" android:padding="@dimen/global_padding" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_text_size" /> </RelativeLayout> <ScrollView android:id="@+id/scrollView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/layout2" android:layout_below="@+id/layout1" android:layout_centerHorizontal="true" android:layout_marginLeft="@dimen/global_margin" android:layout_marginRight="@dimen/global_margin" android:background="@color/color_pink" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/global_margin" android:text="TextView" android:textColor="@color/color_text_normal" android:textSize="@dimen/global_text_size" /> </LinearLayout> </ScrollView> <LinearLayout android:id="@+id/layout2" android:layout_width="match_parent" android:layout_height="@dimen/sample_1_layout_2_height" android:orientation="horizontal" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" > <Button android:id="@+id/button1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_margin="@dimen/global_margin" android:layout_weight="2" android:background="@drawable/selector_button_round" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_text_size" /> <Button android:id="@+id/button2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_margin="@dimen/global_margin" android:layout_weight="3" android:background="@drawable/selector_button_round" android:text="Button" android:textColor="@drawable/selector_button_text" android:textSize="@dimen/global_text_size" /> </LinearLayout> </RelativeLayout>


        หมายเหตุ - อย่าลืมนะจ๊ะว่าออกแบบแค่แนวตั้ง ถ้าเอาไปใช้จริงก็กำหนดให้แสดงผลเป็นแนวตั้งใน AndroidManifest.xml ด้วยนะจ๊ะ


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

                Android Workshop - Layout 1 [Google Drive]

                Android Workshop - Layout 1 [GitHub]

                Android Workshop - Layout 1 [SleepingForLess]

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



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

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


        และสำหรับสไลด์ที่เจ้าของบล็อกใช้สอนในเรื่อง Android UX & Design Guideline สามารถดาวน์โหลดไฟล์ได้จาก

                • Android UX and DesignGuideline [PPTX] : [Google Drive] [Dropbox]

                • Android UX and DesignGuideline [PDF] : [Google Drive] [Dropbox]     





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

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