존재하는 경우 BackStack에서 조각을 재개하는 방법


151

조각을 사용하는 방법을 배우고 있습니다. Fragment클래스 상단에 세 개의 인스턴스 가 초기화되어 있습니다. 다음과 같은 활동에 조각을 추가하고 있습니다.

선언 및 초기화 :

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

교체 / 추가 :

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

이 스 니펫이 제대로 작동합니다. 모든 프래그먼트가 액티비티에 첨부되며 아무런 문제없이 백 스택에 저장됩니다.

그래서 시작할 때 A, C다음과 B같은 스택 외모 :

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

그리고 '뒤로'버튼을 누르면 B파괴되고 C다시 시작됩니다.

그러나 A두 번째로 조각 을 시작하면 백 스택에서 다시 시작하는 대신 백 스택의 맨 위에 추가됩니다.

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

그러나 나는 A그 위에있는 모든 조각 을 재개 하고 파괴 하고 싶습니다 (있는 경우). 사실, 나는 기본 백 스택 동작을 좋아합니다.

어떻게하면 되나요?

예상 : ( A다시 시작하고 상단 조각을 파괴해야 함)

| |
| |
| |
|A|
___

편집 : (AC에서 제안)

이것은 내 시도 코드입니다.

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

내가 시작할 때 문제는, A다음 B, 다음을 눌러 '돌아 가기' B를 제거하고 A다시 시작됩니다. 다시 '뒤로'를 누르면 앱이 종료됩니다. 그러나 그것은 빈 창을 표시하고 그것을 닫으려면 세 번 눌러야합니다.

또한, 나는 시작하면 A, 다음 B, 다음 C, 다음, B다시 ...

예상 :

| |
| |
|B|
|A|
___

실제 :

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

onBackPressed()사용자 지정으로 재정의해야합니까, 아니면 뭔가 빠졌습니까?

답변:


279

문서를 읽으면 트랜잭션 이름 또는 커밋이 제공 한 ID를 기반으로 백 스택을 팝업하는 방법이 있습니다. 이름을 사용하면 "고유 백 스택 항목"논리를 변경하고 강화할 수있는 숫자를 추적 할 필요가 없으므로 더 쉬울 있습니다.

당 하나의 백 스택 항목 만 원 Fragment하므로 백 상태 이름을 Fragment의 클래스 이름 (via getClass().getName())으로 지정하십시오. 그런 다음를 교체 할 때이 방법을 Fragment사용하십시오 popBackStackImmediate(). true를 반환하면 백 스택에 Fragment 인스턴스가 있음을 의미합니다. 그렇지 않은 경우 실제로 Fragment replacement 로직을 실행하십시오.

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

편집하다

문제는-A를 시작한 다음 B를 시작한 다음 뒤로 버튼을 누르면 B가 제거되고 A가 다시 시작됩니다. 다시 뒤로 버튼을 누르면 앱이 종료됩니다. 그러나 그것은 빈 창을 표시하고 그것을 닫으려면 다시 한 번 눌러야합니다.

이것은 FragmentTransaction나중에 스택을 팝 할 수 있도록 백 스택에 추가 되기 때문 입니다. onBackPressed()백 스택에 1 개만 포함 된 경우 이를위한 빠른 수정 은 활동을 재정의 하고 마무리하는 것입니다.Fragment

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

중복 백 스택 항목과 관련하여 조각이 튀어 나오지 않은 경우 조각을 대체하는 조건문은 원래의 코드 스 니펫 명확하게 다릅니다 . 당신이하고있는 일은 백 스택이 터 졌는지 여부에 관계없이 백 스택에 추가하는 것입니다.

이와 같은 것은 원하는 것에 더 가깝습니다.

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

동일한 프래그먼트를 선택하고 표시되는 동안 조건부가 약간 변경되어 중복 항목이 발생했습니다.

이행:

replaceFragment()코드에서와 같이 업데이트 된 메소드를 분리 하지 않는 것이 좋습니다 . 이 방법에는 모든 논리가 포함되어 있으며 부품을 움직이면 문제가 발생할 수 있습니다.

이것은 업데이트 된 replaceFragment()메소드를 클래스에 복사 한 다음 변경 해야한다는 것을 의미합니다

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

그래서 그것은 단순히

replaceFragment (fragmentName);

편집 # 2

백 스택이 변경 될 때 드로어를 업데이트하려면 Fragment에서 승인하고 클래스 이름을 비교하는 메소드를 작성하십시오. 일치하는 것이 있으면 제목과 선택을 변경하십시오. 또한 OnBackStackChangedListener유효한 Fragment가있는 경우 업데이트 메소드를 추가하고 업데이트 메소드를 호출하십시오.

예를 들어, 활동의에서 onCreate(), 추가

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

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

  }
});

그리고 다른 방법 :

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
  }
}

이제 백 스택이 변경 될 때마다 제목과 확인 된 위치가 visible을 반영합니다 Fragment.


답변 주셔서 감사합니다. :) 부분적으로 작동합니다. 진행하면서 질문을 편집했습니다. )
Kaidul

2
@RishabhSrivastava는 "팝"이 보통 최상위 조각을 파괴한다는 것을 명심하십시오. 어떤 방법이 먼저 호출되는지에 따라 다릅니다. Fragment Lifecycle을 살펴보십시오. 귀하의 경우, OnBackstackChangedListener올바른 조각을 다루고 있음을 보장 할 수 있도록 사용하겠습니다 .
A--C

2
@RishabhSrivastava 그래서 내가 제안한 이유 OnBackstackChangedListener입니다.
A--C

1
@AlexanderFarber 맞습니다. 내가이 대답을했을 때 나는 생각하지 않았다 instanceof:)
A--C

2
솔루션은 잘 들리지만 세 개의 조각이 있고 조각을 클릭 A-B-C-B한 다음 한 번 누르면 뒤로 돌아 가지 A않고 다시 돌아갑니다 C. popBackStackImmediate주어진 태그뿐만 아니라 위의 모든 것을 팝업 하기 때문 입니다. 거기에 어떤 일이 있습니까?
Raeglan

10

이 방법으로 문제를 해결할 수 있다고 생각합니다.

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

이 질문 에 원래 게시 된


이와 같은 답변이이 게시물에서 사람들이 쉽게 볼 수 있도록하려는 규칙에 위배되는 경우 알려 주시기 바랍니다. 감사합니다 ..
erluxman

나는 그것이 규칙에 어긋나는 지 확실하지 않지만 (아마도) 이것이 도움이된다
Kathir

1
세 번째 줄의 사용법은 무엇입니까? -> manager.findFragmentByTag (태그); 아무것도하지 않는 것 같습니다.
Rik van Velzen

3

1 단계 : 활동 클래스와의 인터페이스 구현

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
   }       
}

2 단계 : 다른 조각을 호출 할 때이 메소드를 추가하십시오.

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

나는 이것이이 질문에 대답하기에 상당히 늦다는 것을 알고 있지만,이 문제를 혼자서 해결하고 모든 사람과 공유 할 가치가 있다고 생각했습니다 .`

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

그리고 onBackPressed ()에서

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

1
@ X-Black ... BaseFragment는 응용 프로그램에서 사용되는 모든 조각의 기본 클래스입니다.
Vivek Pratap Singh

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

    @Override
    public void onBackStackChanged() {

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

0

더 쉬운 솔루션은이 줄을 바꿀 것입니다

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

그리고 XML 레이아웃 내부에서 사용하십시오

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

Clickable 즉, 포인터 장치로 클릭하거나 터치 장치로 탭할 수 있습니다.

Focusable키보드와 같은 입력 장치에서 포커스를 얻을 수 있음을 의미합니다. 키보드와 같은 입력 장치는 입력 자체를 기반으로 입력 이벤트를 보낼 뷰를 결정할 수 없으므로 포커스가있는 뷰로 보냅니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.