Android 4.2: พฤติกรรมแบ็คสแต็กที่มีส่วนที่ซ้อนกัน


108

พร้อมกับ Android 4.2 สนับสนุนห้องสมุดสนับสนุนได้สำหรับชิ้นส่วนที่ซ้อนกันดูที่นี่ ผมเคยเล่นรอบกับมันและพบพฤติกรรมที่น่าสนใจ / ข้อผิดพลาดเกี่ยวกับสแต็คกลับมาและgetChildFragmentManager () เมื่อใช้ getChildFragmentManager () และ addToBackStack (ชื่อสตริง) โดยการกดปุ่มย้อนกลับระบบจะไม่เรียกใช้ back stack ไปยังส่วนก่อนหน้า ในทางกลับกันเมื่อใช้getFragmentManager ()และ addToBackStack (ชื่อสตริง) โดยการกดปุ่มย้อนกลับระบบจะกลับไปที่แฟรกเมนต์ก่อนหน้า

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

พฤติกรรมนี้ถูกต้องหรือไม่? พฤติกรรมนี้เป็นจุดบกพร่องหรือไม่? มีวิธีแก้ไขปัญหานี้หรือไม่?

โค้ดตัวอย่างด้วย getChildFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

โค้ดตัวอย่างด้วย getFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

อืมน่าสนใจมาก ฉันไม่คาดหวังพฤติกรรมนั้นเช่นกัน หากเป็นกรณีนี้ดูเหมือนว่าเราอาจจะแทนที่ OnBackPressed และในวิธีการนั้นจะได้รับส่วนย่อยที่มีชิ้นส่วนย่อยจากนั้นรับ ChildFragmentManager เพื่อดำเนินธุรกรรมป๊อป
Marco RS

@Marco แน่นอน แต่อย่างไรก็ตามมันเป็นวิธีแก้ปัญหาชั่วคราว เหตุใด API จึงไม่ทำงานอย่างถูกต้อง
Egor

คำตอบ:


67

โซลูชันนี้อาจเป็นคำตอบ @Sean เวอร์ชันที่ดีกว่า:

@Override
public void onBackPressed() {
    // if there is a fragment and the back stack of this fragment is not empty,
    // then emulate 'onBackPressed' behaviour, because in default, it is not working
    FragmentManager fm = getSupportFragmentManager();
    for (Fragment frag : fm.getFragments()) {
        if (frag.isVisible()) {
            FragmentManager childFm = frag.getChildFragmentManager();
            if (childFm.getBackStackEntryCount() > 0) {
                childFm.popBackStack();
                return;
            }
        }
    }
    super.onBackPressed();
}

อีกครั้งฉันเตรียมโซลูชันนี้ตามคำตอบของ @Sean ด้านบน

ดังที่ @ AZ13 กล่าวว่าโซลูชันนี้เป็นไปได้ในสถานการณ์ชิ้นส่วนย่อยระดับหนึ่งเท่านั้น ในกรณีส่วนย่อยหลายระดับงานมีความซับซ้อนเล็กน้อยดังนั้นฉันขอแนะนำให้ลองใช้วิธีนี้เฉพาะกรณีที่เป็นไปได้ที่ฉันได้กล่าวไว้ =)

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


2
林奕忠คำตอบควรใช้แทนคำตอบนี้ - มันจัดการชิ้นส่วนที่ซ้อนกันได้อย่างถูกต้อง
Hameno

รหัสใช้งานได้จริงหรือ? แม้แต่ getFragments ไม่ใช่วิธีสาธารณะ? stackoverflow.com/a/42572412/4729203
wonsuc

ใช้งานได้ครั้งเดียว แต่อาจไม่ทำงานในขณะนี้หาก getFragments ไม่ใช่สาธารณะอีกต่อไป
ismailarilik

1
getFragmentsเป็นสาธารณะแล้ว
grrigore

สิ่งนี้จะทำงานไม่ถูกต้องหากคุณมี FragA -> Frag B -> Frag C ในเพจเจอร์ ...
Zhar

57

ดูเหมือนบั๊ก ดูที่: http://code.google.com/p/android/issues/detail?id=40323

สำหรับวิธีแก้ปัญหาที่ฉันใช้สำเร็จแล้ว (ตามที่แนะนำในความคิดเห็น):

    @Override
public void onBackPressed() {

    // If the fragment exists and has some back-stack entry
    if (mActivityDirectFragment != null && mActivityDirectFragment.getChildFragmentManager().getBackStackEntryCount() > 0){
        // Get the fragment fragment manager - and pop the backstack
        mActivityDirectFragment.getChildFragmentManager().popBackStack();
    }
    // Else, nothing in the direct fragment back stack
    else{
        // Let super handle the back press
        super.onBackPressed();          
    }
}

12
น่าเสียดายที่นี่ไม่ใช่วิธีแก้ปัญหาที่เหมาะสมและเป็นเพียงวิธีแก้ปัญหาสำหรับกรณีที่ง่ายที่สุด สมมติว่าคุณมีแฟรกเมนต์ซึ่งมีแฟรกเมนต์อื่นซึ่งมีแฟรกเมนต์อื่นและอื่น ๆ จากนั้นคุณจะต้องเผยแพร่การเรียกไปยังส่วนสุดท้ายซึ่งมีส่วนย่อยอื่น: /
AZ13

@MuhammadBabar ไม่คุณจะไม่ หากเป็นโมฆะส่วนที่สองของคำสั่งจะไม่ได้รับการประเมินเนื่องจากมีการระบุว่าเป็นเท็จแล้ว ตัวถูกดำเนินการคือ & ไม่ใช่ | .
Sean

26

คำตอบที่แท้จริงสำหรับคำถามนี้อยู่ในฟังก์ชันของ Fragment Transaction ที่เรียกว่า setPrimaryNavigationFragment

/**
 * Set a currently active fragment in this FragmentManager as the primary navigation fragment.
 *
 * <p>The primary navigation fragment's
 * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
 * to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
 * if no ID or transaction name is provided to pop to. Navigation operations outside of the
 * fragment system may choose to delegate those actions to the primary navigation fragment
 * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
 *
 * <p>The fragment provided must currently be added to the FragmentManager to be set as
 * a primary navigation fragment, or previously added as part of this transaction.</p>
 *
 * @param fragment the fragment to set as the primary navigation fragment
 * @return the same FragmentTransaction instance
 */
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);

คุณต้องตั้งค่าฟังก์ชันนี้บนแฟรกเมนต์พาเรนต์เริ่มต้นเมื่อมีการเพิ่มกิจกรรม ฉันมีฟังก์ชัน replaceFragment ภายในกิจกรรมของฉันที่มีลักษณะดังนี้:

public void replaceFragment(int containerId, BaseFragment fragment, boolean addToBackstack) {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setPrimaryNavigationFragment(fragment);
    if (addToBackstack) {
        fragmentTransaction.addToBackStack(fragment.TAG);
    }

    fragmentTransaction.replace(containerId, fragment).commit();
}

สิ่งนี้ทำให้คุณมีพฤติกรรมเช่นเดียวกับการคลิกของคุณกลับจาก Fragment B ปกติกลับไปที่ Fragment A ยกเว้นตอนนี้มันอยู่ในชิ้นส่วนย่อยเช่นกัน!


3
พบมาก! วิธีนี้ช่วยแก้ปัญหาที่ OP กล่าวถึงด้วยวิธีที่สะอาดกว่าและแฮ็กน้อยกว่า
โจนา

นั่นคือการหยุดทำงานเว้นแต่เป็นส่วนหนึ่งของไลบรารีการนำทางเป็น HavHostFragment
Didi

22

โซลูชันนี้อาจเป็นคำตอบ @ismailarilik เวอร์ชันที่ดีกว่า:

เวอร์ชัน Nested Fragment

private boolean onBackPressed(FragmentManager fm) {
    if (fm != null) {
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
            return true;
        }

        List<Fragment> fragList = fm.getFragments();
        if (fragList != null && fragList.size() > 0) {
            for (Fragment frag : fragList) {
                if (frag == null) {
                    continue;
                }
                if (frag.isVisible()) {
                    if (onBackPressed(frag.getChildFragmentManager())) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

@Override
public void onBackPressed() {
    FragmentManager fm = getSupportFragmentManager();
    if (onBackPressed(fm)) {
        return;
    }
    super.onBackPressed();
}

ดูเหมือนว่าจะไม่ทำงานเมื่อมีชิ้นส่วนที่ซ้อนกันหลายระดับ
splinter123

คลาวด์คุณให้สถาปัตยกรรมของชิ้นส่วนที่ซ้อนกันมาให้ฉันไหม ในแอพของฉันมันใช้งานได้ บางทีฉันอาจจะพลาดอะไรบางอย่าง
林奕忠

9
คำสั่งในการเปิดกองหลังไม่เป็นไปตามที่ฉันคาดไว้ในตัวอย่างนี้ ฉันเชื่อว่าคุณควรหาชิ้นส่วนที่มองเห็นได้ที่ซ้อนกันอยู่ลึกที่สุดในต้นไม้ก่อนและพยายามที่จะเปิดแบ็คสแต็คจากนั้นเลื่อนต้นไม้ขึ้นเรื่อย ๆ เพื่อค้นหาชิ้นส่วนที่มองเห็นได้อื่น ๆ ซึ่งคุณสามารถวางแบ็คสแต็คได้จนกว่าคุณจะได้มา การแก้ไขด่วนที่จัดการกับกรณีที่ง่ายที่สุดคือการเปลี่ยนฟังก์ชัน onBackPressed (FragmentManager fm) และสลับบล็อกที่ทำให้แบ็คสแตกด้วยบล็อกที่แจกแจงชิ้นส่วน วิธีการที่จะทำให้แบ็คสแต็คในเศษชิ้นส่วนย่อยที่ซ้อนกันลึกมากขึ้นจะเกิดขึ้นก่อน
ธีโอ

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

มันเป็นไปได้ที่จะใช้แก้ปัญหานี้ได้โดยไม่ต้องSupportFragmentManagerและแทนที่จะเพียงมาตรฐานFragmentManager?
Peter Chappy

13

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

เพิ่มสิ่งนี้ลงในกิจกรรมของคุณเพื่อขยาย AppCompatActivity

@Override
public void onBackPressed()
{
    if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
        super.onBackPressed();
    }
}

เพิ่มสิ่งนี้ลงใน BaseFragment ของคุณหรือคลาสที่คุณสามารถมีชิ้นส่วนทั้งหมดที่สืบทอดมาได้

public static boolean handleBackPressed(FragmentManager fm)
{
    if(fm.getFragments() != null){
        for(Fragment frag : fm.getFragments()){
            if(frag != null && frag.isVisible() && frag instanceof BaseFragment){
                if(((BaseFragment)frag).onBackPressed()){
                    return true;
                }
            }
        }
    }
    return false;
}

protected boolean onBackPressed()
{
    FragmentManager fm = getChildFragmentManager();
    if(handleBackPressed(fm)){
        return true;
    }
    else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
        fm.popBackStack();
        return true;
    }
    return false;
}

นี่เป็นคำตอบที่ดีทำงานบน API ระดับ 23/24 และรองรับ lib 24.1.1
Rinav

ใช้สิ่งนี้มาระยะหนึ่งแล้วเป็นวิธีแก้ปัญหาที่ยอดเยี่ยม แต่เมื่อเร็ว ๆ นี้มีการรวบรวมคำเตือนเกี่ยวกับการเข้าถึง getFragments () โดยตรงจากกลุ่มห้องสมุดภายนอก คุณได้อัปเดตวิธีแก้ไขแล้วหรือยัง?
Simon Huckett

ได้รับการแก้ไขแล้วหรือในปี 2020 เรายังคงพบปัญหาและควรใช้วิธีแก้ปัญหาของคุณ ?? !!
Zhar

3

รหัสนี้จะนำทางโครงสร้างของตัวจัดการแฟรกเมนต์และส่งคืนรายการสุดท้ายที่ถูกเพิ่มเข้ามาซึ่งมีส่วนที่สามารถแตกออกจากสแต็กได้:

private FragmentManager getLastFragmentManagerWithBack(FragmentManager fm)
{
  FragmentManager fmLast = fm;

  List<Fragment> fragments = fm.getFragments();

  for (Fragment f : fragments)
  {
    if ((f.getChildFragmentManager() != null) && (f.getChildFragmentManager().getBackStackEntryCount() > 0))
    {
      fmLast = f.getFragmentManager();
      FragmentManager fmChild = getLastFragmentManagerWithBack(f.getChildFragmentManager());

      if (fmChild != fmLast)
        fmLast = fmChild;
    }
  }

  return fmLast;
}

เรียกวิธีการ:

@Override
public void onBackPressed()
{
  FragmentManager fm = getLastFragmentManagerWithBack(getSupportFragmentManager());

  if (fm.getBackStackEntryCount() > 0)
  {
    fm.popBackStack();
    return;
  }

  super.onBackPressed();
}

สิ่งนี้ใช้ได้กับส่วนที่ซ้อนกันทั้งหมดหากลงทะเบียนอย่างถูกต้อง !! +100
Pratik Saluja

2

เหตุผลก็คือว่าบุคลากรกิจกรรมของคุณจาก FragmentActivity ซึ่งจัดการกดปุ่มย้อนกลับ (ดูเส้น 173 ของFragmentActivity

ในแอปพลิเคชันของเราฉันใช้ ViewPager (ที่มีส่วนย่อย) และแต่ละส่วนสามารถมีส่วนที่ซ้อนกันได้ วิธีที่ฉันจัดการคือ:

  • การกำหนดอินเทอร์เฟซ OnBackKeyPressedListener ด้วยวิธีการเดียว void onBackKeyPressed()
  • ใช้อินเทอร์เฟซนี้ในส่วน "ด้านบน" ที่ ViewPager แสดง
  • การลบล้าง onKeyDown และการตรวจจับการกด BACK และเรียก onBackKeyPressed ในแฟรกเมนต์ที่ใช้งานอยู่ในเพจเจอร์มุมมอง

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


2

หากคุณใช้แฟรกเมนต์ในแฟรกเมนต์ให้ใช้ getChildFragmentManager()

getChildFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

หากคุณใช้ชิ้นส่วนย่อยและแทนที่ด้วยการใช้ getParentFragmentManager()

getParentFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

หากทั้งสองไม่ได้ผลให้คุณลองทำเช่นนี้ getActivity().getSupportFragmentManager()

getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

1

ฉันสามารถจัดการแฟรกเมนต์แบ็คสแต็กได้โดยการเพิ่มแฟรกเมนต์พาเรนต์เมธอดนี้ที่เมธอด onCreate View () และส่งผ่านมุมมองรูท

private void catchBackEvent(View v){
    v.setFocusableInTouchMode(true);
    v.requestFocus();
    v.setOnKeyListener( new OnKeyListener()
    {
        @Override
        public boolean onKey( View v, int keyCode, KeyEvent event )
        {
            if( keyCode == KeyEvent.KEYCODE_BACK )
            {
                if(isEnableFragmentBackStack()){
                    getChildFragmentManager().popBackStack();
                                    setEnableFragmentBackStack(false);
                    return true;
                }
                else
                    return false;   
            }
            return false;
        }
    } );
}

เมธอด isEnableFragmentBackStack () เป็นแฟล็กบูลีนที่จะรู้เมื่อฉันอยู่บนแฟรกเมนต์หลักหรือแฟล็กถัดไป

ตรวจสอบให้แน่ใจว่าเมื่อคุณคอมมิตแฟรกเมนต์ที่ต้องมีสแต็กแล้วคุณต้องเพิ่มเมธอด addToBackstack


1

โซลูชันนี้อาจจะดีกว่าเนื่องจากจะตรวจสอบทุกระดับของชิ้นส่วนที่ซ้อนกัน:

 /**
 * This method will go check all the back stacks of the added fragments and their nested fragments
 * to the the {@code FragmentManager} passed as parameter.
 * If there is a fragment and the back stack of this fragment is not empty,
 * then emulate 'onBackPressed' behaviour, because in default, it is not working.
 *
 * @param fm the fragment manager to which we will try to dispatch the back pressed event.
 * @return {@code true} if the onBackPressed event was consumed by a child fragment, otherwise {@code false}.
 */
public static boolean dispatchOnBackPressedToFragments(FragmentManager fm) {

    List<Fragment> fragments = fm.getFragments();
    boolean result;
    if (fragments != null && !fragments.isEmpty()) {
        for (Fragment frag : fragments) {
            if (frag != null && frag.isAdded() && frag.getChildFragmentManager() != null) {
                // go to the next level of child fragments.
                result = dispatchOnBackPressedToFragments(frag.getChildFragmentManager());
                if (result) return true;
            }
        }
    }

    // if the back stack is not empty then we pop the last transaction.
    if (fm.getBackStackEntryCount() > 0) {
        fm.popBackStack();
        fm.executePendingTransactions();
        return true;
    }

    return false;
}

ในกิจกรรมของonBackPressedคุณคุณสามารถเรียกสิ่งนี้ว่า:

FragmentManager fm = getSupportFragmentManager();
                // if there is a fragment and the back stack of this fragment is not empty,
                // then emulate 'onBackPressed' behaviour, because in default, it is not working
                if (!dispatchOnBackPressedToFragments(fm)) {
                    // if no child fragment consumed the onBackPressed event,
                    // we execute the default behaviour.
                    super.onBackPressed();
                }

1

ขอบคุณทุกคนสำหรับความช่วยเหลือ (เวอร์ชันปรับแต่ง) นี้เหมาะสำหรับฉัน:

@Override
public void onBackPressed() {
    if (!recursivePopBackStack(getSupportFragmentManager())) {
        super.onBackPressed();
    }
}

/**
 * Recursively look through nested fragments for a backstack entry to pop
 * @return: true if a pop was performed
 */
public static boolean recursivePopBackStack(FragmentManager fragmentManager) {
    if (fragmentManager.getFragments() != null) {
        for (Fragment fragment : fragmentManager.getFragments()) {
            if (fragment != null && fragment.isVisible()) {
                boolean popped = recursivePopBackStack(fragment.getChildFragmentManager());
                if (popped) {
                    return true;
                }
            }
        }
    }

    if (fragmentManager.getBackStackEntryCount() > 0) {
        fragmentManager.popBackStack();
        return true;
    }

    return false;
}

หมายเหตุ: คุณอาจต้องการตั้งค่าสีพื้นหลังของชิ้นส่วนที่ซ้อนกันเหล่านี้เป็นสีพื้นหลังหน้าต่างของธีมของแอปเนื่องจากโดยค่าเริ่มต้นจะโปร่งใส ค่อนข้างอยู่นอกเหนือขอบเขตของคำถามนี้ แต่สามารถทำได้โดยการแก้ไขแอตทริบิวต์ android.R.attr.windowBackground และตั้งค่าพื้นหลังของมุมมอง Fragment เป็น ID ทรัพยากรนั้น


1

มากกว่า 5 ปีและปัญหานี้ยังคงมีความเกี่ยวข้อง หากคุณไม่ต้องการใช้ fragmentManager.getFragments () เนื่องจากข้อ จำกัด ขยายและใช้คลาสด้านล่าง:

NestedFragmentActivity.java

abstract public class NestedFragmentActivity extends AppCompatActivity {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    @Override
    public void onBackPressed() {
        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentTagStack.pop();
            mActiveFragmentIdStack.pop();

        } else {
            super.onBackPressed();
        }
    }

    public void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }
}

NestedFragment.java

abstract public class NestedFragment extends Fragment {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    private NestedFragmentActivity mParentActivity;
    private NestedFragment mParentFragment;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (getParentFragment() == null) {
            try {
                mParentActivity = (NestedFragmentActivity) context;
            } catch (ClassCastException e) {
                throw new ClassCastException(context.toString()
                        + " must implement " + NestedFragmentActivity.class.getName());
            }
        } else {
            try {
                mParentFragment = (NestedFragment) getParentFragment();
            } catch (ClassCastException e) {
                throw new ClassCastException(getParentFragment().getClass().toString()
                        + " must implement " + NestedFragment.class.getName());
            }
        }
    }

    public void onBackPressed() {

        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentIdStack.pop();
            mActiveFragmentTagStack.pop();

        } else {
            getChildFragmentManager().popBackStack();
        }
    }

    private void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }

    public void addToParentBackStack() {
        if (mParentFragment != null) {
            mParentFragment.addToBackStack(getId(), getTag());
        } else if (mParentActivity != null) {
            mParentActivity.addToBackStack(getId(), getTag());
        }
    }
}

คำอธิบาย:

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

เมื่อเพิ่ม backstack ใน childFragmentManager คุณจะต้องเรียก "addToParentBackStack ()" ด้วย เพื่อให้แน่ใจว่าแท็ก / id ของแฟรกเมนต์ถูกเพิ่มในแฟรกเมนต์ / กิจกรรมหลักสำหรับป๊อปในภายหลัง

ตัวอย่าง:

    getChildFragmentManager().beginTransaction().replace(
            R.id.fragment,
            fragment,
            fragment.getTag()
    ).addToBackStack(null).commit();
    addToParentBackStack();

1

ฉันได้ดำเนินการอย่างถูกต้องหากยังไม่พบคำตอบใด ๆ จนถึงตอนนี้

เพียงเพิ่มวิธีนี้ในส่วนย่อยที่ซ้อนกันของลูกของคุณ

   @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true ) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
            FragmentManager fm= getFragmentManager();
            if (fm != null) {
                if (fm.getBackStackEntryCount() > 0) {
                    fm.popBackStack();
                    Log.e( "Frag","back" );

                }

                List<Fragment> fragList = fm.getFragments();
                if (fragList != null && fragList.size() > 0) {
                    for (Fragment frag : fragList) {
                        if (frag == null) {
                            continue;
                        }
                        if (frag.isVisible()) {
                          Log.e( "Frag","Visible" );
                        }
                    }
                }
            }


        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
    super.onCreate( savedInstanceState );
}

0

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

 // INSIDE ACTIVITY
 override fun onBackPressed() {
    val fragment = currentFragment

    if(fragment != null && fragment.childFragmentManager.fragments.any()){
        fragment.childFragmentManager.popBackStack()
    }
    else {
        super.onBackPressed()
    }
}

นี่คือวิธีที่ฉันเพิ่มส่วนย่อยลงในส่วนที่เปิดอยู่ในปัจจุบันจากภายในตัวมันเอง

 // INSIDE FRAGMENT
 menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // to see change
 add_fragment_button.setOnClickListener {
        val transaction = childFragmentManager.beginTransaction()

        transaction.add(R.id.menu_fragment_fragment_holder, MenuFragment())

        transaction.addToBackStack(null)

        transaction.commit()
    }

นี่คือเลย์เอาต์ xml ของแฟรกเมนต์พาเรนต์และแฟรกเมนต์ที่เพิ่ม

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/menu_fragment_view">
     <Button
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@+id/add_fragment_button"
         android:text="Just add fragment"/>
     <FrameLayout
         android:id="@+id/menu_fragment_fragment_holder"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"/> 
</androidx.constraintlayout.widget.ConstraintLayout>

0

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

การกำหนดอินเทอร์เฟซ "ParentFragment":

interface ParentFragment {
/**
 * Fragments that host child fragments and want to control their BackStack behaviour when the back button is pressed should implement this
 *
 * @return true if back press was handled, false otherwise
 */
fun onBackPressed(): Boolean

}

การแทนที่ "onBackPressed" ในกิจกรรมหลัก (หรือใน BaseActivity):

override fun onBackPressed() {
    val fm: FragmentManager = supportFragmentManager
    for (frag in fm.fragments) {
        if (frag.isVisible && frag is ParentFragment && frag.onBackPressed()) {
            return
        }
    }
    super.onBackPressed()
}

จากนั้นอนุญาตให้ส่วนพาเรนต์จัดการตามที่ต้องการตัวอย่างเช่น:

override fun onBackPressed(): Boolean {
    val childFragment = childFragmentManager.findFragmentByTag(SomeChildFragment::class.java.simpleName)
    if (childFragment != null && childFragment.isVisible) {
        // Only for that case, pop the BackStack (perhaps when other child fragments are visible don't)
        childFragmentManager.popBackStack()
        return true
    }
    return false
}

สิ่งนี้ช่วยให้หลีกเลี่ยงการคิดว่ามีส่วนย่อยที่ถูกต้องตามกฎหมายที่จะลบออกเมื่อใช้เพจเจอร์มุมมองเช่น (และ back stack entry count> 0)


-1

หากคุณมีส่วนDialogFragmentที่ซ้อนกันอยู่ 'วิธีแก้ปัญหา' จะแตกต่างออกไปเล็กน้อย แทนที่จะตั้งค่าเป็นonKeyListenerไฟล์rootViewคุณจะต้องทำเช่นนั้นกับไฟล์Dialog. นอกจากนี้คุณจะได้รับการตั้งค่าDialogInterface.OnKeyListenerและไม่Viewหนึ่ง แน่นอนจำไว้addToBackStack!

Btw มี 1 ส่วนที่ด้านหลังสำหรับการมอบหมายการโทรกลับไปที่กิจกรรมเป็นกรณีการใช้งานส่วนตัวของฉัน สถานการณ์ทั่วไปอาจกำหนดให้จำนวนนับเป็น 0

นี่คือสิ่งที่คุณต้องทำภายใน onCreateDialog

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog =  super.onCreateDialog(savedInstanceState);
        dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                if(keyCode == KeyEvent.KEYCODE_BACK){
                    FragmentManager cfm = getChildFragmentManager();
                    if(cfm.getBackStackEntryCount()>1){
                        cfm.popBackStack();
                        return true;
                    }   
                }   
                return false;
            }
        });
        return dialog;
    }

ไม่เข้าใจจริงหรือไม่ครบถ้วน DialogFragment หรือ Superclass Fragment ไม่มี setOnKeyListener
Ixx

@Ixx คุณต้องตั้งค่าฟังเป็นDialog(ไม่ใช่DialogFragment) และผู้ฟังที่คุณใช้คือDialogInterface.OnKeyListener- รหัสทำงานและเสร็จสมบูรณ์ (และใช้งานอยู่) ทำไมคุณไม่ระบุสถานการณ์ของคุณและบางทีฉันอาจช่วยได้
Sachin Ahuja

-1

สำหรับ ChildFragments งานนี้ ..

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.