เขียนแอพแอนดรอยด์ตั้งแต่เริ่มต้น ตอนที่ 2 : Activity
หลังจากที่เราได้ลองสร้างแอพเปล่ามาลองรันบนเครื่องดูแล้ว ตอนนี้มาทำความรู้จักกับสิ่งสำคัญสิ่งแรกๆที่ Android Developer ต้องรู้ นั่นคือ Activity และ Fragment นั่นเอง
สวัสดีค่ะทุกคน บล็อกนี้เป็น 1 ใน content series สอนการเขียนแอพแอนดรอยด์ตั้งแต่เริ่มต้น จนพอเขียนได้เนอะ เหมาะสำหรับใครที่อยากลองเขียน Android Native เนอะ โดยใน content series ที่ชื่อว่า Basic Android Development จะมีดังนี้
- Introduction with Android Project
- Activity
- Fragment
ในบล็อกนี้เป็นหัวข้อ Activity เป็นหัวข้อพื้นฐานที่ต้องรู้ เป็นหัวใจสำคัญในการทำ Android Development เลยนะ
ก่อนอื่น Activity คืออะไรกันนะ?
สมมุติว่าเราเปิดหน้าแอพ TrueMoney ก็จะเจอหน้าตาแบบนี้เนอะ
Activity เป็นหนึ่งใน component สำคัญของแอพพลิเคชันบนระบบปฏิบัติการแอนดรอยด์ และเป็นสิ่งที่เป็นตัว launch แอพของเราขึ้นมา อย่างในรูปนี้ก็คือ user เปิดแอพ TrueMoney จะเจอหน้าแรกแบบนี้ โดยแอพพลิเคชันจะประกอบด้วยอย่างน้อย 1 Activity และต้อง declare ที่ AndroidManifest ไม่เช่นนั้นจะไม่สามารถรันแอฟพลิเคชั่นได้
และจะต้องมีการ declare ทุก ๆ Activity ในแอพ ใน AndroidManifest ไม่เช่นนั้น แอพพลิเคชันของเราจะ crash
โดยตัว Activity จะมี layout ที่เป็น XML มาคู่กันด้วยเสมอ
(ยกเว้นทำแบบ Compose ที่สามารถเขียน layout ต่างๆได้จากโค้ด จะคล้ายๆ Flutter หน่อยๆ แต่บล็อกซีรีส์นี้ไม่ได้กล่าว — อันนี้จะเขียนแยกสรุปสิ่งที่เรียนจาก Compose Camp อีกบล็อกนึง)
Activity Lifecycle
คือ วงจรชีวิตของ activity ใน application ของเรา
การที่เราทำอะไรต่างๆในแอปพลิเคชั่น ก็จะอยู่ใน Activity Lifecycle เสมอ ไม่ว่าจะเป็นการเข้ามาที่แอพ เปลี่ยนหน้าแอพ การออกจากแอพ
โดยตัว Lifecycle ของ Activity จะมีหน้าตาเป็นแบบนี้
onCreate()
Activity ของเรากำลังถูกสร้างonStart()
เราเริ่มเห็นหน้า Activity ของเราแล้ว UI มาครบonResume()
มีสองแบบ คือ ได้รับ focus จาก input กับกลับเข้าหน้า Activity นี้มาใหม่onPause()
หยุดการทำงานของ Activity นั้นไว้ก่อนonStop()
หยุดการทำงานของ Activity นั้นไปเลยonDestroy()
ทำลาย Activity นั้นซะ
แต่เราชอบรูปนี้มากกว่า เพราะมันสั้น และเข้าใจได้ง่ายกว่า
เมื่อเปิดแอพขึ้นมานั่นจะเข้า onCreate()
เพื่อสร้าง activity หน้านั้นขึ้นมา แล้วเข้า onStart()
เพื่อเริ่มต้น activity แล้วเข้า onResume()
ซึ่งเราจะเห็น view ของ activity แล้วเพราะได้ render เสร็จแล้วนั่นเอง
เมื่อมีการพับหน้าจอลง เช่น มือถือเข้าสู่โหมดล็อกหน้าจอ ก็จะเข้า onPause()
แล้วเมื่อปลดล็อกหน้าจอออกมาแล้ว จะ onResume()
กลับมาใหม่
เมื่อเรากดปุ่ม home ออกจากแอพ จะเข้า onPause()
จากนั้นเข้า onStop()
เพื่อหยุดการทำงานของ activity นั้นลงไปก่อน เมื่อกลับเข้ามาในแอพ จะทำการ onRestart()
activity นั้นกลับมาใหม่ แล้วเข้าสู่ onStart()
และ onResume()
เพื่อ render หน้า activity นั้นกลับมาใหม่
และเมื่อเราออกจาก activity หน้านั้นโดยการกด back หรืออะไรก็ตามที่ทำให้ activity นั้นถูกทำลาย หรือออกจากแอพ จาก onResume()
จะเข้า onPause()
จากนั้นเข้า onStop()
เพื่อหยุดการทำงานของ activity และเข้า onDestroy()
เพื่อทำลาย activity นั้นซะ
อันนี้รวมทั้งหมดเนอะ
สำหรับการลองเล่นเกี่ยวกับ Actvity Lifecycle น้ัน เรามักจะใส่ toast ต่างๆไว้ใน lifecycle แต่อัน เพื่อทำการ check เนอะ ว่าตอนนี้เข้าอันไหนบ้าง
ตัว demo สามารถดูได้ที่คลิปนี้เลย
ส่วนโค้ดดูได้ที่นี่จ้า
แล้ว toast คืออะไรหล่ะ?
มันคือ ขนมปังปิ้งที่แสนน่ารับประทานยังไงหล่ะ
เดี๋ยวๆ toast คือ View ที่ใช้แจ้งเตือนอะไรบางอย่าง เล็กๆ อยู่ตรงด้านล่างจอ เช่น บอกว่ากำลังส่งข้อความอยู่นะ ที่ชื่อ toast ก็น่าจะเหมือนขนมปังปิ้งที่พอกรอบได้ที่แล้ว จะเด้งขึ้นมาจากเครื่องปิ้งขนมปังนั่นเอง จะว่าไปก็หิวแหะ
การใส่ toast ก็แสนจะง่าย หน้าตาจะเป็นแบบนี้ เดี๋ยวเราจะมาแนะนำแต่ละส่วนกัน
Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show()
ในส่วน makeText
จะประกอบด้วย 3 parameter หลักๆ
- application context: context ของหน้าที่ toast เราจะโชว์ ในที่นี้จะใช้
this
เนื่องจากอยู่ในActivity
นะ - text: ข้อความที่เราต้องการให้แสดง
- duration: อยากให้ toast ของเราโชว์นานเท่าไหร่ โชว์สั้นๆให้ใช้
Toast.LENGTH_SHORT
โชว์ยาวๆให้ใช้Toast.LENGTH_LONG
และปิดท้ายด้วย show()
เพื่อแสดง toast ที่เราสร้าง แสดงขึ้นมาเนอะ
ถ้าใครอยากลองเล่น เราสร้าง branch ชื่อว่า activity-lifecycle
มาให้เล่นเพิ่มหล่ะ ไปลองกันได้เลยแบบง่ายๆ เพียง build app เสร็จแล้วดูผลลัพธ์ได้เลย
มาลองทำการสร้าง Activity ใหม่ และส่งค่าไปยัง Activity ใหม่กันเถอะ
จากอันนี้ เราจะมี Activity ทั้งสิ้น 1 อันด้วยกัน
ทีนี้เราจะมาสร้าง Activity อีกอันเพิ่มกันดีกว่า
โดยในตัวอย่างนี้ เราจะทำแบบนี้ เราจะส่งค่าจาก activity 1 แล้วเอาค่าที่ได้ ไปเปิดใน activity 2 แบบง่ายๆ
ก่อนอื่นเลย จะได้หน้า Activity เป็นหน้าเปล่าๆ ที่มีคำว่า “Hello World” มาใช่ป่าว เราจะมาเพิ่ม Button เพื่อเปิดไปหน้า Activity ใหม่กัน
โดยตัว layout ที่เราใช้นั้น ชื่อว่า ConstraintLayout นั่นเอง เป็น layout ที่ช่วยลดความซับซ้อนเวลาที่เราทำ UI ใหญ่ๆ และมีความซับซ้อน อีกทั้งเจ้า ConstraintLayout ยังอยู่ใน Andriod Jetpack ด้วยนะ
ในที่นี้เราวาง TextView ไว้กลางหน้าจอ เราก็บอกว่า ให้ด้านบน ด้านล่าง ด้านซ้าย ด้านขวา ยึดจาก parent นะ ทำให้มันอยู่ตรงกลางจอนั่นเอง
เดี๋ยวเราอธิบายแต่ละส่วนคร่าวๆกัน
- parent view ของ view นี้ คือ ConstraintLayout มี child view คือ TextView
- TextView เรากำหนด id ของ view นี้ ชื่อว่า
textview
ก็แล้วกัน เดี๋ยวได้ใช้นะทุกคน - เรากำหนดความกว้างของเจ้า TextView ด้วย
layout_width
เป็นwrap_content
ก็คือให้ความกว้างของมัน เท่ากับ content ข้างในนั่นเอง - ส่วนความสูงก็เช่นกัน กำหนดด้วย
layout_height
เป็นwrap_content
ก็คือให้ความสูงของมัน เท่ากับ content ข้างในเช่นกัน - ส่วน parent ด้านนอกที่เป็น ConstraintLayout จะมีความกว้างและความสูง เป็น
match_parent
คือให้มีขนาดเต็มหน้าจอนั่นเอง layout_constraintStart_toStartOf
อันนี้บอกว่า เจ้าtextview
นี้ จุด start หรือด้านซ้ายของ view ของเรา ไปอยู่กับ start เดียวกับ view ที่เรา reference ในที่นี้เรา reference view ของเราคือ parent นั่นเอง- ส่วน
layout_constraintEnd_toEndOf
,layout_constraintTop_toTopOf
และlayout_constraintBottom_toBottomOf
ความหมายคล้ายกับlayout_constraintStart_toStartOf
แค่เปลี่ยนแนวที่เรา reference เช่น end หรือด้านขวา, ด้านบน, ด้านล่าง text
เราสามารถกำหนดว่าจะใส่คำว่าอะไรให้กับ TextView ซึ่ง attribute พวกนี้ สามารถกำหนดค่าได้ผ่าน layout XML แบบนี้ หรือผ่าน code ก็ได้ อดใจรอ เดี๋ยวมีให้ทำแน่นอน
ต่อมาเราจะมาเพิ่ม Button กัน โดยเราจะให้มันอยู่ใต้ TextView และอยู่ตรงกลางของด้านความกว้าง แบบในรูปนี้เลย
มาเพิ่ม TextView ตามนี้เลย เดี๋ยวเราจะมาอธิบายแต่ละส่วนกัน
- เราเพิ่ม
Button
ไว้ที่ด้านล่างของTextView
แต่ไม่ได้หมายความว่า Button อยู่ใต้ TextView นะ แค่วางในที่ที่เรากลับมาอ่าน layout ได้ง่ายเฉย ๆ ว่าวางอะไรตรงไหน layout_marginTop
บอกว่า เจ้า Button ตัวนี้ ห่างจากข้างบนเท่าไหร่ ในที่นี้ห่าง16dp
ซึ่ง dp เป็นหน่วยที่ใช้ในการกำหนดขนาดของ view นั้น ๆ นั่นเองlayout_constraintTop_toBottomOf
อันนี้บอกว่าด้านบน Button ของเรา reference กับด้านล่างของ View ไหน ในที่นี้คือ TextView จึงใส่ id ของ TextView ลงไป นั่นคือtextview
นั่นเอง- ส่วน
layout_constraintStart_toStartOf
และlayout_constraintEnd_toEndOf
เราจะอิงกับ TextView นั่นเอง
ต่อมา เรามาสร้าง Activity ใหม่กัน อันนี้ก็ลอกจากของเดิมมาเลยแหละ
อธิบายเพิ่มเติม
- อย่าลืมเพิ่ม
SecondActivity
ที่ไฟล์AndroidManifest.xml
ด้วยนะ ไม่งั้นพอเราเพิ่มปุ๊บ กดปุ่มปั้บ แอพ crash จ้า เพราะไม่ได้กำหนด Activity เพิ่มในนี้อ่ะ textSize
เป็น attribute ที่เอามากำหนดขนาดของตัวหนังสือ ซึ่งเราจะใช้เป็นหน่วย sp นะ
จากนั้นมาเพิ่มการทำงานของ Button ที่เราเพิ่งสร้างกัน โดยเราจะให้กดปุ่มนี้ แล้วไปหน้า SecondActivity
กัน ที่ MainActivity
นะ
เราทำการใช้ event listener ในการกดปุ่ม มีชื่อว่า setOnClickListener
โดยเรียกจาก View ที่เราต้องการ ในที่นี้คือ Button นั่นเอง
binding.button.setOnClickListener {
//TBC
}
ตอนนี้เราก็สามารถกดปุ่มในแอพเพื่อทำอะไรต่างๆได้แล้ว แต่เราก็ได้แค่กดปุ่ม ยังทำอะไรมากกว่านี้ไม่ได้ ถัดมาทำส่วนไปยังหน้า SecondActivity
โดยการสร้าง Intent ขึ้นมาใหม่ แบบนี้
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
ตัว Intent จะรับ 2 parameter คือ context ของจุดตั้งต้น ในที่นี้เป็น this เนอะ และ Class คือ หน้าไหนที่เราจะไป รับเป็น class เข้ามาเนอะ
รวมร่างกัน
เมื่อลองรันแอพดู ผลที่ได้คือ กดปุ่มแล้วไปหน้าที่สองแล้วนะ แบบนี้
แต่เรายังไปได้อีก เราจะทำการเปลี่ยน TextView
ของ MainActivity
เป็น EditText
เพื่อให้เราลองพิมพ์อะไรก็ได้ แล้วไปโชว์ที่ SecondActivity
ในไฟล์ activity_main.xml
เราจะเปลี่ยนจาก TextView
เป็น EditText
กัน
โดยเจ้า EditText เราจะตั้งชื่อมันว่า edittext
จะเปลี่ยนความกว้าง layout_width
จาก wrap_content
เป็น match_parent
และเพิ่ม margin แนวนอน layout_marginHorizontal
เป็น 24dp
เพื่อให้เรากดพิมพ์ได้ง่ายขึ้นนั่นเอง
แล้วก็อย่าลืมเปลี่ยน id ที่เชื่อมความสัมพันธ์ จาก textview
เป็น edittext
ด้วยนะ ไม่งั้นมันจะ error เพราะไม่มี id textview
นะ
จากนั้นเราจะทำการนำค่าที่เราพิมพ์จาก EditText
นำไปส่งต่อไปยัง SecondActivity
ผ่าน Intent นั่นเอง
การนำ text ที่เราพิมพ์บน EditText ออกมาใช้นั้น เราจะทำการอ้างอิง View ที่เราจะใช้ก่อน ในที่นี้คือ EditText
เนอะ จากนั้นเราใช้ text
ซึ่งเราสามารถใช้ในการ setText
หรือ getText
ก็ได้ แต่การที่เราจะ getText
จาก EditText มันจะคืนเป็น Editable
ออกมา
จึงต้องใช้ toString()
ตามหลังเพื่อให้ได้ text จริงๆออกมา
binding.edittext.text.toString()
การส่งค่า เราจะแนบไปกับ Intent โดยใส่ค่าที่เราจะส่งไปกับ putExtra()
โดย parameter แรก คือชื่อในการอ้างอิงค่านั้นๆ ในที่นี้เป็นคำว่า “text” และ parameter ที่สอง คือ ค่าที่เราจะส่งเข้าไปนั่นเอง
intent.putExtra("text", binding.edittext.text.toString())
เมื่อส่งค่าไปแล้ว อย่าลืมไปรับค่ามาใช้งานกันต่อนะ ไปที่ SecondActivity ทำการประกาศตัวแปร ชื่อว่า text
และใช้ getStringExtra()
ในการรับค่ามา โดยใช้เจ้าชื่อในการอ้างอิงเหมือนกันกับตอนส่งนะ
private val text by lazy {
intent.getStringExtra("text")
}
สุดท้าย นำ text ที่ได้ มาแสดงที่ TextView
binding.textview.text = text
และแล้วเราก็ได้ตามนี้แล้วแหละ เย้ๆ
โค้ดทั้งหมด
สุดท้ายเรามา refactor code กันในแบบที่ Android Developer ใช้กันนะ เนื่องจากในการทำงานจริง เช่น ลูกค้าซื้อของต่างๆลงตระกร้า แล้วต้องการ checkout เราก็จะไปเปิด activity หน้า checkout ซึ่งมีหลากหลายเส้นทางในการเปิดหน้านี้
ดังนั้นเราจะทำ function นึงขึ้นมา ชื่อว่า newIntent()
ในการส่งค่าต่างๆที่เราต้องการไปยัง Activity น้ันๆ โดยอยู่ใน companion object
ด้วยความที่ Kotlin มันไม่มี static จึงใส่ไว้ในนี้ เพื่อให้ class อื่น ๆ สามารถเรียกใช้ function นี้ได้นั่นเอง
แล้วก็เจ้าชื่อในการอ้างอิงอ่ะ ควรเป็นตัวแปรแบบ constant เนอะ
companion object {
const val EXTRA_TEXT = "SecondActivity:EXTRA_TEXT"
fun newIntent(context: Context, text: String): Intent {
return Intent(context, SecondActivity::class.java).apply {
putExtra(EXTRA_TEXT, text)
}
}
}
การนำไปใช้งาน
startActivity(SecondActivity.newIntent(
this,
binding.edittext.text.toString()
))
โค้ดที่ผ่านการ refactor มาแล้ว
อันที่เสร็จแล้วจะตามนี้เลย เรา commit ตามแต่ละขั้นตอนไว้ให้นะ
จริง ๆ แล้ว Intent มีกี่ประเภทกันนะ ?
Intent มี 2 ประเภท คือ
- Explicit Intent: เป็น intent ที่เรียก package app ที่เราต้องการ หรือเรียกใช้ class แบบเจาะจง โดยที่เราทำกันเมื่อกี้ จะเป็นแบบ Explicit เนอะ
- Implicit Intent: เป็น intent ที่ไม่ได้เรียกใช้แบบเฉพาะเจาะจง แต่จะต้องตรงกับเงื่อนไข ก็จะมี Action, Type และ Category
หน้าตาโค้ดในการเรียกใช้ Implicit Intent จะเป็นแบบนี้เนอะ
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(
Intent.EXTRA_TEXT,
"Hello Android Fragmentation World"
)
intent.type = "text/plain"
startActivity(intent)
ผลที่ได้ก็จะประมาณนี้อ่ะเนอะ
ตัวอย่างในชีวิตประจำวัน คือ เราอ่านข่าวจากเว็บแล้วเอ้ออออน่าสนใจมาก อยากแชร์ให้เพื่อนรับรู้ เราก็กดแชร์ลง Facebook อะไรงี้
ในรูปนี้ จากตัวอย่างเมื่อกี้ Activity A มาจากแอพ Chrome ส่ง Implicit Intent ไปให้ Android System แล้วโยนเจ้า Intent ไปที่ Activity B ของ Facebook
การรับส่งข้อมูล
ถ้าใช้ Parcelable จะเร็วกว่า Serializable เนอะ ซึ่งเราใช้ data ที่เป็น Parcelable ตลอดเลย แหะ สามารถอ่านเพิ่มเติมได้ที่บล็อกพี่เอกน้า
ในส่วนของ Activity ก็น่าจะประมาณนี้เนอะ บล็อกตอนต่อไป มาเรียนรู้เรื่อง Fragment กันเถอะ
Reference :
เนื่องจากบางเว็บ ทาง Medium ไม่ได้ generate ตัว link มาให้สวยงามทุกอัน เลยขอเป็นในรูปแบบ bullet ก็แล้วกัน กันความน้อยใจ 555
- https://developer.android.com/guide/components/activities/intro-activities
- https://blog.skooldio.com/activity-lifecycle-android/
- https://developer.android.com/develop/ui/views/layout/constraint-layout
- https://blacklenspub.com/kotlin-generation-ed77a1f7f47a
- https://developer.android.com/guide/components/intents-filters
- https://akexorcist.dev/android-intent-and-pending-intent/
- https://akexorcist.dev/parcelable-vs-serializable/