Code

Code

วันอาทิตย์ที่ 25 ธันวาคม พ.ศ. 2559

ANDROID : Ripple Effect



Ripple Effect


   สวัสดีครับ ทุกท่านน่าจะรู้จัก Selector กันดี ที่เห็นชัดๆก็จะเป็น Selector ของ Button แบบรูปข้างล่างนี้

Selector (ดูดรูปมาจากบทความพี่เอกซะเลย ขี้เกียจทำใหม่ฮ่าๆ)
http://www.akexorcist.com/2013/09/android-design-button-selector-custom.html

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

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

    ในแง่ของ UX แล้วนั้น ไม่ดีเอามากๆเพราะ User ไม่รู้ว่าปุ่มได้ถูกกดแล้วนะ ซึ่ง Selector ที่เรากำลังพูดถึงเนี่ยมันเป็น Effect ที่ค่อนข้างจะเก่าแล้วเริ่มจะล้าสมัยแล้ว 


     ทาง Google เองก็ได้ออก Guideline ของตัวเองขึ้นมา ชื่อว่า Material Design ซึ่ง Effect ตอนกดได้เปลี่ยนจาก Selector ไปเป็น Ripple แทน ซึ่งไม่ได้มีผลในแง่ของ Design เท่านั้น

     แต่ Google ยังได้เปลี่ยนไปใช้ Effect นี้ใน Android 5.0 ขึ้นไปหรือ v21 เลยทีเดียวเชียว ซึ่งถ้าในแอพทุกท่านมีการใช้ Button ที่เป็นรูปแล้วใส่ Selector การแสดงผลบน Android 5.0 ขึ้นไป Effect ตอนกดจะตีกันมั่วไปหมด ทำให้ User เกิดการสับสนได้ เพราะไม่ใช่แค่ Button เท่านั้นที่มี Ripple Effect แต่รวมไปถึง View ต่างๆที่มี action ตอนกดทั้งหมดไม่ว่าจะเป็น Menu , Fab , Checkbox , และอื่นๆที่สามารถกดได้ล้วนแล้วแต่ถูกกำหนดให้ใช้ Ripple Effect เป็นค่า Defalut ซึ่งถ้าท่านยังใช้ Selector บน Android 5.0 ขึ้นไปอยู่ ถึงว่าเป็นฤกษ์งามยามดีที่ท่านจะได้เปลี่ยนมาใช้ Ripple ตามหลัก Material Design ของ Google แล้วละ 


      การแสดงผลของ Ripple Effect นั้นจะคล้ายๆเวลาเราโยนหินลงน้ำที่จะทำให้น้ำกระเพื่อมเป็นวงกลมขยายออกไป Google นำหลักการนี้มาใช้เมื่อเราแตะที่ View บนหน้าจอก็จะมีการกระเพื่อมภายใน View นั้นให้เรารู้ว่าเราแตะโดนแล้วนะ 

   ซึ่งการกระเพื่อมนี้ละเอียดถึงขนาดรู้ว่าเราแตะที่มุมไหนของ View มันก็จะเริ่มกระเพื่อมจากจุดที่เราแตะต่างกับ selector ที่แตะตรงไหนก็ตามปุ่มก็จะยุบลงไปหมดเลย 

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

    สี Default ของ Ripple Effect นั้น มีสองแบบ คือ Ripple โทนสว่างและ Ripple โทนมืด ซึ่งการที่ Ripple จะใช้โทนไหนนั้น ขึ้นอยู่กับ Theme ที่เรากำหนดให้กับแอพของเรานั้นเอง 

ซึ่งหากเรากำหนด Theme แอพของเราด้วยคำสั่งนี้
Theme.AppCompat

จะทำให้ Ripple Effect เป็นโทนสว่าง พื้นหลังปุ่มเป็นสีมืดและตัวหนังสือเป็นสีขาว

แต่ถ้าเรากำหนด Theme แอพของเราด้วยคำสั่งนี้
Theme.AppCompat.Light
จะทำให้ Ripple Effect เป็นโทนมืด พื้นหลังปุ่มเป็นสีสว่างและตัวหนังสือเป็นสีดำ

ซึ่งหากเราจะทำการเปลี่ยนสีปุ่มนั้นจะใช้คำสั่ง
android:backgroundTint="@android:color/holo_blue_light"
แต่คำสั่ง "backgroundTint" นั้นรองรับบน API level 21 ขึ้นไปเท่านั้น
หากไปรันบนเครื่องที่ต่ำกว่า API level 21 ปุ่มก็จะไม่เปลี่ยนสี เราเลยยังไม่สามารถใช้คำสั่งนี้ได้ หาก MinSdkVersion ของเราต่ำกว่า 21
เราเลยต้องทำ Background ให้รองรับเครื่องที่ API level 21 ขึ้นไป โดยจะใช้ Ripple และเครื่องที่ต่ำกว่า API level 21 จะใช้ Selector

 ทฤษฎีกันเยอะละ มาลงมือปฏิบัติกันดีกว่า..

เราจะเริ่มสร้าง button พื้นหลังสีฟ้ากันตามรูปแบบนี้

1. เริ่มจากสร้างปุ่มธรรมดาๆขึ้นมา
<Button
         android:id="@+id/button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="Button" />
ได้ Button แบบนี้ 

ถ้าใครที่ไม่เป็น button แบบนี้นึกออกไหมว่าลืมอะไร ลืมตั้งให้แอพใช้ Theme.AppCompat นั้นเอง ส่วนวิธีปรับให้แอพใช้ theme นี้ก็ไปที่ androidmanifest.xml แล้ว set theme นี้ให้กับ application หรือจะปรับผ่าน style ก็ตามสะดวก

2. สร้าง Ripple Effect กัน เราจะ create drawable resource file ไว้ใน drawable โดยเลือก version 21 ตั้งชื่อว่า button_blue สาเหตุที่เราต้องเลือก Platform API level : 21 เป็นเพราะ ripple รองรับ API level : 21 ขึ้นไปเท่านั้นเราไม่สามารถเอาไปไว้ใน folder drawable ธรรมดาได้


จากนั้นใส่โค้ดตามข้างล่างนี้

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
         android:color="?android:colorButtonNormal">
         <item>
              <shape android:shape="rectangle">
                  <corners android:radius="6dp"/>
                  <solid android:color="#64B5F6" />
              </shape>
         </item>
</ripple>

อธิบาย
- เราจะใช้ tag <ripple> ในการใช้ Ripple Effect

- สีของ Ripple นั้นเราจะใช้สีจาก colotButtonNormal ซึ่งสีนี้จะขึ้นอยู่กับ Theme ที่เราใช้นั้นเองว่าจะได้ Ripple เป็นโทนสว่างหรือโทนมืด สามารถเปลี่ยนเป็นสีอื่นได้ แต่แนะนำว่าหากเป็นปุ่มควรจะมี Effect ที่สอดคล้องกันทั้งแอพ ไม่งั้นมันจะดูแปลกๆ

- tag <item> : คือ tag ที่เรากำหนดว่าจะให้ button เรามีรูปร่างหน้าตาเป็นแบบไหนนั้นเอง โดยคำสั่งที่เราใช้คือ shape แบบ rectangle (สี่เหลี่ยม) แต่ปุ่มเราไม่ได้เป็นสีเหลี่ยมนิมันมีมุมโค้งอยู่ เราเลยเพิ่ม tag <corner> เพื่อกำหนดมุมโค้งของกับปุ่ม ส่วน tag <solid> คือสีพื้นหลังของปุ่ม เป็นอันเสร็จ

เรามากำหนด background ให้ปุ่มกันเลย
<Button
         android:id="@+id/button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="@drawable/button_blue"
         android:text="Button" />

เหมือนรูปที่เราต้องการเป๊ะ ลองรันดูเลยย
(API Level : 21 ขึ้นไปนะ เพราะเรายังไม่ได้ทำ selector สำหรับรุ่นที่ต่ำกว่า)
แจ่มแมวไปเลย

ที่นี้ทุกคนลองเอาไปรันบนรุ่นที่ต่ำกว่า API Level : 21 สิ       Error !!! เลย  เป็นเพราะว่า Ripple ที่เราใช้นั้นมันไม่รองรับ มันเป็นของที่มีบน API Level : 21 ฉะนั้นเราเลยต้องทำ selector สำหรับรุ่นที่ต่ำกว่า 21
มาลุย !! 

1.  Create drawable resource file ไว้ใน drawable ชื่อต้องเหมือนกันกับตัว Ripple ที่เราได้สร้างไปก่อนหน้านี้นะครับ นั้นคือ button_blue 

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
         <item android:state_pressed="true">
               <shape android:shape="rectangle">
                  <corners android:radius="6dp"/>
                  <solid android:color="#226aa4"/>
               </shape>
         </item>
         <item>
               <shape android:shape="rectangle">
                  <corners android:radius="6dp"/>
                  <solid android:color="#39a3f9"/>
               </shape>
         </item>
</selector>
อธิบาย
- เราจะใช้ tag <selector> ในการทำปุ่มขึ้นมา ซึ่งประกอบไปด้วย item 2 ตัว

  1. <item> (tag ล่าง) : ปุ่มสถานะปกติ

  2. <item> (tag บน) : ปุ่มสถานะถูกกด (state_pressed = true)

- ใน <item> ก็ประกอบไปด้วย tag<shape> ซึ่งเป็น tag ที่กำหนดรูปร่างของปุ่ม ในโค้ดนี้เราจะทำรูปร่างของปุ่มเหมือนกันทั้งสอง item ยกเว้นสีของ state_pressed ที่เราจะทำให้สีเข้มกว่าสถานะปกติเพื่อให้รู้ว่าถูกกด

จากนั้นรันเลยรันบนเครื่องที่ต่ำกว่า API Level : 21 นะ ไม่ต้องทำอะไรกับ button เพราะระบบจะทำการแยกให้เองว่าเครื่องที่รันอยู่จะใช้ไฟล์ button_blue จาก folder ไหน เราเรียกว่าระบบนี้ว่า Configuration Qualifier นั้นเอง



   เห็นผลลัพธ์กันหรือยัง ที่นี้ปุ่มของเราก็จะลองรับได้กับทุกเครื่องแล้ว จริงๆแล้วตัว Ripple นี้มี library ให้ใช้กับ View เยอะแยะเลย แต่ผมไม่เห็นความจำเป็นที่เราจะต้องใช้นะ เพราะเราสร้างเองก็ไม่ได้ยาก แถม library บางตัวก็ไม่ค่อยจะสมบูรณ์ เวลากดค้างแล้ว Ripple มันไม่ค่อยเนียนเหมือนของระบบ ซึ่งถ้าใครคิดว่าจะใช้ Ripple Library ที่นักพัฒนาท่านอื่นทำมา นำมาแสดงผลกับเครื่องที่ต่ำกว่าAPI Level : 21 ผมคิดว่าไม่เหมาะ เพราะแอพเราจะเป็นแอพเดียวที่มี Ripple ในหมู่แอพอื่นๆของเครื่อง เพราะงั้นแยก Ripple กับ selector ไว้เป็นการดีที่สุดสำหรับในแง่ของ UX


   โอเครหลังจากเราเข้าใจพื้นฐานง่ายๆแล้ว เรามาทำอะไรสนุกๆกับเจ้า Ripple กันดีกว่า

     เราจะมาทำ Ripple แบบในภาพนี้กัน Ripple แบบในภาพนี้ส่วนใหญ่จะใช้กับ item ที่มีขนาดเล็กซึ่งหากให้ Ripple เคลื่อนไหวอยู่ภายในจะทำให้แสดงผลไม่ชัดเจน User อาจจะไม่รู้สึกว่ากดโดน ให้ทายว่าที่เห็นชัดๆเลยเราใช้กับอะไร ติ๊งต๊องๆๆๆๆ เมนูไง เมนูจะมีขนาดไอคอนที่เล็กเลยต้องทำให้ Effect ของ Ripple แสดงผลภายนอกไอคอนนั้นเอง หากเป็นเมนูที่อยู่บน toolbar ระบบจะทำการใส่ Ripple ให้เอง แต่ถ้าเราจะแปะไอคอนรูปหัวใจขนาดเล็กไว้บนเนื้อหา เราก็ต้องทำ  Ripple แบบในภาพขึ้นมาเอง ซึ่งเราจะทำต้องรองรับรุ่นที่ต่ำกว่า API Level : 21 ด้วย นั้นคือต้องทำทั้ง Ripple และ Selector ซึ่งค่อนข้างจะซับซ้อนจากที่เราทำไปก่อนหน้านี้นิดหนึ่ง

    เริ่มจากสร้าง ImageView รูปหัวใจก่อน
<ImageView
         android:id="@+id/imageView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:layout_margin="16dp"
         android:clickable="true"
         android:padding="8dp"
         android:src="@drawable/ic_heart" />
ได้รูปหัวใจแบบนี้
ที่นี้เราจะมาทำ Background โดยใช้ Ripple Effect กันก่อน 

- create drawable ใน folder v21 ตั้งชื่อว่า bg_heart
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
         android:color="?android:colorButtonNormal">
</ripple>

- ใช้ <tag> ripple
- สีของ ripple เราก็กำหนดให้ใช้สีจาก colorButtonNormal 

จากนั้นก็นำ bg_heart ไปกำหนดเป็น background ให้กับรูปหัวใจ

ทดลองรันบนเครื่องที่ API Level : 21 ขึ้นไป 
     ขั้นตอนต่อไปเราจะทำให้มันลองรับกับเครื่องที่ต่ำกว่า API Level : 21 โดยใช้ selector กับ background ของรูปหัวใจ ตามรูปนี้
ขอบคุณรูปภาพประกอบจากพี่เอก

- create drawable ใน folder drawable ตั้งชื่อว่า bg_heart
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
         <item android:state_pressed="true">
              <shape android:shape="oval">
                  <solid android:color="@android:color/darker_gray" />
              </shape>
         </item>
</selector>
อธิบาย
<tag> item  กำหนดให้ state_pressed = true คือสถานะของ item นี้จะแสดงผลตอนถูกกด
<tag> shape = oval คือการทำให้ลักษณะเป็นวงกลม
<tag> solid  ก็กำหนดสีให้เป็นสีเทา

แล้วเราก็นำมากำหนดให้กับไอคอนตามนี้
<ImageView
         android:id="@+id/imageView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:layout_margin="16dp"
         android:clickable="true"
         android:padding="8dp"
         android:background="@drawable/bg_heart"
         android:src="@drawable/ic_heart" />

ทดลองรันบน เครื่องที่ต่ำกว่า API Level : 21



มาถึงตรงนี้เราก็จะได้ Ripple สำหรับไอคอนที่รองรับทั้ง Ripple บนเครื่อง API Level : 21 ขึ้นไป และ Selector บนเครื่องที่ต่ำกว่า API Level : 21 

ก็ลองนำไปใช้กับแอพพลิเคชั่นของท่านทั้งหลายดูนะครับ จะได้รองรับ Effect ทั้งแบบใหม่และแบบเก่า

Goodluck-Havefun (glhf)

Reference

ไม่มีความคิดเห็น :

แสดงความคิดเห็น