Fragments onResume จาก back stack


96

ฉันใช้แพ็คเกจความเข้ากันได้เพื่อใช้ Fragments กับ Android 2.2 เมื่อใช้แฟรกเมนต์และเพิ่มการเปลี่ยนระหว่างพวกมันไปที่แบ็คสแต็กฉันต้องการให้เกิดพฤติกรรมเดียวกันของ onResume ของกิจกรรมนั่นคือเมื่อใดก็ตามที่แฟรกเมนต์ถูกนำไปที่ "โฟร์กราวด์" (ผู้ใช้มองเห็นได้) หลังจากที่โผล่ออกมาจาก backstack ฉันต้องการให้มีการเปิดใช้งานการโทรกลับบางประเภทภายในส่วน (เพื่อทำการเปลี่ยนแปลงบางอย่างในทรัพยากร UI ที่แชร์เป็นต้น)

ฉันเห็นว่าไม่มีการเรียกกลับภายในกรอบส่วนย่อย มีแนวทางปฏิบัติที่ดีเพื่อให้บรรลุเป้าหมายนี้หรือไม่?


13
คำถามที่ดี ฉันกำลังพยายามเปลี่ยนชื่อแถบการดำเนินการขึ้นอยู่กับส่วนที่มองเห็นได้เป็นกรณีการใช้งานสำหรับสถานการณ์นี้และดูเหมือนว่าจะมีการโทรกลับที่ขาดหายไปใน API ถึงฉัน
Manfred Moser

3
กิจกรรมของคุณสามารถตั้งชื่อเรื่องได้เนื่องจากทราบว่าจะมีการแสดงเศษชิ้นส่วนใดในคราวเดียว นอกจากนี้ฉันคิดว่าคุณสามารถทำบางสิ่งบางอย่างonCreateViewที่จะเรียกส่วนย่อยหลังจากป๊อป
PJL

1
@PJL +1 อ้างอิงว่าเป็นวิธีที่ควรทำ อย่างไรก็ตามฉันชอบวิธีการฟัง
momo

คำตอบ:


116

สำหรับการขาดวิธีแก้ปัญหาที่ดีกว่าฉันจึงทำให้สิ่งนี้ได้ผล: สมมติว่าฉันมี 1 กิจกรรม (MyActivity) และส่วนย่อยไม่กี่ชิ้นที่มาแทนที่ซึ่งกันและกัน (มองเห็นได้ครั้งละหนึ่งรายการเท่านั้น)

ใน MyActivity ให้เพิ่ม Listener นี้:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(อย่างที่คุณเห็นฉันใช้แพ็คเกจความเข้ากันได้)

การใช้งาน getListener:

private OnBackStackChangedListener getListener()
    {
        OnBackStackChangedListener result = new OnBackStackChangedListener()
        {
            public void onBackStackChanged() 
            {                   
                FragmentManager manager = getSupportFragmentManager();

                if (manager != null)
                {
                    MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);

                    currFrag.onFragmentResume();
                }                   
            }
        };

        return result;
    }

MyFragment.onFragmentResume()จะถูกเรียกหลังจากกด "ย้อนกลับ" คำเตือนบางประการแม้ว่า:

  1. ถือว่าคุณได้เพิ่มธุรกรรมทั้งหมดลงในแบ็คสแต็ค (โดยใช้ FragmentTransaction.addToBackStack())
  2. จะเปิดใช้งานเมื่อมีการเปลี่ยนแปลงสแต็กแต่ละครั้ง (คุณสามารถจัดเก็บสิ่งอื่น ๆ ในสแต็กด้านหลังเช่นภาพเคลื่อนไหว) ดังนั้นคุณอาจได้รับการเรียกหลายครั้งสำหรับชิ้นส่วนเดียวกัน

3
คุณควรยอมรับคำตอบของคุณเองว่าถูกต้องหากคำตอบนั้นได้ผลสำหรับคุณ
powerj1984

7
มันทำงานอย่างไรในแง่ของ id แฟรกเมนต์ที่คุณกำลังค้นหา แต่ละชิ้นส่วนแตกต่างกันอย่างไรและพบชิ้นส่วนที่เหมาะสมในสแต็กด้านหลัง
Manfred Moser

2
@ Warpzit วิธีนั้นไม่ได้ถูกเรียกและเอกสาร android ยอมรับอย่างเงียบ ๆ ว่าจะไม่ถูกเรียก (จะเรียกเฉพาะเมื่อกิจกรรมนั้นกลับมาทำงานต่อ - ซึ่งจะไม่เกิดขึ้นเลยหากกิจกรรมนั้นใช้งานอยู่แล้ว)
อดัม

2
ไร้สาระที่เราต้องทำแบบนี้! แต่ดีใจที่มีคนอื่นพบ "คุณลักษณะ" นี้
stevebot

3
onResume () ไม่ถูกเรียก
Marty Miller

33

ฉันได้เปลี่ยนวิธีแก้ปัญหาที่แนะนำเล็กน้อยแล้ว ทำงานได้ดีขึ้นสำหรับฉันเช่นนั้น:

private OnBackStackChangedListener getListener() {
    OnBackStackChangedListener result = new OnBackStackChangedListener() {
        public void onBackStackChanged() {
            FragmentManager manager = getSupportFragmentManager();
            if (manager != null) {
                int backStackEntryCount = manager.getBackStackEntryCount();
                if (backStackEntryCount == 0) {
                    finish();
                }
                Fragment fragment = manager.getFragments()
                                           .get(backStackEntryCount - 1);
                fragment.onResume();
            }
        }
    };
    return result;
}

1
โปรดอธิบายความแตกต่างและเหตุใดจึงใช้สิ่งนี้
Math chiller

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

9
ไม่มีบันทึกสำหรับวิธีการที่เรียกว่าgetFragments()ใน SupportFragmentManager ... -1: - /
Protostome

1
เรียก OnResume () แล้ว แต่ฉันกำลังเปิดหน้าจอว่างเปล่า .. มีวิธีอื่นในการแก้ไขปัญหานี้หรือไม่
kavie

ฉันคิดว่าสิ่งนี้นำไปสู่ข้อยกเว้น ArrayOutOfBounds ถ้า backStackEntryCount เป็น 0 ฉันเข้าใจผิดหรือเปล่า? พยายามแก้ไขโพสต์ แต่ถูกปฏิเสธ
Mike T

6

หลังจากนั้นpopStackBack()คุณสามารถใช้การโทรกลับต่อไปนี้: onHiddenChanged(boolean hidden)ภายในส่วนของคุณ


2
ดูเหมือนว่าจะไม่ทำงานกับชิ้นส่วนที่เข้ากันได้กับแอป
Lo-Tan

นั่นคือสิ่งที่ฉันใช้ ขอบคุณ!
Albert Vila Calvo

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

4

ส่วนต่อไปนี้ที่พัฒนา Android อธิบายกลไกการสื่อสารสร้างการเรียกกลับเหตุการณ์กิจกรรม หากต้องการอ้างอิงบรรทัดจากมัน:

วิธีที่ดีในการทำเช่นนั้นคือกำหนดอินเทอร์เฟซการเรียกกลับภายในแฟรกเมนต์และกำหนดให้กิจกรรมโฮสต์นำไปใช้ เมื่อกิจกรรมได้รับการติดต่อกลับผ่านอินเทอร์เฟซจะสามารถแบ่งปันข้อมูลกับส่วนอื่น ๆ ในโครงร่างได้ตามความจำเป็น

แก้ไข: แฟรกเมนต์มีส่วนonStart(...)ที่ถูกเรียกใช้เมื่อผู้ใช้มองเห็นแฟรกเมนต์ ในทำนองเดียวกันonResume(...)เมื่อมองเห็นได้และกำลังทำงานอยู่ สิ่งเหล่านี้เชื่อมโยงกับคู่กิจกรรมของพวกเขา ในระยะสั้น: ใช้onResume()


1
ฉันแค่มองหาวิธีการในคลาส Fragment ที่เปิดใช้งานเมื่อใดก็ตามที่แสดงต่อผู้ใช้ ไม่ว่าจะเป็นเพราะ FragmentTransaction.add () / replace () หรือ FragmentManager.popBackStack ()
oriharel

@oriharel ดูคำตอบที่อัปเดต เมื่อโหลดกิจกรรมแล้วคุณจะได้รับสายต่างๆ onAttach, onCreate, onActivityCreated, onStart, onResume หากคุณเรียกใช้ `` setRetainInstance (true) 'คุณจะไม่ได้รับ onCreate เมื่อมีการแสดงส่วนอีกครั้งเมื่อคลิกย้อนกลับ
PJL

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

อืมกับ lib v4 ที่เข้ากันได้ฉันเห็น onResume สำหรับส่วนของฉัน ฉันชอบคำตอบของ @oriharel แม้ว่ามันจะแยกความแตกต่างระหว่างการมองเห็นผ่าน popBackStack มากกว่าแค่ถูกนำไปที่เบื้องหน้า
PJL

3

หากส่วนย่อยถูกวางไว้บนแบ็คสแต็ค Android ก็ทำลายมุมมองของมัน อินสแตนซ์แฟรกเมนต์ไม่ได้ถูกฆ่า วิธีง่ายๆในการเริ่มต้นควรฟังเหตุการณ์ onViewCreated ซึ่งเป็นตรรกะของ "onResume ()" ที่นั่น

boolean fragmentAlreadyLoaded = false;
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState == null && !fragmentAlreadyLoaded) {
            fragmentAlreadyLoaded = true;

            // Code placed here will be executed once
        }

        //Code placed here will be executed even when the fragment comes from backstack
    }

3

ในกิจกรรมของฉัน onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

ใช้วิธีนี้เพื่อจับ Fragment เฉพาะและเรียก onResume ()

private FragmentManager.OnBackStackChangedListener getListener()
    {
        FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
        {
            public void onBackStackChanged()
            {
                Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
                if (currentFragment instanceof YOURFRAGMENT) {
                    currentFragment.onResume();
                }
            }
        };

        return result;
    }

2

ปรับปรุงเล็กน้อยและรวมอยู่ในโซลูชันผู้จัดการ

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

อย่าลังเลที่จะใช้คลาสต่อไปนี้เมื่อจัดการกับปัญหานี้ (ใช้ Dagger2 injection) โทรในกิจกรรม:

//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager(); 
myFragmentBackstackStateManager.apply(fragmentManager);

FragmentBackstackStateManager.java:

@Singleton
public class FragmentBackstackStateManager {

    private FragmentManager fragmentManager;

    @Inject
    public FragmentBackstackStateManager() {
    }

    private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
        @Override
        public void onFragmentPushed(Fragment parentFragment) {
            parentFragment.onPause();
        }

        @Override
        public void onFragmentPopped(Fragment parentFragment) {
            parentFragment.onResume();
        }
    };

    public FragmentBackstackChangeListenerImpl getListener() {
        return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
    }

    public void apply(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
        fragmentManager.addOnBackStackChangedListener(getListener());
    }
}

FragmentBackstackChangeListenerImpl.java:

public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {

    private int lastBackStackEntryCount = 0;
    private final FragmentManager fragmentManager;
    private final BackstackCallback backstackChangeListener;

    public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
        this.fragmentManager = fragmentManager;
        this.backstackChangeListener = backstackChangeListener;
        lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
    }

    private boolean wasPushed(int backStackEntryCount) {
        return lastBackStackEntryCount < backStackEntryCount;
    }

    private boolean wasPopped(int backStackEntryCount) {
        return lastBackStackEntryCount > backStackEntryCount;
    }

    private boolean haveFragments() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList != null && !fragmentList.isEmpty();
    }


    /**
     * If we push a fragment to backstack then parent would be the one before => size - 2
     * If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
     *
     * @return fragment that is parent to the one that is pushed to or popped from back stack
     */
    private Fragment getParentFragment() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList.get(Math.max(0, fragmentList.size() - 2));
    }

    @Override
    public void onBackStackChanged() {
        int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
        if (haveFragments()) {
            Fragment parentFragment = getParentFragment();

            //will be null if was just popped and was last in the stack
            if (parentFragment != null) {
                if (wasPushed(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPushed(parentFragment);
                } else if (wasPopped(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPopped(parentFragment);
                }
            }
        }

        lastBackStackEntryCount = currentBackStackEntryCount;
    }
}

BackstackCallback.java:

public interface BackstackCallback {
    void onFragmentPushed(Fragment parentFragment);

    void onFragmentPopped(Fragment parentFragment);
}

0

นี่คือคำตอบที่ถูกต้องที่คุณสามารถเรียก onResume () โดยให้ส่วนที่แนบมากับกิจกรรม หรือคุณสามารถใช้ onAttach และ onDetach


1
คุณสามารถโพสต์ตัวอย่างได้ไหม
kavie

0

onResume () สำหรับส่วนทำงานได้ดี ...

public class listBook extends Fragment {

    private String listbook_last_subtitle;
...

    @Override
       public void onCreate(Bundle savedInstanceState) {

        String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
        listbook_last_subtitle = thisFragSubtitle;
       }
...

    @Override
        public void onResume(){
            super.onResume();
            getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
        }
...

0
public abstract class RootFragment extends Fragment implements OnBackPressListener {

 @Override
 public boolean onBackPressed() {
  return new BackPressImpl(this).onBackPressed();
 }

 public abstract void OnRefreshUI();

}


public class BackPressImpl implements OnBackPressListener {

 private Fragment parentFragment;

 public BackPressImpl(Fragment parentFragment) {
  this.parentFragment = parentFragment;
 }

 @Override
 public boolean onBackPressed() {
  ((RootFragment) parentFragment).OnRefreshUI();
 }
}

และขอบเขตสุดท้าย Frament ของคุณจาก RootFragment เพื่อดูเอฟเฟกต์


0

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

@Override
public void onResume() {
    super.onResume();
    // Get/Backup current title
    mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
            .getTitle();
    // Set new title
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(R.string.this_fragment_title);
}

@Override
public void onDestroy() {
    // Set title back
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(mTitle);

    super.onDestroy();
}

0

ฉันใช้ enum FragmentTagsเพื่อกำหนดคลาสแฟรกเมนต์ทั้งหมดของฉัน

TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)

ส่งผ่านFragmentTags.TAG_FOR_FRAGMENT_A.name()เป็นแท็กแฟรกเมนต์

และตอนนี้

@Override
public void onBackPressed(){
   FragmentManager fragmentManager = getFragmentManager();
   Fragment current
   = fragmentManager.findFragmentById(R.id.fragment_container);
    FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());

  switch(fragmentTag){
    case TAG_FOR_FRAGMENT_A:
        finish();
        break;
   case TAG_FOR_FRAGMENT_B:
        fragmentManager.popBackStack();
        break;
   case default: 
   break;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.