android fragment-다른 조각을 밀어 넣을 때 조각의 뷰 상태를 저장하는 방법


137

안드로이드에서는 프래그먼트 (예 FragA:)가 백 스택에 추가되고 다른 프래그먼트 (예 :) FragB가 맨 위에옵니다. 이제 반격 FragA이 시작되고 상단 onCreateView()이 호출됩니다. 이제 나는 그 위에 밀려 나기 FragA전에 특정 상태 FragB에있었습니다.

내 질문은 어떻게 FragA이전 상태로 복원 할 수 있습니까? 상태를 저장하는 방법이 있습니까 (번들처럼) 그렇다면 그렇다면 어떤 방법을 재정의해야합니까?

답변:


98

에서 단편 가이드 FragmentList의 예를 찾을 수 있습니다 :

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

나중에 다음과 같이 사용할 수 있습니다.

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

나는 Fragments의 초보자이지만 문제의 해결책처럼 보입니다.;) BackActivation에서 조각이 반환 된 후 OnActivityCreated가 호출됩니다.


18
이 작업을 수행 할 수 없었습니다. savedInstanceState는 항상 null이었습니다. xml 레이아웃을 통해 조각을 추가하고 있습니다. mCurCheckPosition을 static으로 변경해야 작동하지만 해킹 느낌이 듭니다.
scottyab

57
onSaveInstanceState를 호출하지 않습니다. 왜 그런가요? 따라서이 방법은 효과가 없습니다.
12

10
동일한 액티비티의 다른 조각에서 돌아 오는 동안 조각 상태를 유지하려는 경우이 방법이 실제로 작동합니까? onSaveInstanceState ()는 Activity onPause / onStop 이벤트에서만 호출됩니다. 문서에 따르면 : "활동과 마찬가지로 활동의 프로세스가 종료되고 활동을 다시 작성할 때 단편 상태를 복원해야하는 경우 번들을 사용하여 단편의 상태를 유지할 수 있습니다. 조각의 onSaveInstanceState () 콜백을 수행하고 onCreate (), onCreateView () 또는 onActivityCreated () 중에 복원합니다. "
Paramvir Singh

26
기록을 위해이 접근법은 틀 렸으며 투표권이있는 곳이 없어야합니다. onSaveInstanceState해당 활동이 종료 될 때만 호출됩니다.
Martin Konecny

16
설정 변경이 발생하고 활동이 파괴 될 때 onSaveInstanceState ()가 온라인으로 호출됩니다.이 답변은 잘못되었습니다
Tadas Valaitis

83

조각의은 onSaveInstanceState(Bundle outState)자체 부착 된 조각에 조각의 활동 호출을하지 않는 한 호출되지 않습니다. 따라서이 메소드는 무언가 (일반적으로 회전)가 활동을 강제 SaveInstanceState하고 나중에 복원 할 때까지 호출되지 않습니다 . 그러나 하나의 활동과 그 안에 많은 조각이 있고 (집중적으로 사용 replace) 응용 프로그램이 한 방향으로 만 실행되는 경우 onSaveInstanceState(Bundle outState)오랫동안 호출되지 않을 수 있습니다.

가능한 세 가지 해결 방법을 알고 있습니다.

첫번째:

중요한 데이터를 보유하기 위해 조각의 인수를 사용하십시오.

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

두 번째이지만 덜 간단한 방법-싱글 톤으로 변수를 보유하십시오.

세 번째- replace()조각화 하지 않고 대신 add()/ show()/를 hide()사용하십시오.


3
Fragment.onSaveInstanceState()호출되지 않은 최고의 솔루션 . 목록보기의 항목 또는 해당 ID 만 포함하여 자신의 데이터를 인수에 저장하기 만하면됩니다 (다른 중앙 집중식 데이터 관리자가있는 경우). 목록보기의 위치를 ​​저장할 필요가 없습니다-자동으로 저장되고 복원되었습니다.
John Pang

내 앱에서 예제를 사용하려고했지만 이것은 String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);항상 null입니다. 뭐가 문제 야?
fragon

단편의를 사용하는 것은 getArguments()확실히 ViewPager에서 중첩 된 조각을 포함하여, 길을 가야하는 것입니다. 나는 1 액티비티를 사용하고 많은 조각을 입 / 출력하며 이것은 완벽하게 작동합니다. 다음은 제안 된 솔루션을 조사하기위한 간단한 테스트입니다. 1) Fragment A에서 Fragment B로 이동하십시오. 2) 장치 방향을 두 번 변경하십시오. 3) 장치의 뒤로 버튼을 누릅니다.
Andy H.

첫 번째 방법을 시도했지만 효과가 없었습니다. getArguments ()는 항상 null을 반환합니다. 프래그먼트가 교체되고 onCreate ()에서 이전 번들이 손실되도록 새 번들을 설정했기 때문에 의미가 있습니다. 내가 뭘 놓치고 틀렸습니까?
Zvi

@ Zvi, 나는 1,5 년 전에이 코드를 썼고 모든 세부 사항을 기억하지는 않지만, 기억하는 것처럼 replace는 프래그먼트를 다시 만들지 않고 코드에서 새 인스턴스를 만든 경우에만 프래그먼트를 다시 만듭니다. 이 경우 분명히 생성자가 setArguments(new Bundle());이전 번들을 호출하고 덮어 씁니다. 따라서 조각을 한 번만 만든 다음 매번 새 인스턴스를 만드는 대신이 인스턴스를 사용하십시오.
Fyodor Volchyok

20

ViewPager를 사용하여 Fragments를 사용하면 매우 쉽습니다. 이 메소드 만 호출하면 setOffscreenPageLimit()됩니다.

문서에 따라 :

유휴 상태의보기 계층 구조에서 현재 페이지의 한쪽에 유지해야하는 페이지 수를 설정하십시오. 이 한계를 초과하는 페이지는 필요할 때 어댑터에서 다시 작성됩니다.

여기서 비슷한 문제


5
이것은 다릅니다. setOffScreenPageLimit는 캐시처럼 작동하지만 (ViewPager가 주어진 순간에 처리해야하는 페이지 수를 의미) 조각의 상태를 저장하는 데 사용되지는 않습니다.
Renaud Mathieu

필자의 경우 setOffscreenPageLimit ()과 함께 작동했습니다. 프래그먼트가 손상되었지만 뷰 상태가 저장되고 복원되었습니다.
Davincho

고마워, 나도 도와 줬어
Elijah

몇 년이 지난 지금도 여전히 관련이 있습니다. 실제로 질문에 대한 답은 아니지만 문제를 해결합니다
Supreme Dolphin

19

뷰를 한 번만 부 풀리십시오.

예를 들면 다음과 같습니다.

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}


또한 기존 조각을 배열로 유지해야합니다
Amir

조각의 rootView에 대한 참조를 유지하는 것이 나쁜 습관이라고 들었습니다. 누출이 발생할 수 있습니다 [인용 필요]?
giraffe.guru

1
@ giraffe.guru Fragment는 루트 뷰가 그렇게하지 않을 것이라고 말합니다. 일부 GC- 루트 요소에 의한 참조는 전역 정적 속성과 같이 비 ui 스레드 변수입니다. Fragment가비지 수집 할 수 있도록 인스턴스는, GC-루트가 아닌. 루트 뷰도 마찬가지입니다.
Lym Zoy

내 하루도 똑같아
Vijendra patidar

달콤하고 간단합니다.
Rupam Das

8

나는 이것과 매우 비슷한 문제로 일했다. 나는 종종 이전 조각으로 돌아갈 것이라는 것을 알았으므로 조각 .isAdded()이 사실 인지 확인했으며 , 그렇다면 조각 을 만드는 것보다 transaction.replace()그냥 수행합니다 transaction.show(). 이렇게하면 프래그먼트가 이미 스택에있는 경우 프래그먼트가 다시 생성되지 않습니다.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

명심해야 할 또 다른 사항은 프래그먼트 자체의 자연스러운 순서를 유지하면서 방향 (구성) 변경시 파기 및 재 작성되는 활동 자체를 처리해야 할 수도 있다는 것입니다. 노드의 AndroidManifest.xml에서이 문제를 해결하려면 다음을 수행하십시오.

android:configChanges="orientation|screenSize"

Android 3.0 이상에서는 screenSize분명히 필요합니다.

행운을 빕니다


transaction.addToBackStack (button_id + "stack_item"); //이 줄의 기능은 무엇입니까? 여기서 button_id는 무엇입니까?
raghu_3

button_id는 구성 변수 일뿐입니다. addToBackStack에 전달 된 문자열 인수는 백 스택 상태의 선택적 이름 일뿐입니다. 단일 백 스택 만 관리하는 경우에는 널로 설정할 수 있습니다.
rmirabelle

, 난 당신이 - 그것을 들여다을 기쁘게, 조각이 유사한 문제를 수있는 데 stackoverflow.com/questions/22468977/...
raghu_3

1
android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternets매니페스트를 추가하지 마십시오 . 대신 다음과 같이 상태를 저장하고 복원하는 방법을 배우십시오. speakerdeck.com/cyrilmottier/…
Marcin Koziński 2016 년

그리고 나서 당신이 미친 듯이 상태를 저장하는 것이 얼마나 위험한지 알게되었고 제시된 솔루션이 당신의 특정 상황에서 가장 깨끗하게 문제를 해결한다는 것을 알게되면, 동의하지 않는 사람들의
공감대에

5

내가 찾은 최고의 솔루션은 다음과 같습니다.

onSavedInstanceState () : 활동이 종료 될 때 항상 조각 내에서 호출됩니다 (활동을 다른 것으로 이동하거나 구성 변경). 따라서 동일한 액티비티에서 여러 조각을 호출하는 경우 다음 접근법을 사용해야합니다.

프래그먼트의 OnDestroyView ()를 사용하여 전체 객체를 해당 메소드에 저장하십시오. 그런 다음 OnActivityCreated () : 객체가 null인지 확인합니다 (이 메소드는 매번 호출하기 때문에). 이제 여기에서 객체의 상태를 복원하십시오.

항상 작동합니다!


4

다음과 같이 android manifest에 지정된 조각 활동의 구성 변경을 처리하는 경우

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

그런 다음 onSaveInstanceState프래그먼트의가 호출되지 않고 savedInstanceState객체는 항상 null입니다.


1

나는 onSaveInstanceState좋은 해결책 이라고 생각하지 않습니다 . 그것은 단지 destoryed 된 활동을 위해 사용합니다.

안드로이드 3.0에서 Fragmen은 FragmentManager에 의해 관리자였습니다. 조건은 : 하나의 활동 매핑 매니 프래그먼트, backStack에 프래그먼트가 추가 (바꾸지 않을 것입니다 : 다시 생성 됨)하면 뷰가 저장됩니다. 마지막으로 돌아 가면 이전과 같이 표시됩니다.

그래서 fragmentManger와 트랜잭션이 처리하기에 충분하다고 생각합니다.


0

목록보기가 포함 된 조각에 하이브리드 접근법을 사용했습니다. 현재 조각을 바꾸지 않고 새로운 조각을 추가하고 현재 조각을 숨기므로 성능이 좋은 것 같습니다. 내 조각을 호스팅하는 활동에 다음과 같은 방법이 있습니다.

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

목록 항목을 클릭 / 탭 할 때마다 (목록보기가 포함 된) 조각 에서이 방법을 사용하므로 세부 정보 조각을 시작 / 표시해야합니다.

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()새 조각을 추가 할 때 다른 조각에 대한 태그로 사용하는 문자열 배열을 반환합니다 ( 위의 transaction.add방법의 addFragment메소드 참조).

목록보기가 포함 된 조각에서는 onPause () 메서드 에서이 작업을 수행합니다.

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

그런 다음 프래그먼트의 onCreateView (실제로 onCreateView에서 호출되는 메소드에서)에서 상태를 복원합니다.

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}

0

결국이 Fragment (EditText의 내용)에 단일 값을 저장 / 복원해야하기 때문에이 복잡한 솔루션을 많이 시도한 후에 결국 가장 우아한 솔루션은 아니지만 SharedPreference를 만들고 내 상태를 저장합니다. 나를 위해 일했다


0

활동에서 다른 조각으로 필드 값을 유지하는 간단한 방법

프래그먼트 인스턴스 생성 및 교체 및 제거 대신 추가

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

그런 다음 조각을 다시 추가하거나 제거하는 대신 조각을 표시하고 숨기십시오.

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;


-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}

5
단순히 코드를 게시하는 대신 답변에 대한 정보를 포함시키는 것을 고려하십시오. 우리는 단지 '수정'을 제공하는 것이 아니라 사람들이 배우도록 도와줍니다. 원래 코드에서 무엇이 잘못되었는지, 어떻게 다르게했는지, 왜 변경이 효과가 있었는지 설명해야합니다.
Andrew Barber
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.