Cách tiếp tục Fragment từ BackStack nếu tồn tại


151

Tôi đang học cách sử dụng các mảnh vỡ. Tôi có ba trường hợp Fragmentđược khởi tạo ở đầu lớp. Tôi đang thêm đoạn vào một hoạt động như thế này:

Khai báo và khởi tạo:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

Thay thế / Thêm:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

Những đoạn này đang hoạt động đúng. Mỗi phân đoạn được gắn vào hoạt động và được lưu vào ngăn xếp phía sau mà không gặp vấn đề gì.

Vì vậy, khi tôi khởi chạy A, Cvà sau đó B, ngăn xếp trông như thế này:

| |
|B|
|C|
|A|
___

Và khi tôi nhấn nút 'quay lại', Bbị hủy và Cđược nối lại.

Nhưng, khi tôi khởi chạy đoạn Athứ hai, thay vì tiếp tục từ ngăn xếp trở lại, nó được thêm vào ở đầu ngăn xếp phía sau

| |
|A|
|C|
|A|
___

Nhưng tôi muốn tiếp tục Avà phá hủy tất cả các mảnh vỡ trên nó (nếu có). Trên thực tế, tôi chỉ thích hành vi ngăn xếp trở lại mặc định.

Làm thế nào để tôi thực hiện điều này?

Dự kiến: ( Anên được nối lại và các mảnh vỡ trên cùng sẽ bị phá hủy)

| |
| |
| |
|A|
___

Chỉnh sửa: (được đề xuất bởi A - C)

Đây là mã cố gắng của tôi:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

Vấn đề là, khi tôi khởi chạy Avà sau Bđó nhấn 'trở lại', Bsẽ bị xóa và Ađược tiếp tục. và nhấn 'trở lại' lần thứ hai sẽ thoát khỏi ứng dụng. Nhưng nó đang hiển thị một cửa sổ trống và tôi phải nhấn lại lần thứ ba để đóng nó.

Ngoài ra, khi tôi khởi chạy A, sau đó B, sau đó C, Bmột lần nữa ...

Hy vọng:

| |
| |
|B|
|A|
___

Thực tế:

| |
|B|
|B|
|A|
___

Tôi nên ghi đè onBackPressed()với bất kỳ tùy chỉnh hoặc tôi đang thiếu một cái gì đó?

Câu trả lời:


279

Đọc tài liệu , có một cách để bật ngăn xếp trở lại dựa trên tên giao dịch hoặc id được cung cấp bởi cam kết. Sử dụng tên thể dễ dàng hơn vì không cần phải theo dõi một số có thể thay đổi và củng cố logic "mục nhập ngăn xếp trở lại duy nhất".

Vì bạn chỉ muốn một mục nhập ngăn xếp trở lại cho mỗi Fragment, hãy đặt tên trạng thái trở lại là tên lớp của Fragment (thông qua getClass().getName()). Sau đó khi thay thế a Fragment, sử dụng popBackStackImmediate()phương pháp. Nếu nó trả về true, điều đó có nghĩa là có một thể hiện của Fragment trong stack back. Nếu không, thực sự thực hiện logic thay thế Fragment.

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

BIÊN TẬP

Vấn đề là - khi tôi khởi chạy A và sau đó B, sau đó nhấn nút quay lại, B bị loại bỏ và A được nối lại. và nhấn lại nút trở lại sẽ thoát khỏi ứng dụng. Nhưng nó đang hiển thị một cửa sổ trống và cần một báo chí khác để đóng nó.

Điều này là do việc FragmentTransactionđược thêm vào ngăn xếp phía sau để đảm bảo rằng chúng ta có thể bật các mảnh trên đầu sau này. Một sửa chữa nhanh cho việc này là ghi đè onBackPressed()và hoàn thành Hoạt động nếu ngăn xếp phía sau chỉ chứa 1Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

Về các mục ngăn xếp trở lại trùng lặp, câu lệnh điều kiện của bạn thay thế đoạn nếu nó không được bật rõ ràng khác với đoạn mã gốc của tôi. Những gì bạn đang làm là thêm vào ngăn xếp phía sau bất kể ngăn xếp phía sau có được bật hay không.

Một cái gì đó như thế này nên gần hơn với những gì bạn muốn:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

Điều kiện đã được thay đổi một chút kể từ khi chọn cùng một đoạn trong khi nó có thể nhìn thấy cũng gây ra các mục trùng lặp.

Thực hiện:

Tôi đặc biệt khuyên bạn không nên sử dụng replaceFragment()phương thức cập nhật như bạn đã làm trong mã của mình. Tất cả logic được chứa trong phương pháp này và các bộ phận chuyển động xung quanh có thể gây ra vấn đề.

Điều này có nghĩa là bạn nên sao chép replaceFragment()phương thức cập nhật vào lớp của bạn sau đó thay đổi

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

vì vậy nó chỉ đơn giản là

replaceFragment (fragmentName);

EDIT # 2

Để cập nhật ngăn kéo khi ngăn xếp phía sau thay đổi, hãy tạo một phương thức chấp nhận trong Fragment và so sánh các tên lớp. Nếu bất cứ điều gì phù hợp, thay đổi tiêu đề và lựa chọn. Đồng thời thêm một OnBackStackChangedListenervà yêu cầu nó gọi phương thức cập nhật của bạn nếu có một Đoạn hợp lệ.

Ví dụ: trong Activity onCreate(), thêm

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

Và phương pháp khác:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

Bây giờ, bất cứ khi nào ngăn xếp phía sau thay đổi, tiêu đề và vị trí được kiểm tra sẽ phản ánh rõ ràng Fragment.


Cảm ơn câu trả lời của bạn. :) Nó đang hoạt động một phần. Tôi đã chỉnh sửa câu hỏi của tôi với tiến độ. Xin hãy xem)
Kaidul

2
@RishabhSrivastava hãy ghi nhớ một "pop" thường phá hủy đoạn trên cùng. Về phương thức nào được gọi đầu tiên, nó phụ thuộc - hãy xem Vòng đời mảnh vỡ. Đối với trường hợp của bạn, tôi sẽ sử dụng một OnBackstackChangedListenerđể bạn có thể đảm bảo rằng bạn đang xử lý đúng Mảnh vỡ.
A - C

2
@RishabhSrivastava Đó là lý do tại sao tôi cũng đề xuất OnBackstackChangedListenernhư vậy.
A - C

1
@AlexanderFarber Bạn đã đúng. Quay lại khi tôi thực hiện câu trả lời này, tôi đã không nghĩ tới instanceof:)
A - C

2
Giải pháp của bạn nghe có vẻ tốt, nhưng nếu bạn có ba mảnh và nhấp vào chúng A-B-C-Bvà nhấn lại một lần bạn sẽ quay lại Avà không quay lại C. Đó là bởi vì popBackStackImmediatekhông chỉ bật thẻ được đưa ra mà còn tất cả mọi thứ ở trên. Có bất kỳ công việc xung quanh đó?
Raeglan

10

Tôi nghĩ phương pháp này của tôi giải quyết vấn đề của bạn:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

ban đầu được đăng trong Câu hỏi này


Xin vui lòng cho tôi biết nếu trả lời như thế này là trái với quy tắc tôi chỉ muốn làm cho mọi người dễ dàng nhìn thấy trong bài viết này. Cảm ơn ..
erluxman

Tôi không chắc liệu điều đó có trái với quy tắc (có thể không), nhưng điều này chắc chắn rất hữu ích
Kathir

1
Việc sử dụng dòng thứ ba là gì? -> manager.findFragmentByTag (thẻ); Có vẻ như nó không làm gì cả ..
Rik van Velzen

3

Bước 1: Triển khai giao diện với lớp hoạt động của bạn

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

Bước 2: Khi bạn gọi một đoạn khác, thêm phương thức này:

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();

2

Tôi biết điều này khá muộn để trả lời câu hỏi này nhưng tôi đã tự mình giải quyết vấn đề này và nghĩ rằng đáng để chia sẻ nó với mọi người.

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

Và trong onBackPression ()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}

1
@ X-Black ... BaseFragment là lớp cơ sở cho mọi phân đoạn được sử dụng trong ứng dụng.
Vivek Pratap Singh

0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

        if(getFragmentManager().getBackStackEntryCount()==0) {
            onResume();
        }    
    }
});

0

Giải pháp dễ dàng hơn sẽ thay đổi dòng này

ft.replace(R.id.content_frame, A); đến ft.add(R.id.content_frame, A);

Và bên trong bố cục XML của bạn, vui lòng sử dụng

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable có nghĩa là nó có thể được bấm bởi một thiết bị con trỏ hoặc được gõ bởi một thiết bị cảm ứng.

Focusablecó nghĩa là nó có thể lấy nét từ một thiết bị đầu vào như bàn phím. Các thiết bị đầu vào như bàn phím không thể quyết định chế độ xem nào sẽ gửi các sự kiện đầu vào của nó dựa trên chính các đầu vào, vì vậy chúng sẽ gửi chúng đến chế độ xem tập trung.

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.