เศษเล็กเศษน้อยภายใน


145

ฉันสงสัยว่านี่เป็นข้อบกพร่องจริง ๆ ใน Android API หรือไม่:

ฉันมีการตั้งค่าเช่นนั้น:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. เป็นเมนูที่โหลดแฟรกเมนต์ # 2 (หน้าจอค้นหา) ในบานหน้าต่างด้านขวา
  2. เป็นหน้าจอค้นหาที่มีแฟรกเมนต์ # 3 ซึ่งเป็นรายการผลลัพธ์
  3. รายการผลลัพธ์ถูกใช้ในหลาย ๆ ที่ (รวมทั้งเป็นชิ้นส่วนระดับสูงที่ทำงานได้ในสิทธิ์ของตัวเอง)

ฟังก์ชั่นนี้ใช้งานได้ดีบนโทรศัพท์มือถือ (ในกรณีที่ 1 & 2 และ 3 ActivityFragment)

อย่างไรก็ตามเมื่อฉันใช้รหัสนี้:

    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();       
    Fragment frag = new FragmentNumber2();
    if(toLoad != null) frag.setArguments(toLoad);
    transaction.replace(R.id.rightPane, frag);      
    transaction.commit();

ที่ไหนR.id.leftPaneและR.id.rightPaneอยู่<fragment>ในเค้าโครงเชิงเส้นแนวนอน

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

07-27 15:22:55.940: ERROR/AndroidRuntime(8105): Caused by: java.lang.IllegalArgumentException: Binary XML file line #57: Duplicate id 0x7f080024, tag null, or parent id 0x0 with another fragment for FragmentNumber3

นี่คือสาเหตุเนื่องจากคอนเทนเนอร์สำหรับ FragmentNumber3 ถูกทำซ้ำและไม่มี ID ที่ไม่ซ้ำอีกต่อไป ชิ้นส่วนเริ่มต้นยังไม่ถูกทำลาย (?) ก่อนที่จะเพิ่มชิ้นใหม่ (ในใจของฉันซึ่งหมายความว่าจะไม่ถูกแทนที่ )

มีคนบอกฉันได้ไหมว่าคำตอบนี้เป็นไปได้หรือไม่


1
เป็นไปได้ซ้ำของFragment Inside Fragment
rds

6
@rds นี่เป็นคำถามโบราณบิตไม่มีจุดหมายเพื่อทำเครื่องหมายว่าซ้ำกัน
pietv8x

คำตอบ:


203

ไม่รองรับแฟรกเมนต์ที่ซ้อนกัน การพยายามแยกส่วนภายใน UI ของชิ้นส่วนอื่นจะส่งผลให้พฤติกรรมที่ไม่ได้กำหนดและมีแนวโน้มที่จะเกิดความเสียหาย

อัปเดต : สนับสนุนแฟรกเมนต์ที่ซ้อนกันตั้งแต่ Android 4.2 (และ Android Support Library rev 11): http://developer.android.com/about/versions/android-4.2.html#NestedFragments

หมายเหตุ (ตามเอกสารนี้ ): " หมายเหตุ: คุณไม่สามารถขยายเลย์เอาต์ลงในแฟรกเมนต์เมื่อโครงร่างนั้นรวม a <fragment>. แฟรกเมนต์ที่ซ้อนกันได้รับการสนับสนุนก็ต่อเมื่อถูกเพิ่มลงในแฟรกเมนต์แบบไดนามิก "


14
ไม่รองรับเนื่องจากไม่ใช่เป้าหมายการออกแบบสำหรับการนำไปใช้ครั้งแรก ฉันเคยได้ยินคำขอจำนวนมากเกี่ยวกับคุณลักษณะนี้ดังนั้นจึงอาจทำได้ในบางจุด แต่ตามปกติจะมีสิ่งอื่น ๆ อีกมากมายที่ต้องแข่งขันกับลำดับความสำคัญ
hackbod

4
ฉันจัดการสิ่งนี้ด้วยการขยาย FragmentActivity, FragmentManager และ FragmentTransaction หลักฐานพื้นฐานขยาย DeferringFragmentActivity ในกิจกรรมของฉันโดยให้ API เดียวกันดังนั้นจึงไม่มีการเปลี่ยนแปลงรหัสอื่น ๆ เมื่อฉันเรียกใช้ getFragmentManager ฉันจะได้รับอินสแตนซ์ที่ DeferringFragmentManager และเมื่อฉันเรียกใช้ startTransaction ฉันจะได้รับ DeferredTransaction ธุรกรรมนี้เก็บ POJO ด้วยวิธีการและอาร์กิวเมนต์ที่เรียก เมื่อคอมมิชชันคือการโทรเราจะมองหา DeferredTransactions เมื่อการทำธุรกรรมทั้งหมดได้รับการยอมรับเราจะเริ่มต้นการทำธุรกรรมจริงและเรียกใช้วิธีการที่เก็บไว้ทั้งหมดด้วย args
dskinner

11
จุดนั้นคือตอนนี้ Fragmentตอนนี้ซ้อนเป็นส่วนหนึ่งของ Android API ใช่แล้ว! developer.android.com/about/versions/...
Alex Lockwood

9
ว้าวฝันร้ายเป็นอย่างไร: ถ้าคุณใช้ <fragment> บนแฟรกเมนต์และแฟรกเมนต์นั้นเกิดขึ้นเพื่อใช้แฟรกเมนต์เด็กมันจะไม่ล้มเหลวด้วยข้อผิดพลาดที่ชัดเจน ล้มเหลวอย่างลึกลับโดยมีข้อยกเว้นเช่น "ชิ้นส่วนไม่ได้สร้างมุมมอง" มีหลายชั่วโมงของการแก้จุดบกพร่องเวลาผ่านไป ...
เกล็นเมย์นาร์

6
@ MartínMarconciniแน่นอน แต่นั่นก็ไม่ได้ชัดเจนทั้งหมดขึ้นอยู่กับการทำงานที่ได้รับจาก API หากสิ่งที่ไม่ได้รับอนุญาตควรมีการบันทึกไว้อย่างชัดเจนอย่าปล่อยให้นักพัฒนาดึงผมออกเพราะมีบางอย่างไม่ทำงานตามที่คุณคาดหวัง
dcow

98

แฟรกเมนต์ที่ซ้อนกันได้รับการสนับสนุนใน Android 4.2 และใหม่กว่า

ขณะนี้ไลบรารีสนับสนุน Android ยังสนับสนุนชิ้นส่วนที่ซ้อนอยู่ด้วยดังนั้นคุณสามารถใช้การออกแบบชิ้นส่วนซ้อนกันบน Android 1.6 และสูงกว่า

หากต้องการซ้อนแฟรกเมนต์ให้เรียกใช้getChildFragmentManager ()บนแฟรกเมนต์ที่คุณต้องการเพิ่มแฟรกเมนต์ สิ่งนี้จะส่งคืน FragmentManager ที่คุณสามารถใช้เหมือนปกติจากกิจกรรมระดับบนสุดเพื่อสร้างธุรกรรมแฟรกเมนต์ ตัวอย่างเช่นต่อไปนี้เป็นโค้ดบางส่วนที่เพิ่มแฟรกเมนต์จากภายในคลาสแฟรกเมนต์ที่มีอยู่:

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

หากต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับชิ้นส่วนที่ซ้อนกันโปรดอ่านบทช่วยสอนเหล่านี้
ตอนที่ 1
ส่วนที่ 2
ส่วนที่ 3

และนี่คือการโพสต์ SO ซึ่งหารือเกี่ยวกับการปฏิบัติที่ดีที่สุดสำหรับชิ้นส่วนที่ซ้อนกัน


ข้อเสียเปรียบหลักของ Nestedfragment คือเราไม่สามารถเรียก optionmenu จาก childfragment :( ถ้าเราใช้ระบบ ABS!
LOG_TAG

คุณช่วยตรวจสอบปัญหาของฉันได้ไหม? มันคล้ายกันมาก .. stackoverflow.com/questions/32240138/... สำหรับฉันแล้วเด็ก framnet ไม่ได้รับการพองตัวจากรหัส
Nicks

33

.. คุณสามารถล้างส่วนที่ซ้อนกันของคุณในdestroyviewวิธีการของส่วนผู้ปกครอง:

@Override
    public void onDestroyView() {

      try{
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();

        transaction.remove(nestedFragment);

        transaction.commit();
      }catch(Exception e){
      }

        super.onDestroyView();
    }

4
หากคุณทำการทดสอบวงจรชีวิตด้วย SetAlwaysFinish ( bricolsoftconsulting.com/2011/12/23/ … ) คุณจะเห็นว่ารหัสนี้ทำให้เกิดข้อผิดพลาดเมื่อกิจกรรมอื่นดำเนินต่อไปพร้อมกับเปิดใช้งานให้เสร็จสิ้นเสมอ (IllegalStateException: ไม่สามารถดำเนินการนี้ได้ หลังจาก onSaveInstanceState) การตัดโค้ดด้านบนด้วยการลอง / จับไม่ใช่วิธีที่หรูหราที่สุด แต่ดูเหมือนว่าจะทำให้ทุกอย่างทำงานได้
Theo

เกือบจะใช้งานได้แล้ว หลังจากนั้นฉันได้รับ Stackoverflow ในการวาด UI แน่นอนหลีกเลี่ยงเศษซ้อนกัน ...
neteinstein

14

ฉันมีแอปพลิเคชันที่ฉันกำลังพัฒนาซึ่งมีลักษณะคล้ายกับ Tabs ใน Action Bar ที่เปิดตัวแฟรกเมนต์บางส่วนของแฟรกเมนต์เหล่านี้มีแฟรกเมนต์ฝังตัวจำนวนมากอยู่ภายใน

ฉันได้รับข้อผิดพลาดเดียวกันเมื่อฉันพยายามเรียกใช้แอปพลิเคชัน ดูเหมือนว่าหากคุณยกตัวอย่างชิ้นส่วนภายในเลย์เอาต์ xml หลังจากที่ไม่มีการเลือกแท็บแล้วทำการเลือกใหม่ฉันจะได้รับข้อผิดพลาดของ inflator

ฉันแก้ไขสิ่งนี้แทนที่ชิ้นส่วนทั้งหมดใน xml ด้วย Linearlayouts จากนั้นใช้ Fragment manager / transaction fragment เพื่อสร้างอินสแตนซ์ชิ้นส่วนที่ทุกอย่างทำงานได้อย่างถูกต้องในระดับการทดสอบอย่างน้อยตอนนี้

ฉันหวังว่านี่จะช่วยคุณได้


ทุกคนสามารถแสดงความคิดเห็นเกี่ยวกับประสิทธิภาพของวิธีการนี้ได้หรือไม่? ฉันคิดว่ามันน่าเสียดายที่สามารถใช้ Fragments ได้เพียงระดับเดียวเท่านั้น - อาจใช้ไม่ได้เลย การเพิ่มพวกเขาโดยทางโปรแกรมลงในกลุ่มการดูตัวแทนจะทำงานโดยไม่มีคำเตือน?
Rafael Nobre

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

4

ฉันได้พบกับปัญหาเดียวกันได้พยายามสองสามวันด้วยและควรบอกว่าวิธีที่ง่ายที่สุดที่จะเอาชนะฉันพบว่าการใช้ fragment.hide () / fragment.show () เมื่อเลือกแท็บ / ไม่เลือก ()

public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
    if (mFragment != null)
        ft.hide(mFragment);
}

เมื่อการหมุนหน้าจอเกิดขึ้นชิ้นส่วนแม่และลูกทั้งหมดจะถูกทำลายอย่างถูกต้อง

วิธีการนี้มีข้อดีอีกประการหนึ่งคือการใช้ hide () / show () ไม่ทำให้มุมมองส่วนย่อยหลุดสถานะดังนั้นไม่จำเป็นต้องกู้คืนตำแหน่งการเลื่อนก่อนหน้านี้สำหรับ ScrollViews ตัวอย่างเช่น

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

ฉันต้องการฟังความคิดเห็นจากนักพัฒนาที่มีประสบการณ์มากกว่า


0

หากคุณพบว่าชิ้นส่วนที่ซ้อนกันของคุณไม่ถูกลบหรือถูกทำซ้ำ (เช่นเมื่อรีสตาร์ทกิจกรรมหมุนหน้าจอ) ลองเปลี่ยน:

transaction.add(R.id.placeholder, newFragment);

ถึง

transaction.replace(R.id.placeholder, newFragment);

หากด้านบนไม่ช่วยลอง:

Fragment f = getChildFragmentManager().findFragmentById(R.id.placeholder);

FragmentTransaction transaction = getChildFragmentManager().beginTransaction();

if (f == null) {
    Log.d(TAG, "onCreateView: fragment doesn't exist");
    newFragment= new MyFragmentType();
    transaction.add(R.id.placeholder, newFragment);
} else {
    Log.d(TAG, "onCreateView: fragment already exists");
    transaction.replace(R.id.placeholder, f);
}
transaction.commit();

เรียนรู้ที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.