ส่วนที่ซ้อนกันจะหายไปในระหว่างการเปลี่ยนภาพเคลื่อนไหว


99

นี่คือสถานการณ์: กิจกรรมมีชิ้นส่วนAซึ่งจะใช้getChildFragmentManager()ในการเพิ่มชิ้นส่วนA1และA2ในonCreateลักษณะนี้:

getChildFragmentManager()
  .beginTransaction()
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

จนถึงตอนนี้ดีมากทุกอย่างดำเนินไปตามที่คาดไว้

จากนั้นเราจะเรียกใช้ธุรกรรมต่อไปนี้ในกิจกรรม:

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .replace(R.id.fragmentHolder, new FragmentB())
  .addToBackStack(null)
  .commit()

ในระหว่างการเปลี่ยนแปลงที่enterภาพเคลื่อนไหวสำหรับชิ้นส่วนBทำงานได้อย่างถูกต้อง แต่เศษ A1 และ A2 หายไปอย่างสิ้นเชิง เมื่อเราเปลี่ยนกลับธุรกรรมด้วยปุ่มย้อนกลับการทำธุรกรรมจะเริ่มต้นอย่างถูกต้องและแสดงตามปกติในระหว่างการpopEnterเคลื่อนไหว

ในการทดสอบสั้น ๆ ของฉันมันแปลกกว่า - ถ้าฉันตั้งค่าภาพเคลื่อนไหวสำหรับชิ้นส่วนย่อย (ดูด้านล่าง) exitภาพเคลื่อนไหวจะทำงานเป็นระยะ ๆ เมื่อเราเพิ่มส่วนB

getChildFragmentManager()
  .beginTransaction()
  .setCustomAnimations(enter, exit)
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

เอฟเฟกต์ที่ฉันต้องการบรรลุนั้นเรียบง่าย - ฉันต้องการให้แอนิเมชั่นexit(หรือควรจะเป็นpopExit?) บนAแฟรกเมนต์ (anim2) ทำงานโดยทำให้คอนเทนเนอร์ทั้งหมดเคลื่อนไหวรวมทั้งลูกที่ซ้อนกันด้วย

มีวิธีใดบ้างที่จะบรรลุสิ่งนั้น?

แก้ไข : โปรดดูกรณีทดสอบที่นี่

Edit2 : ขอบคุณ @StevenByle ที่ผลักดันให้ฉันพยายามต่อไปด้วยภาพเคลื่อนไหวแบบคงที่ เห็นได้ชัดว่าคุณสามารถตั้งค่าแอนิเมชั่นแบบต่อออปได้ (ไม่ใช่แบบโกลบอลไปจนถึงธุรกรรมทั้งหมด) ซึ่งหมายความว่าเด็ก ๆ สามารถมีชุดแอนิเมชั่นคงที่ไม่ จำกัด ในขณะที่พาเรนต์ของพวกเขาสามารถมีแอนิเมชั่นที่แตกต่างกันและสามารถทำทุกอย่างได้ในธุรกรรมเดียว . ดูการอภิปรายด้านล่างและที่โครงการกรณีทดสอบการปรับปรุง


คืออะไรR.id.fragmentHolderที่เกี่ยวกับ A, A1, A2, etc?
CommonsWare

FragmentHolder เป็นรหัสในโครงร่างของกิจกรรมผู้ถือ Fragment {One, Two} อยู่ในเค้าโครงของส่วน A ทั้งสามมีความแตกต่างกัน เริ่มแรก Fragment A ถูกเพิ่มใน fragmentHolder (เช่นแฟรกเมนต์ B กำลังแทนที่แฟรกเมนต์ A)
Delyan

ฉันได้สร้างโครงการตัวอย่างที่นี่: github.com/BurntBrunch/NestedFragmentsAnimationsTestนอกจากนี้ยังมี apk ที่รวมอยู่ในที่เก็บ นี่เป็นข้อผิดพลาดที่น่ารำคาญจริงๆและฉันกำลังมองหาวิธีแก้ไข (สมมติว่าไม่มีอยู่ในรหัสของฉัน)
Delyan

ฉันทราบข้อมูลเพิ่มเติมเกี่ยวกับปัญหานี้แล้ว สาเหตุที่ชิ้นส่วนหายไปเป็นเพราะเด็ก ๆ จัดการกับเหตุการณ์วงจรชีวิตก่อนพาเรนต์ โดยพื้นฐานแล้ว A1 และ A2 จะถูกลบออกก่อน A และเนื่องจากไม่มีการตั้งค่าภาพเคลื่อนไหวจึงหายไปทันที วิธีที่จะลดปัญหานี้ได้บ้างคือการลบ A1 และ A2 ออกอย่างชัดเจนในธุรกรรมที่แทนที่ A ด้วยวิธีนี้จะทำให้เคลื่อนไหวได้เมื่อออกจากระบบอย่างไรก็ตามความเร็วในการเคลื่อนไหวจะเป็นกำลังสองเนื่องจากคอนเทนเนอร์หลักจะเคลื่อนไหวด้วยเช่นกัน วิธีการแก้ปัญหาที่ไม่ก่อให้เกิดสิ่งประดิษฐ์นี้จะได้รับการชื่นชม
Delyan

การเปลี่ยนแปลง (แทนที่ส่วนเริ่มต้น) ที่คุณพูดถึงในคำถามคือของจริงที่คุณต้องการทำหรือเป็นเพียงตัวอย่าง? คุณจะเรียกใช้changeFragmentวิธีนี้เพียงครั้งเดียว?
Luksprog

คำตอบ:


37

เพื่อหลีกเลี่ยงไม่ให้ผู้ใช้เห็นชิ้นส่วนที่ซ้อนกันหายไปเมื่อชิ้นส่วนพาเรนต์ถูกลบ / แทนที่ในธุรกรรมคุณสามารถ "จำลอง" ชิ้นส่วนเหล่านั้นที่ยังคงมีอยู่โดยการให้ภาพของพวกมันตามที่ปรากฏบนหน้าจอ รูปภาพนี้จะถูกใช้เป็นพื้นหลังสำหรับคอนเทนเนอร์แฟรกเมนต์ที่ซ้อนกันดังนั้นแม้ว่ามุมมองของส่วนที่ซ้อนกันจะหายไป แต่รูปภาพก็จะจำลองการมีอยู่ นอกจากนี้ฉันไม่เห็นว่าการสูญเสียการโต้ตอบกับมุมมองของส่วนที่ซ้อนกันเป็นปัญหาเพราะฉันไม่คิดว่าคุณจะต้องการให้ผู้ใช้ดำเนินการกับพวกเขาเมื่อพวกเขาอยู่ในขั้นตอนการลบ (อาจเป็นการกระทำของผู้ใช้ในฐานะ ดี).

ฉันได้ทำตัวอย่างเล็กน้อยในการตั้งค่าภาพพื้นหลัง (บางอย่างพื้นฐาน)


1
ฉันตัดสินใจที่จะให้รางวัลแก่คุณเนื่องจากนั่นเป็นวิธีแก้ปัญหาที่ฉันใช้ ขอบคุณมากที่สละเวลา!
Delyan

17
ว้าวสกปรกมาก นักพัฒนา Android ต้องใช้เวลานานแค่ไหนเพื่อความลื่นไหล
Dean Wild

1
ฉันมีแท็บ Viewpager จากที่สองฉันกำลังแทนที่ส่วนอื่น ๆ และเมื่อฉันกดกลับไปฉันต้องการแสดงแท็บที่สองของ viewpager มันกำลังเปิดขึ้น แต่มันกำลังแสดงหน้าว่าง ฉันลองทำตามที่คุณแนะนำในหัวข้อด้านบนแล้ว แต่ก็ยังเหมือนเดิม
Harish

ปัญหายังคงอยู่เมื่อผู้ใช้กลับมาตามที่ @Harish เขียน
Ewoks

1
ว้าวอย่างจริงจัง? มันเป็นปี 2018 และยังคงเป็นสิ่งที่? :(
Archie G. Quiñones

69

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

มีBaseFragmentคลาสที่ขยายFragmentและทำให้ชิ้นส่วนทั้งหมดของคุณขยายชั้นเรียนนั้น (ไม่ใช่แค่เศษชิ้นส่วนย่อย)

ในBaseFragmentชั้นเรียนนั้นให้เพิ่มสิ่งต่อไปนี้:

// Arbitrary value; set it to some reasonable default
private static final int DEFAULT_CHILD_ANIMATION_DURATION = 250;

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    final Fragment parent = getParentFragment();

    // Apply the workaround only if this is a child fragment, and the parent
    // is being removed.
    if (!enter && parent != null && parent.isRemoving()) {
        // This is a workaround for the bug where child fragments disappear when
        // the parent is removed (as all children are first removed from the parent)
        // See https://code.google.com/p/android/issues/detail?id=55228
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else {
        return super.onCreateAnimation(transit, enter, nextAnim);
    }
}

private static long getNextAnimationDuration(Fragment fragment, long defValue) {
    try {
        // Attempt to get the resource ID of the next animation that
        // will be applied to the given fragment.
        Field nextAnimField = Fragment.class.getDeclaredField("mNextAnim");
        nextAnimField.setAccessible(true);
        int nextAnimResource = nextAnimField.getInt(fragment);
        Animation nextAnim = AnimationUtils.loadAnimation(fragment.getActivity(), nextAnimResource);

        // ...and if it can be loaded, return that animation's duration
        return (nextAnim == null) ? defValue : nextAnim.getDuration();
    } catch (NoSuchFieldException|IllegalAccessException|Resources.NotFoundException ex) {
        Log.w(TAG, "Unable to load next animation from parent.", ex);
        return defValue;
    }
}

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

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


5
นี่เป็นวิธีแก้ปัญหาที่ฉันชอบที่สุดในกระทู้ ไม่ต้องใช้บิตแมปไม่ต้องใช้รหัสพิเศษใด ๆ ในส่วนย่อยและไม่รั่วไหลข้อมูลจากผู้ปกครองไปยังส่วนย่อย
jacobhyphenated

1
@EugenPechanec คุณต้องการ nextAnim จากส่วนหลัก - ไม่ใช่จากเด็ก นั่นคือประเด็นทั้งหมด
Kevin Coppock

1
น่าเสียดายที่วิธีนี้ทำให้เกิดการรั่วไหลของหน่วยความจำสำหรับชิ้นส่วนย่อยและโดยปริยายสำหรับชิ้นส่วนหลักเช่นกันใน Android เวอร์ชันด้านล่าง Lollipop :(
Cosmin

8
ขอบคุณสำหรับโซลูชันที่มีประโยชน์นี้ แต่ต้องมีการอัปเดตเล็กน้อยเพื่อให้ทำงานกับไลบรารีการสนับสนุนปัจจุบัน (27.0.2 ไม่ทราบว่าเวอร์ชันใดทำลายรหัสนี้) mNextAnimตอนนี้อยู่ในmAnimationInfoวัตถุ คุณสามารถเข้าถึงได้ดังนี้Field animInfoField = Fragment.class.getDeclaredField("mAnimationInfo"); animInfoField.setAccessible(true); Object animationInfo = animInfoField.get(fragment); Field nextAnimField = animationInfo.getClass().getDeclaredField("mNextAnim");
David Lericolais

5
@DavidLericolais ต้องการเพิ่มอีกบรรทัดของรหัสหลังจากของคุณ val nextAnimResource = nextAnimField.getInt(animationInfo);เพื่อแทนที่เส้นint nextAnimResource = nextAnimField.getInt(fragment);
tingyik90

32

ฉันสามารถหาวิธีแก้ปัญหาที่ค่อนข้างสะอาด IMO เป็นวิธีที่แฮ็กน้อยที่สุดและในทางเทคนิคแล้วโซลูชัน "วาดบิตแมป" อย่างน้อยก็มีการแยกส่วนโดย lib

ตรวจสอบให้แน่ใจว่าลูกของคุณ frags แทนที่คลาสพาเรนต์ด้วยสิ่งนี้:

private static final Animation dummyAnimation = new AlphaAnimation(1,1);
static{
    dummyAnimation.setDuration(500);
}

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if(!enter && getParentFragment() != null){
        return dummyAnimation;
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
}

หากเรามีอนิเมชั่นการออกจากส่วนย่อยของเด็กพวกเขาจะเคลื่อนไหวแทนที่จะกะพริบตา เราสามารถใช้ประโยชน์จากสิ่งนี้ได้โดยการมีแอนิเมชั่นที่ดึงเศษชิ้นส่วนย่อยที่อัลฟาเต็มเป็นระยะเวลาหนึ่ง ด้วยวิธีนี้พวกเขาจะยังคงมองเห็นได้ในส่วนหลักในขณะที่เคลื่อนไหวทำให้มีพฤติกรรมที่ต้องการ

ปัญหาเดียวที่ฉันคิดได้คือการติดตามระยะเวลานั้น ฉันอาจตั้งค่าเป็นตัวเลขขนาดใหญ่ แต่ฉันเกรงว่าอาจมีปัญหาด้านประสิทธิภาพหากยังวาดภาพเคลื่อนไหวอยู่ที่ไหนสักแห่ง


ช่วยได้ขอบคุณ มูลค่าของระยะเวลาไม่สำคัญ
iscariot

ทางออกที่สะอาดที่สุด
Liran Cohen

16

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

getChildFragmentManager().beginTransaction()
        .setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none)
        .add(R.id.container, nestedFragment)
        .commit();

xml สำหรับ R.anim.none (เวลาในการเข้า / ออกของผู้ปกครองของฉันคือ 250ms)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="0" android:duration="250" />
</set>

ฉันได้ทำสิ่งที่คล้ายกันมาก แต่ใช้ "แสดง" มากกว่า "เพิ่ม" เมื่ออัปเดตเด็ก ฉันยังเพิ่ม "getChildFragmentManager (). executePendingTransactions ()" แม้ว่าฉันจะไม่แน่ใจว่าจำเป็นอย่างยิ่งหรือไม่ แม้ว่าโซลูชันนี้จะทำงานได้ดีและไม่จำเป็นต้อง "ให้ภาพ" ของ Fragment เหมือนคำแนะนำบางอย่าง
Brian Yencho

มันเยี่ยมมาก อย่างไรก็ตามฉันได้รับความล่าช้าในการเปลี่ยนชิ้นส่วนลูกของฉัน เพื่อหลีกเลี่ยงปัญหานี้เพียงตั้งค่าพารามิเตอร์ที่ 2 โดยไม่มีแอนิเมชั่น:fragmentTransaction.setCustomAnimations(R.anim.none, 0, R.anim.none, R.anim.none)
ono

7

ฉันเข้าใจว่าสิ่งนี้อาจไม่สามารถแก้ปัญหาของคุณได้อย่างสมบูรณ์ แต่อาจจะตรงกับความต้องการของคนอื่นคุณสามารถเพิ่มenter/ exitและpopEnter/popExitภาพเคลื่อนไหวเพื่อลูก ๆ ของคุณFragments ที่ทำไม่ได้จริงย้าย / ภาพเคลื่อนไหวFragments ตราบใดที่แอนิเมชั่นมีระยะเวลา / ออฟเซ็ตเดียวกันกับFragmentแอนิเมชันระดับบนสุดภาพเคลื่อนไหวเหล่านั้นจะดูเหมือนเคลื่อนไหว / เคลื่อนไหวไปพร้อมกับแอนิเมชันของผู้ปกครอง


1
ฉันได้มอบรางวัลให้กับ Luksprog เนื่องจากโซลูชันของเขาทำงานได้ในระดับสากล ฉันได้ลองใช้เคล็ดลับการเคลื่อนไหวแบบคงที่ (ระยะเวลาไม่สำคัญ - เมื่อผู้ปกครองหายไปมุมมองจะหายไปอย่างชัดเจน) แต่ไม่ได้ผลในทุกกรณี (ดูความคิดเห็นของฉันใต้คำถาม) นอกจากนี้วิธีการนี้ยังรั่วไหลของนามธรรมเนื่องจากผู้ปกครองของส่วนที่มีเด็กจำเป็นต้องรู้เกี่ยวกับข้อเท็จจริงนั้นและดำเนินการเพิ่มเติมเพื่อตั้งค่าภาพเคลื่อนไหวของเด็ก ๆ ไม่ว่าในกรณีใดขอบคุณมากที่สละเวลา!
Delyan

เห็นด้วยนี่เป็นวิธีแก้ปัญหามากกว่าวิธีการกันน้ำและถือได้ว่าเปราะบางเล็กน้อย แต่จะใช้ได้กับกรณีง่ายๆ
Steven Byle

4

คุณสามารถทำได้ในส่วนย่อยของลูก

@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
    if (true) {//condition
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(getView(), "alpha", 1, 1);
        objectAnimator.setDuration(333);//time same with parent fragment's animation
        return objectAnimator;
    }
    return super.onCreateAnimator(transit, enter, nextAnim);
}

ขอบคุณ! อาจไม่ใช่วิธีที่ดีที่สุด แต่อาจเป็นวิธีแก้ปัญหาที่ง่ายที่สุด
bug56

2

@@@@@@@@@@@@@@@@@@@@@@@@@@@

แก้ไข: ฉันลงเอยด้วยการยกเลิกการใช้โซลูชันนี้เนื่องจากมีปัญหาอื่น ๆ ที่เกิดขึ้น Square เพิ่งออกมาพร้อมกับ 2 ไลบรารีที่มาแทนที่แฟรกเมนต์ ฉันจะบอกว่านี่อาจเป็นทางเลือกที่ดีกว่าการพยายามแฮ็กชิ้นส่วนเพื่อทำบางสิ่งที่ Google ไม่ต้องการให้ทำ

http://corner.squareup.com/2014/01/mortar-and-flow.html

@@@@@@@@@@@@@@@@@@@@@@@@@@@

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

เนื่องจากฉันใช้ Child Fragments ทั่วทุกที่ฉันจึงได้พบกับโซลูชันนี้ที่มีประสิทธิภาพและเป็นโมดูลาร์ (ดังนั้นจึงสามารถลบออกได้อย่างง่ายดาย) ในกรณีที่พวกเขาเคยแก้ไขมันและไม่จำเป็นต้องใช้แอนิเมชั่นที่ไม่ต้องใช้อีกต่อไป

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

public class ChildFragmentAnimationManager {

private static ChildFragmentAnimationManager instance = null;

private Map<Fragment, List<Fragment>> fragmentMap;

private ChildFragmentAnimationManager() {
    fragmentMap = new HashMap<Fragment, List<Fragment>>();
}

public static ChildFragmentAnimationManager instance() {
    if (instance == null) {
        instance = new ChildFragmentAnimationManager();
    }
    return instance;
}

public FragmentTransaction animate(FragmentTransaction ft, Fragment parent) {
    List<Fragment> children = getChildren(parent);

    ft.setCustomAnimations(R.anim.no_anim, R.anim.no_anim, R.anim.no_anim, R.anim.no_anim);
    for (Fragment child : children) {
        ft.remove(child);
    }

    return ft;
}

public void putChild(Fragment parent, Fragment child) {
    List<Fragment> children = getChildren(parent);
    children.add(child);
}

public void removeChild(Fragment parent, Fragment child) {
    List<Fragment> children = getChildren(parent);
    children.remove(child);
}

private List<Fragment> getChildren(Fragment parent) {
    List<Fragment> children;

    if ( fragmentMap.containsKey(parent) ) {
        children = fragmentMap.get(parent);
    } else {
        children = new ArrayList<Fragment>(3);
        fragmentMap.put(parent, children);
    }

    return children;
}

}

ถัดไปคุณต้องมีคลาสที่ขยาย Fragment ซึ่ง Fragment ทั้งหมดของคุณจะขยายออกไป (อย่างน้อย Child Fragments ของคุณ) ฉันมีคลาสนี้อยู่แล้วและฉันเรียกมันว่า BaseFragment เมื่อสร้างมุมมองแฟรกเมนต์เราจะเพิ่มเข้าใน ChildFragmentAnimationManager และลบออกเมื่อถูกทำลาย คุณสามารถทำได้ onAttach / Detach หรือวิธีการจับคู่อื่น ๆ ในลำดับ เหตุผลของฉันในการเลือก Create / Destroy View เป็นเพราะถ้า Fragment ไม่มี View ฉันไม่สนใจที่จะสร้างภาพเคลื่อนไหวเพื่อให้เห็นต่อไป วิธีนี้ควรทำงานได้ดีขึ้นกับ ViewPagers ที่ใช้ Fragments เนื่องจากคุณจะไม่ติดตามทุก Fragment ที่ FragmentPagerAdapter ถืออยู่ แต่จะมีเพียง 3 เท่านั้น

public abstract class BaseFragment extends Fragment {

@Override
public  View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    Fragment parent = getParentFragment();
    if (parent != null) {
        ChildFragmentAnimationManager.instance().putChild(parent, this);
    }

    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onDestroyView() {
    Fragment parent = getParentFragment();
    if (parent != null) {
        ChildFragmentAnimationManager.instance().removeChild(parent, this);
    }

    super.onDestroyView();
}

}

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

FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ChildFragmentAnimationManager.instance().animate(ft, ReaderFragment.this)
                    .setCustomAnimations(R.anim.up_in, R.anim.up_out, R.anim.down_in, R.anim.down_out)
                    .replace(R.id.container, f)
                    .addToBackStack(null)
                    .commit();

นอกจากนี้คุณก็มีแล้วนี่คือไฟล์ no_anim.xml ที่อยู่ในโฟลเดอร์ res / anim ของคุณ:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/linear_interpolator">
    <translate android:fromXDelta="0" android:toXDelta="0"
        android:duration="1000" />
</set>

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


1

ฉันคิดว่าฉันพบวิธีแก้ปัญหาที่ดีกว่าการสแนปช็อตส่วนปัจจุบันเป็นบิตแมปตามที่ Luksprog แนะนำ

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

ลองนึกภาพว่าเรามีFragmentAและFragmentBทั้งสองมีเศษย่อย ตอนนี้เมื่อคุณทำตามปกติ:

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .add(R.id.fragmentHolder, new FragmentB())
  .remove(fragmentA)    <-------------------------------------------
  .addToBackStack(null)
  .commit()

แต่คุณทำ

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .add(R.id.fragmentHolder, new FragmentB())
  .hide(fragmentA)    <---------------------------------------------
  .addToBackStack(null)
  .commit()

fragmentA.removeMe = true;

ตอนนี้สำหรับการใช้งาน Fragment:

public class BaseFragment extends Fragment {

    protected Boolean detachMe = false;
    protected Boolean removeMe = false;

    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        if (nextAnim == 0) {
            if (!enter) {
                onExit();
            }

            return null;
        }

        Animation animation = AnimationUtils.loadAnimation(getActivity(), nextAnim);
        assert animation != null;

        if (!enter) {
            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    onExit();
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                }
            });
        }

        return animation;
    }

    private void onExit() {
        if (!detachMe && !removeMe) {
            return;
        }

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        if (detachMe) {
            fragmentTransaction.detach(this);
            detachMe = false;
        } else if (removeMe) {
            fragmentTransaction.remove(this);
            removeMe = false;
        }
        fragmentTransaction.commit();
    }
}

popBackStack จะไม่ทำให้เกิดข้อผิดพลาดเนื่องจากพยายามแสดง Fragment ที่ถูกแยกออกหรือไม่?
Alexandre

1

ฉันมีปัญหาเดียวกันกับส่วนแผนที่ มันยังคงหายไปในระหว่างอนิเมชั่นการออกจากส่วนที่มี วิธีแก้ปัญหาคือการเพิ่มภาพเคลื่อนไหวสำหรับส่วนแผนที่ลูกซึ่งจะทำให้มองเห็นได้ในระหว่างการออกจากภาพเคลื่อนไหวของส่วนหลัก ภาพเคลื่อนไหวของชิ้นส่วนย่อยจะรักษาอัลฟ่าไว้ที่ 100% ในช่วงระยะเวลา

ภาพเคลื่อนไหว: res / animator / keep_child_fragment.xml

<?xml version="1.0" encoding="utf-8"?>    
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:propertyName="alpha"
        android:valueFrom="1.0"
        android:valueTo="1.0"
        android:duration="@integer/keep_child_fragment_animation_duration" />
</set>

จากนั้นภาพเคลื่อนไหวจะถูกนำไปใช้เมื่อมีการเพิ่มส่วนแผนที่ลงในส่วนหลัก

ส่วนหลัก

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.map_parent_fragment, container, false);

    MapFragment mapFragment =  MapFragment.newInstance();

    getChildFragmentManager().beginTransaction()
            .setCustomAnimations(R.animator.keep_child_fragment, 0, 0, 0)
            .add(R.id.map, mapFragment)
            .commit();

    return view;
}

สุดท้ายระยะเวลาของแอนิเมชั่นแฟรกเมนต์ลูกถูกตั้งค่าในไฟล์ทรัพยากร

ค่า / integers.xml

<resources>
  <integer name="keep_child_fragment_animation_duration">500</integer>
</resources>

0

ในการทำให้ภาพเคลื่อนไหวของชิ้นส่วน neasted แตกออกเราสามารถบังคับให้ pop back stack บน ChildFragmentManager การดำเนินการนี้จะเริ่มการทำงานของภาพเคลื่อนไหวการเปลี่ยนแปลง ในการดำเนินการนี้เราจำเป็นต้องติดตามเหตุการณ์ OnBackButtonPressed หรือฟังการเปลี่ยนแปลงแบ็คสแต็ค

นี่คือตัวอย่างที่มีรหัส

View.OnClickListener() {//this is from custom button but you can listen for back button pressed
            @Override
            public void onClick(View v) {
                getChildFragmentManager().popBackStack();
                //and here we can manage other fragment operations 
            }
        });

  Fragment fr = MyNeastedFragment.newInstance(product);

  getChildFragmentManager()
          .beginTransaction()
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
                .replace(R.neasted_fragment_container, fr)
                .addToBackStack("Neasted Fragment")
                .commit();

0

เมื่อเร็ว ๆ นี้ฉันพบปัญหานี้ในคำถามของฉัน: ส่วนที่ซ้อนกันเปลี่ยนไม่ถูกต้อง

ฉันมีวิธีแก้ปัญหานี้โดยไม่ต้องบันทึกบิตแมปหรือใช้การสะท้อนกลับหรือวิธีการอื่นใดที่ไม่น่าพอใจ

สามารถดูตัวอย่างโครงการได้ที่นี่: https://github.com/zafrani/NestedFragmentTransitions

สามารถดู GIF ของเอฟเฟกต์ได้ที่นี่: https://imgur.com/94AvrW4

ในตัวอย่างของฉันมีชิ้นส่วนเด็ก 6 ชิ้นแบ่งระหว่างชิ้นส่วนหลักสองชิ้น ฉันสามารถบรรลุการเปลี่ยนสำหรับเข้าออกป๊อปและดันได้โดยไม่มีปัญหาใด ๆ การเปลี่ยนแปลงการกำหนดค่าและการกดย้อนกลับได้รับการจัดการเรียบร้อยแล้ว

โซลูชันจำนวนมากอยู่ใน BaseFragment ของฉัน (ส่วนที่ขยายโดยลูก ๆ และส่วนหลักของฉัน) ฟังก์ชัน onCreateAnimator ซึ่งมีลักษณะดังนี้:

   override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator {
    if (isConfigChange) {
        resetStates()
        return nothingAnim()
    }

    if (parentFragment is ParentFragment) {
        if ((parentFragment as BaseFragment).isPopping) {
            return nothingAnim()
        }
    }

    if (parentFragment != null && parentFragment.isRemoving) {
        return nothingAnim()
    }

    if (enter) {
        if (isPopping) {
            resetStates()
            return pushAnim()
        }
        if (isSuppressing) {
            resetStates()
            return nothingAnim()
        }
        return enterAnim()
    }

    if (isPopping) {
        resetStates()
        return popAnim()
    }

    if (isSuppressing) {
        resetStates()
        return nothingAnim()
    }

    return exitAnim()
}

กิจกรรมและส่วนพาเรนต์มีหน้าที่ในการตั้งค่าสถานะของบูลีนเหล่านี้ ง่ายกว่าในการดูวิธีการและสถานที่จากโครงการตัวอย่างของฉัน

ฉันไม่ได้ใช้ส่วนสนับสนุนในตัวอย่างของฉัน แต่ตรรกะเดียวกันนี้สามารถใช้กับพวกมันและฟังก์ชัน onCreateAnimation ได้


0

วิธีง่ายๆในการแก้ไขปัญหานี้คือใช้Fragmentคลาสจากไลบรารีนี้แทนคลาสแฟรกเมนต์ไลบรารีมาตรฐาน:

https://github.com/marksalpeter/contract-fragment

ตามหมายเหตุด้านข้างแพคเกจยังมีรูปแบบการมอบสิทธิ์ที่เป็นประโยชน์ContractFragmentซึ่งคุณอาจพบว่ามีประโยชน์สำหรับการสร้างแอปของคุณโดยใช้ประโยชน์จากความสัมพันธ์ส่วนแม่ลูก


0

จากคำตอบข้างต้นของ @kcoppock

หากคุณมี Activity-> Fragment-> Fragments (การเรียงซ้อนหลาย ๆ ตัวช่วยต่อไปนี้) การแก้ไขเล็กน้อยสำหรับคำตอบที่ดีที่สุดของ IMHO

public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {

    final Fragment parent = getParentFragment();

    Fragment parentOfParent = null;

    if( parent!=null ) {
        parentOfParent = parent.getParentFragment();
    }

    if( !enter && parent != null && parentOfParent!=null && parentOfParent.isRemoving()){
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else
    if (!enter && parent != null && parent.isRemoving()) {
        // This is a workaround for the bug where child fragments disappear when
        // the parent is removed (as all children are first removed from the parent)
        // See https://code.google.com/p/android/issues/detail?id=55228
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else {
        return super.onCreateAnimation(transit, enter, nextAnim);
    }
}

0

ปัญหาของฉันอยู่ที่การลบส่วนพาเรนต์ (ft.remove (fragment)) ไม่มีการเคลื่อนไหวของเด็ก

ปัญหาพื้นฐานคือชิ้นส่วนย่อยของเด็กจะถูกทำลายทันทีก่อนที่ส่วนของผู้ปกครองจะออกจากภาพเคลื่อนไหว

อนิเมชั่นแบบกำหนดเองของ Child Fragment จะไม่ถูกเรียกใช้งานในการลบ Parent Fragment

ในขณะที่คนอื่น ๆ หลีกเลี่ยงการซ่อน PARENT (ไม่ใช่เด็ก) ก่อนที่จะลบ PARENT เป็นวิธีที่จะไป

            val ft = fragmentManager?.beginTransaction()
            ft?.setCustomAnimations(R.anim.enter_from_right,
                    R.anim.exit_to_right)
            if (parentFragment.isHidden()) {
                ft?.show(vehicleModule)
            } else {
                ft?.hide(vehicleModule)
            }
            ft?.commit()

หากคุณต้องการลบพาเรนต์จริงๆคุณควรตั้งค่า Listener เกี่ยวกับแอนิเมชันแบบกำหนดเองของคุณเพื่อให้ทราบว่าเมื่อแอนิเมชันสิ้นสุดลงดังนั้นคุณสามารถทำการสรุปในส่วนหลัก (ลบ) ได้อย่างปลอดภัย หากคุณไม่ทำในเวลาที่เหมาะสมคุณอาจจบลงด้วยการฆ่าภาพเคลื่อนไหว แอนิเมชั่น NB ทำในคิวแบบอะซิงโครนัสของตัวเอง

BTW คุณไม่ต้องการภาพเคลื่อนไหวที่กำหนดเองในส่วนย่อยเนื่องจากจะสืบทอดภาพเคลื่อนไหวระดับบนสุด



0

กระทู้เก่า แต่ในกรณีที่มีคนสะดุดที่นี่:

วิธีการทั้งหมดข้างต้นรู้สึกไม่น่าสนใจสำหรับฉันมากโซลูชันบิตแมปสกปรกมากและไม่มีประสิทธิภาพ ส่วนอื่น ๆ ต้องการให้ส่วนย่อยทราบเกี่ยวกับระยะเวลาของการเปลี่ยนแปลงที่ใช้ในธุรกรรมที่ใช้สร้างส่วนย่อยที่เป็นปัญหา ทางออกที่ดีกว่าในสายตาของฉันฉันมีดังนี้:

val currentFragment = supportFragmentManager.findFragmentByTag(TAG)
val transaction = supportFragmentManager
    .beginTransaction()
    .setCustomAnimations(anim1, anim2, anim1, anim2)
    .add(R.id.fragmentHolder, FragmentB(), TAG)
if (currentFragment != null) {
    transaction.hide(currentFragment).commit()
    Handler().postDelayed({
        supportFragmentManager.beginTransaction().remove(currentFragment).commit()
    }, DURATION_OF_ANIM)
} else {
    transaction.commit()
}

เราเพียงแค่ซ่อนส่วนปัจจุบันและเพิ่มส่วนใหม่เมื่อภาพเคลื่อนไหวเสร็จสิ้นเราจะลบส่วนเก่าออก วิธีนี้จัดการได้ในที่เดียวและไม่มีการสร้างบิตแมป

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