คู่มือนักพัฒนา Android สำหรับรูปแบบการนำทาง Fragment
เผยแพร่แล้ว: 2022-03-11หลายปีที่ผ่านมา ฉันได้เห็นการนำรูปแบบการนำทางต่างๆ ไปใช้ใน Android แอพบางตัวใช้เฉพาะกิจกรรม ในขณะที่บางตัวกิจกรรมผสมกับ Fragment และ/หรือ Custom Views
การนำรูปแบบการนำทางที่ฉันชื่นชอบไปใช้นั้นยึดตามปรัชญา "หนึ่งกิจกรรม-หลายส่วน" หรือเพียงแค่รูปแบบการนำทางแบบแยกส่วน ซึ่งทุกหน้าจอในแอปพลิเคชันเป็น Fragment แบบเต็มหน้าจอ และส่วนย่อยทั้งหมดหรือส่วนใหญ่เหล่านี้อยู่ใน หนึ่งกิจกรรม
แนวทางนี้ไม่เพียงแต่ทำให้การใช้งานการนำทางง่ายขึ้น แต่ยังมีประสิทธิภาพที่ดีขึ้นมาก และส่งผลให้ผู้ใช้ได้รับประสบการณ์ที่ดีขึ้น
ในบทความนี้ เราจะพิจารณาการใช้งานรูปแบบการนำทางทั่วไปใน Android จากนั้นจึงแนะนำรูปแบบการนำทางตาม Fragment การเปรียบเทียบและความแตกต่างกับรูปแบบอื่นๆ มีการอัปโหลดแอปพลิเคชันสาธิตที่ใช้รูปแบบนี้ไปยัง GitHub แล้ว
โลกแห่งกิจกรรม
แอปพลิเคชัน Android ทั่วไปที่ใช้เฉพาะกิจกรรมจะจัดอยู่ในโครงสร้างคล้ายต้นไม้ (ให้แม่นยำยิ่งขึ้นในกราฟกำกับ) โดยที่ตัวเรียกใช้งานเริ่มต้นกิจกรรมรูท เมื่อคุณไปยังส่วนต่างๆ ในแอปพลิเคชัน จะมี back stack ของกิจกรรมที่ OS ดูแล
ตัวอย่างง่ายๆแสดงในแผนภาพด้านล่าง:
กิจกรรม A1 เป็นจุดเริ่มต้นในแอปพลิเคชันของเรา (เช่น เป็นหน้าจอเริ่มต้นหรือเมนูหลัก) จากนั้นผู้ใช้สามารถนำทางไปยัง A2 หรือ A3 เมื่อคุณต้องการสื่อสารระหว่างกิจกรรม คุณสามารถใช้ startActivityForResult() หรือบางทีคุณอาจแบ่งปันวัตถุตรรกะทางธุรกิจที่เข้าถึงได้ทั่วโลกระหว่างกัน
เมื่อคุณต้องการเพิ่มกิจกรรมใหม่ คุณต้องทำตามขั้นตอนต่อไปนี้:
- กำหนดกิจกรรมใหม่
- ลงทะเบียนใน AndroidManifest.xml
- เปิดด้วย startActivity() จากกิจกรรมอื่น
แน่นอนว่าแผนภาพการนำทางนี้เป็นแนวทางที่ค่อนข้างง่าย มันอาจจะซับซ้อนมากเมื่อคุณต้องจัดการแบ็คสแต็กหรือเมื่อคุณต้องใช้กิจกรรมเดิมซ้ำหลายครั้ง เช่น เมื่อคุณต้องการนำทางผู้ใช้ผ่านหน้าจอบทช่วยสอน แต่จริง ๆ แล้วแต่ละหน้าจอใช้กิจกรรมเดียวกันเป็น ฐาน.
โชคดีที่เรามีเครื่องมือที่เรียกว่างานและแนวทางปฏิบัติบางประการสำหรับการนำทางแบ็คสแตกที่เหมาะสม
จากนั้นด้วย API ระดับ 11 ก็แตกเป็นเสี่ยงๆ...
โลกแห่งชิ้นส่วน
Android เปิดตัวแฟรกเมนต์ใน Android 3.0 (API ระดับ 11) เพื่อรองรับการออกแบบ UI แบบไดนามิกและยืดหยุ่นมากขึ้นบนหน้าจอขนาดใหญ่ เช่น แท็บเล็ตเป็นหลัก เนื่องจากหน้าจอแท็บเล็ตมีขนาดใหญ่กว่าโทรศัพท์มือถือมาก จึงมีพื้นที่มากขึ้นในการรวมและเปลี่ยนส่วนประกอบ UI Fragment อนุญาตให้ออกแบบดังกล่าวโดยที่คุณไม่ต้องจัดการการเปลี่ยนแปลงที่ซับซ้อนในลำดับชั้นของมุมมอง การแบ่งเค้าโครงของกิจกรรมออกเป็นแฟรกเมนต์ คุณจะสามารถแก้ไขลักษณะที่ปรากฏของกิจกรรมขณะรันไทม์ และรักษาการเปลี่ยนแปลงเหล่านั้นไว้ในแบ็คสแต็กที่จัดการโดยกิจกรรม – อ้างจากคู่มือ API ของ Google สำหรับ Fragments
ของเล่นใหม่นี้ช่วยให้นักพัฒนาสามารถสร้าง UI แบบหลายบานหน้าต่างและนำส่วนประกอบกลับมาใช้ในกิจกรรมอื่นๆ ได้ นักพัฒนาบางคนชอบสิ่งนี้ในขณะที่คนอื่นไม่ชอบ เป็นที่ถกเถียงกันโดยทั่วไปว่าจะใช้ Fragment หรือไม่ แต่ฉันคิดว่าทุกคนคงเห็นด้วยที่ Fragment นั้นนำมาซึ่งความซับซ้อนเพิ่มเติม และนักพัฒนาจำเป็นต้องเข้าใจมันจริงๆ เพื่อที่จะใช้มันได้อย่างเหมาะสม
Fullscreen Fragment Nightmare ใน Android
ฉันเริ่มเห็นตัวอย่างมากขึ้นเรื่อยๆ โดยที่ชิ้นส่วนต่างๆ ไม่ได้เป็นเพียงส่วนหนึ่งของหน้าจอ แต่อันที่จริงแล้ว ทั้งหน้าจอเป็นเพียงส่วนเล็กๆ ที่มีอยู่ในกิจกรรม เมื่อฉันเห็นการออกแบบที่ทุกกิจกรรมมีส่วนเต็มหน้าจอเพียงส่วนเดียวและไม่มีอะไรมากไปกว่านี้ และเหตุผลเดียวที่กิจกรรมเหล่านี้มีอยู่คือการโฮสต์ส่วนย่อยเหล่านี้ ถัดจากข้อบกพร่องในการออกแบบที่ชัดเจน ยังมีปัญหาอื่นกับแนวทางนี้ ดูแผนภาพจากด้านล่าง:
A1 สามารถสื่อสารกับ F1 ได้อย่างไร? A1 สามารถควบคุม F1 ได้ทั้งหมดตั้งแต่สร้าง F1 A1 สามารถส่งผ่านบันเดิลได้ ตัวอย่างเช่น ในการสร้าง F1 หรือสามารถเรียกใช้เมธอดสาธารณะได้ F1 สามารถสื่อสารกับ A1 ได้อย่างไร สิ่งนี้ซับซ้อนกว่า แต่สามารถแก้ไขได้ด้วยรูปแบบการโทรกลับ/ผู้สังเกตการณ์ โดยที่ A1 สมัครรับข้อมูล F1 และ F1 แจ้งเตือน A1
แต่ A1 และ A2 จะสื่อสารกันได้อย่างไร? สิ่งนี้ได้รับการคุ้มครองแล้ว ตัวอย่างเช่น ผ่าน startActivityForResult()
และตอนนี้คำถามที่แท้จริงก็มาถึง: F1 และ F2 จะสื่อสารกันได้อย่างไร ในกรณีนี้ เราสามารถมีองค์ประกอบตรรกะทางธุรกิจที่พร้อมใช้งานทั่วโลก ดังนั้นจึงสามารถใช้เพื่อส่งข้อมูลได้ แต่สิ่งนี้ไม่ได้นำไปสู่การออกแบบที่หรูหราเสมอไป จะเกิดอะไรขึ้นถ้า F2 ต้องการส่งข้อมูลบางอย่างไปยัง F1 ในทางที่ตรงกว่านี้ ด้วยรูปแบบการโทรกลับ F2 สามารถแจ้งเตือน A2 จากนั้น A2 เสร็จสิ้นด้วยผลลัพธ์และผลลัพธ์นี้จะถูกบันทึกโดย A1 ซึ่งแจ้งเตือน F1
วิธีนี้ต้องใช้รหัสสำเร็จรูปจำนวนมากและกลายเป็นแหล่งของแมลง ความเจ็บปวด และความโกรธอย่างรวดเร็ว
จะเกิดอะไรขึ้นถ้าเราสามารถกำจัดกิจกรรมทั้งหมดและเก็บเพียงกิจกรรมเดียวที่เก็บชิ้นส่วนที่เหลือ
รูปแบบการนำทางส่วนย่อย
หลายปีที่ผ่านมา ฉันเริ่มใช้รูปแบบ "หนึ่งกิจกรรม-หลายส่วน" ในแอปพลิเคชันส่วนใหญ่ของฉัน และยังคงใช้รูปแบบนี้อยู่ มีการอภิปรายมากมายเกี่ยวกับแนวทางนี้ ตัวอย่างเช่น ที่นี่ และ ที่นี่ สิ่งที่ฉันพลาดไปคือตัวอย่างที่เป็นรูปธรรมซึ่งฉันสามารถเห็นและทดสอบตัวเองได้
ลองดูแผนภาพต่อไปนี้:
ตอนนี้ เรามีกิจกรรมคอนเทนเนอร์เพียงกิจกรรมเดียว และเรามีชิ้นส่วนหลายชิ้นที่มีโครงสร้างเหมือนต้นไม้อีกครั้ง FragmentManager นำทางระหว่างกัน โดยมีแบ็คสแตก
สังเกตว่าตอนนี้เราไม่มี startActivityForResult() แต่เราสามารถใช้รูปแบบการโทรกลับ/ผู้สังเกตการณ์ได้ มาดูข้อดีและข้อเสียของวิธีนี้กัน:
ข้อดี:
1. AndroidManifest.xml . สะอาดกว่าและบำรุงรักษาได้มากกว่า
ขณะนี้เรามีเพียงหนึ่งกิจกรรม เราไม่จำเป็นต้องอัปเดตรายการทุกครั้งที่เราเพิ่มหน้าจอใหม่อีกต่อไป ต่างจากกิจกรรมที่เราไม่ต้องประกาศแฟรกเมนต์
อาจดูเหมือนเป็นเรื่องเล็กน้อย แต่สำหรับแอปพลิเคชันขนาดใหญ่ซึ่งมีกิจกรรมมากกว่า 50 กิจกรรม จะสามารถปรับปรุงความสามารถในการอ่านไฟล์ AndroidManifest.xml ได้อย่างมาก
ดูไฟล์รายการของแอปพลิเคชันตัวอย่างที่มีหลายหน้าจอ ไฟล์ Manifest ยังคงเรียบง่าย
<?xml version="1.0" encoding="utf-8"?> package="com.exarlabs.android.fragmentnavigationdemo.ui" > <application android:name= ".FragmentNavigationDemoApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.exarlabs.android.fragmentnavigationdemo.ui.MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
2. การจัดการการนำทางแบบรวมศูนย์
ในตัวอย่างโค้ดของฉัน คุณจะเห็นว่าฉันใช้ NavigationManager ซึ่งในกรณีของฉันมันจะถูกฉีดเข้าไปในทุกส่วน ผู้จัดการนี้สามารถใช้เป็นที่รวมศูนย์สำหรับการบันทึก การจัดการแบ็คสแตก และอื่นๆ ดังนั้นพฤติกรรมการนำทางจะถูกแยกออกจากส่วนที่เหลือของตรรกะทางธุรกิจ และไม่กระจายไปทั่วในการใช้งานของหน้าจอต่างๆ

ลองนึกภาพสถานการณ์ที่เราอยากจะเริ่มหน้าจอที่ผู้ใช้สามารถเลือกบางรายการจากรายชื่อบุคคล คุณยังต้องการส่งผ่านอาร์กิวเมนต์การกรองบางอย่าง เช่น อายุ อาชีพ และเพศ
ในกรณีของกิจกรรม คุณจะเขียน:
Intent intent = new Intent(); intent.putExtra("age", 40); intent.putExtra("occupation", "developer"); intent.putExtra("gender", "female"); startActivityForResult(intent, 100);
จากนั้นคุณต้องกำหนด onActivityResult ที่ใดที่หนึ่งด้านล่างและจัดการกับผลลัพธ์
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); }
ปัญหาส่วนตัวของฉันเกี่ยวกับวิธีการนี้คือการที่อาร์กิวเมนต์เหล่านี้เป็น "ส่วนเสริม" และไม่จำเป็น ดังนั้นฉันต้องตรวจสอบให้แน่ใจว่ากิจกรรมที่ได้รับจะจัดการกับกรณีต่างๆ ทั้งหมดเมื่อส่วนเพิ่มเติมขาดหายไป ต่อมาเมื่อมีการสร้างโครงสร้างใหม่และไม่จำเป็นต้องใช้ "อายุ" เพิ่มเติมอีกต่อไป ฉันต้องค้นหาทุกที่ในโค้ดที่ฉันเริ่มกิจกรรมนี้ และตรวจสอบให้แน่ใจว่าส่วนเสริมทั้งหมดถูกต้อง
นอกจากนี้ จะดีกว่าไหมถ้าผลลัพธ์ (รายชื่อบุคคล) มาถึงในรูปแบบ _List
ในกรณีของการนำทางแบบแยกส่วน ทุกอย่างจะตรงไปตรงมามากขึ้น สิ่งที่คุณต้องทำคือเขียนเมธอดใน NavigationManager ที่เรียกว่า startPersonSelectorFragment() ด้วยอาร์กิวเมนต์ที่จำเป็นและด้วยการใช้งานการเรียกกลับ
mNavigationManager.startPersonSelectorFragment(40, "developer", "female", new PersonSelectorFragment.OnPersonSelectedListener() { @Override public boolean onPersonsSelected(List<Person> selection) { [do something] return false; } });
หรือกับ RetroLambda
mNavigationManager.startPersonSelectorFragment(40, "developer", "female", selection -> [do something]);
3. วิธีการสื่อสารที่ดีขึ้นระหว่างหน้าจอ
ระหว่างกิจกรรม เราสามารถแบ่งปันได้เฉพาะ Bundle ที่สามารถเก็บข้อมูลพื้นฐานหรือข้อมูลต่อเนื่องได้ ขณะนี้ด้วยแฟรกเมนต์ เราสามารถใช้รูปแบบการโทรกลับได้ เช่น F1 สามารถฟัง F2 ที่ส่งผ่านอ็อบเจ็กต์ตามอำเภอใจได้ โปรดดูตัวอย่างการใช้งานการโทรกลับของตัวอย่างก่อนหน้า ซึ่งส่งคืน _List
4. Building Fragment ถูกกว่าการสร้างกิจกรรม
สิ่งนี้จะชัดเจนเมื่อคุณใช้ลิ้นชักซึ่งมีตัวอย่าง 5 รายการเมนูและในแต่ละหน้าลิ้นชักควรแสดงอีกครั้ง
ในกรณีของการนำทางกิจกรรมล้วนๆ แต่ละหน้าควรขยายและเริ่มต้นลิ้นชัก ซึ่งแน่นอนว่ามีราคาแพง
ในไดอะแกรมด้านล่าง คุณจะเห็นรูทแฟรกเมนต์ (FR*) หลายส่วน ซึ่งเป็นแฟรกเมนต์แบบเต็มหน้าจอ ซึ่งสามารถเข้าถึงได้โดยตรงจาก Drawer และ Drawer สามารถเข้าถึงได้เฉพาะเมื่อมีการแสดงส่วนย่อยเหล่านี้ ทุกสิ่งที่อยู่ทางด้านขวาของเส้นประในไดอะแกรมมีไว้เป็นตัวอย่างของโครงร่างการนำทางตามอำเภอใจ
เนื่องจากกิจกรรมคอนเทนเนอร์มีลิ้นชัก เราจึงมีอินสแตนซ์ลิ้นชักเพียงตัวเดียว ดังนั้นในทุกขั้นตอนการนำทางที่ควรมองเห็นลิ้นชัก คุณจึงไม่ต้องขยายและเริ่มต้นใหม่อีกครั้ง ยังไม่แน่ใจว่าสิ่งเหล่านี้ทำงานอย่างไร ดูตัวอย่างแอปพลิเคชันของฉันซึ่งสาธิตการใช้งานลิ้นชัก
ข้อเสีย
ความกลัวที่ยิ่งใหญ่ที่สุดของฉันคือการที่ถ้าฉันใช้รูปแบบการนำทางแบบแยกส่วนในโปรเจ็กต์ ที่ไหนสักแห่งที่อยู่ข้างหน้า ฉันจะพบปัญหาที่ไม่คาดฝัน ซึ่งแก้ไขได้ยากเมื่อต้องอาศัยความซับซ้อนที่เพิ่มขึ้นของชิ้นส่วน ไลบรารีของบุคคลที่สาม และระบบปฏิบัติการเวอร์ชันต่างๆ จะเกิดอะไรขึ้นหากฉันต้องจัดโครงสร้างใหม่ทุกสิ่งที่ฉันได้ทำไปแล้ว
อันที่จริง ฉันต้องแก้ไขปัญหาเกี่ยวกับแฟรกเมนต์ที่ซ้อนกัน ไลบรารีบุคคลที่สามซึ่งใช้แฟรกเมนต์ เช่น ShinobiControls, ViewPagers และ FragmentStatePagerAdapters ด้วย
ฉันต้องยอมรับว่าการได้รับประสบการณ์เพียงพอกับชิ้นส่วนเพื่อให้สามารถแก้ปัญหาเหล่านี้เป็นกระบวนการที่ค่อนข้างยาว แต่ในทุกกรณีปัญหาไม่ได้อยู่ที่ว่าปรัชญาไม่ดี แต่ฉันไม่เข้าใจเศษส่วนดีพอ บางทีถ้าคุณเข้าใจเศษส่วนดีกว่าฉัน คุณจะไม่พบปัญหาเหล่านี้ด้วยซ้ำ
ข้อเสียเดียวที่ฉันสามารถพูดถึงได้ในตอนนี้คือเรายังคงพบปัญหาซึ่งไม่น่าจะแก้ได้เนื่องจากไม่มีไลบรารีที่ครบถ้วนซึ่งแสดงสถานการณ์ที่ซับซ้อนทั้งหมดของแอปพลิเคชันที่ซับซ้อนพร้อมการนำทางตามส่วนย่อย
บทสรุป
ในบทความนี้ เราได้เห็นวิธีอื่นในการใช้การนำทางในแอปพลิเคชัน Android เราเปรียบเทียบกับปรัชญาการนำทางแบบดั้งเดิมที่ใช้กิจกรรม และเราได้เห็นเหตุผลดีๆ สองสามข้อว่าทำไมการใช้สิ่งนี้จึงมีประโยชน์มากกว่าวิธีการแบบเดิม
ในกรณีที่คุณยังไม่ได้ดำเนินการ ให้ตรวจสอบแอปพลิเคชันสาธิตที่อัปโหลดไปยังการติดตั้ง GitHub อย่าลังเลที่จะแยกหรือสนับสนุนด้วยตัวอย่างที่ดีกว่าซึ่งจะแสดงการใช้งานได้ดียิ่งขึ้น