Phân đoạn trên Tiếp tục từ ngăn xếp trở lại


94

Tôi đang sử dụng gói tương thích để sử dụng Fragment với Android 2.2. Khi sử dụng các phân đoạn và thêm các chuyển đổi giữa chúng vào backstack, tôi muốn đạt được hành vi tương tự của onResume của một hoạt động, tức là, bất cứ khi nào một phân đoạn được đưa lên "tiền cảnh" (hiển thị cho người dùng) sau khi bật ra khỏi backstack, tôi muốn một số loại gọi lại được kích hoạt trong phân đoạn (ví dụ: để thực hiện các thay đổi nhất định trên tài nguyên giao diện người dùng được chia sẻ).

Tôi thấy rằng không có lệnh gọi lại được xây dựng trong khuôn khổ phân mảnh. có thực hành tốt để đạt được điều này không?


13
Câu hỏi tuyệt vời. Tôi đang cố gắng thay đổi tiêu đề của thanh tác vụ tùy thuộc vào phân đoạn nào được hiển thị dưới dạng trường hợp sử dụng cho trường hợp này và đối với tôi, có vẻ như một lệnh gọi lại bị thiếu trong API.
Manfred Moser

3
Hoạt động của bạn có thể đặt tiêu đề được cung cấp để nó biết các phân đoạn nào đang được hiển thị cùng một lúc. Ngoài ra, tôi cho rằng bạn có thể làm điều gì đó trong onCreateViewđó sẽ được gọi cho phân mảnh sau khi bật lên.
PJL,

1
@PJL +1 Theo tài liệu tham khảo đó là cách nó nên được thực hiện. Tuy nhiên tôi thích cách người nghe
momo

Câu trả lời:


115

Đối với việc thiếu giải pháp tốt hơn, tôi đã nhận được điều này phù hợp với mình: Giả sử tôi có 1 hoạt động (MyActivity) và một vài phân đoạn thay thế nhau (mỗi lần chỉ hiển thị một hoạt động).

Trong MyActivity, hãy thêm trình nghe này:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Như bạn có thể thấy, tôi đang sử dụng gói tương thích).

triển khai 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()sẽ được gọi sau khi nhấn "Quay lại". một số lưu ý mặc dù:

  1. Nó giả định rằng bạn đã thêm tất cả các giao dịch vào backstack (sử dụng FragmentTransaction.addToBackStack())
  2. Nó sẽ được kích hoạt sau mỗi lần thay đổi ngăn xếp (bạn có thể lưu trữ những thứ khác trong ngăn xếp phía sau chẳng hạn như hoạt ảnh) để bạn có thể nhận được nhiều lệnh gọi cho cùng một trường hợp của phân mảnh.

3
Bạn nên chấp nhận câu trả lời của chính mình là đúng nếu nó phù hợp với bạn.
powerj1984

7
Điều đó hoạt động như thế nào về id phân đoạn mà bạn đang truy vấn? Nó khác nhau như thế nào đối với từng phân mảnh và mảnh phù hợp được tìm thấy trong ngăn xếp phía sau?
Manfred Moser

2
@Warpzit rằng phương thức đó không được gọi và các tài liệu android lặng lẽ thừa nhận rằng nó sẽ không bao giờ được gọi (nó chỉ được gọi khi hoạt động được tiếp tục - điều này không bao giờ xảy ra, nếu hoạt động đó đã hoạt động)
Adam

2
Thật nực cười khi chúng ta phải làm điều này! Nhưng rất vui khi ai đó khác có thể tìm thấy "tính năng" này
stevebot

3
onResume () KHÔNG được gọi
Marty Miller

33

Tôi đã thay đổi giải pháp được đề xuất một chút. Hoạt động tốt hơn cho tôi như vậy:

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
xin vui lòng giải thích sự khác biệt và tại sao sử dụng điều này.
Math chiller

2
Sự khác biệt giữa giải pháp của tôi và giải pháp trước đó là trên mỗi thay đổi phân mảnh như thêm, thay thế hoặc quay ngược lại trong đống phân mảnh, phương thức "onResume" của phân mảnh trên cùng sẽ được gọi. Không có id phân đoạn được mã hóa cứng trong phương thức này. Về cơ bản, nó gọi onResume () trong phân đoạn mà bạn đang xem xét trên mọi thay đổi, bất kể nó đã được tải trong bộ nhớ hay chưa.
Brotoo 25

9
Không có bản ghi nào cho một phương thức được gọi getFragments()trong SupportFragmentManager ... -1: - /
Protostome

1
OnResume () được gọi nhưng tôi đang nhận được màn hình trống..Có cách nào khác để giải quyết vấn đề này không?
kavie

Tôi nghĩ rằng điều này dẫn đến Ngoại lệ ArrayOutOfBounds nếu backStackEntryCount bằng 0. Tôi có nhầm không? Đã cố gắng chỉnh sửa bài đăng nhưng nó đã bị từ chối.
Mike T

6

Sau khi popStackBack()bạn có thể sử dụng lệnh gọi lại sau: onHiddenChanged(boolean hidden)trong phân đoạn của bạn


2
Điều này dường như không hoạt động với các phân đoạn so sánh ứng dụng.
Lo-Tan

Đó là những gì tôi đã sử dụng. Cảm ơn!
Albert Vila Calvo

Giải pháp đơn giản nhất! Chỉ cần thêm kiểm tra xem phân đoạn hiện có hiển thị hay không và thì bạn có thể đặt tiêu đề thanh ứng dụng.
EricH206

4

Phần sau đây tại Nhà phát triển Android mô tả cơ chế giao tiếp Tạo sự kiện gọi lại hoạt động . Để trích dẫn một dòng từ nó:

Một cách tốt để làm điều đó là xác định một giao diện gọi lại bên trong phân mảnh và yêu cầu hoạt động máy chủ thực hiện nó. Khi hoạt động nhận được lệnh gọi lại thông qua giao diện, nó có thể chia sẻ thông tin với các phân đoạn khác trong bố cục nếu cần.

Chỉnh sửa: Phân mảnh có một onStart(...)được gọi khi phân mảnh hiển thị cho người dùng. Tương tự như một onResume(...)khi hiển thị và tích cực chạy. Chúng được gắn với các đối tác hoạt động của chúng. Tóm lại: sử dụngonResume()


1
Tôi chỉ đang tìm một phương thức trong lớp Fragment được kích hoạt bất cứ khi nào nó được hiển thị cho người dùng. cho dù là do FragmentTransaction.add () / Replace () hay FragmentManager.popBackStack ().
oriharel

@oriharel Xem câu trả lời cập nhật. Khi hoạt động được tải, bạn sẽ nhận được nhiều cuộc gọi khác nhau, onAttach, onCreate, onActivityCreate, onStart, onResume. Nếu bạn đã gọi setRetainInstance (true) 'thì bạn sẽ không nhận được onCreate khi phân đoạn đang được hiển thị lại khi nhấp vào trở lại.
PJL

15
onStart () không bao giờ được gọi khi nhấp vào "Quay lại" giữa các đoạn trong cùng một hoạt động. Tôi đã thử hầu hết các lệnh gọi lại trong tài liệu và không có lệnh nào trong số chúng được gọi trong trường hợp như vậy. xem câu trả lời của tôi để biết cách giải quyết.
oriharel

hmm với compat lib v4 Tôi đang thấy onResume cho phân đoạn của mình. Tôi thích câu trả lời của @ oriharel mặc dù nó phân biệt giữa việc trở nên hiển thị thông qua một popBackStack hơn là chỉ được đưa lên tiền cảnh.
PJL,

3

Trong hoạt động của tôi onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Sử dụng phương pháp này để bắt Fragment cụ thể và gọi 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

Một chút cải tiến và được gói gọn trong một giải pháp quản lý.

Những điều cần lưu ý. FragmentManager không phải là một singleton, nó chỉ quản lý các Fragment bên trong Activity, vì vậy trong mọi hoạt động, nó sẽ mới. Ngoài ra, giải pháp này cho đến nay không tính đến ViewPager gọi phương thức setUserVbrokenHint () giúp kiểm soát mức độ hiển thị của Fragment.

Vui lòng sử dụng các lớp sau khi giải quyết vấn đề này (sử dụng Dagger2 injection). Gọi trong Hoạt động:

//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);
}

1

Nếu một phân mảnh được đặt trên backstack, Android sẽ phá hủy chế độ xem của nó. Bản thân cá thể phân mảnh không bị giết. Một cách đơn giản để bắt đầu nên lắng nghe sự kiện onViewCreate, một logic "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
    }

0

Đây là câu trả lời chính xác mà bạn có thể gọi onResume () với điều kiện phân đoạn được đính kèm với hoạt động. Ngoài ra, bạn có thể sử dụng onAttach và onDetach


1
Bạn có thể đăng ví dụ xin vui lòng?
kavie

0

onResume () cho phân đoạn hoạt động tốt ...

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();
 }
}

và mức độ cuối cùng Frament của bạn từ RootFragment để xem hiệu quả


0

Cách giải quyết của tôi là lấy tiêu đề hiện tại của thanh tác vụ trong Fragment trước khi đặt nó thành tiêu đề mới. Bằng cách này, khi Fragment xuất hiện, tôi có thể thay đổi trở lại tiêu đề đó.

@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

Tôi đã sử dụng enum FragmentTagsđể định nghĩa tất cả các lớp phân mảnh của mình.

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

chuyển FragmentTags.TAG_FOR_FRAGMENT_A.name()dưới dạng thẻ phân mảnh.

và bây giờ

@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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.