ViewPager 및 프래그먼트 — 프래그먼트의 상태를 저장하는 올바른 방법은 무엇입니까?


492

조각은 UI 논리를 일부 모듈로 분리하는 데 매우 좋은 것 같습니다. 그러나 ViewPager수명주기 와 함께 여전히 안개 낀다. 따라서 전문가의 생각이 절실히 필요합니다!

편집하다

아래 바보 같은 솔루션을 참조하십시오 ;-)

범위

주요 활동에는 ViewPager조각이 있습니다. 이러한 조각은 다른 (서브) 활동에 대해 약간 다른 논리를 구현할 수 있으므로 조각의 데이터는 활동 내부의 콜백 인터페이스를 통해 채워집니다. 그리고 첫 출시에서 모든 것이 잘 작동하지만! ...

문제

액티비티가 다시 생성되면 (예 : 방향 변경 등) ViewPager의 조각을 생성합니다. 코드 (아래에서 찾을 수 있음)는 활동이 생성 될 때마다 ViewPager조각과 동일한 새 조각 어댑터 를 만들려고 시도 하지만 (문제 일 수도 있음) FragmentManager는 이미이 조각을 어딘가에 어디에 있습니까 (?) 이를위한 레크리에이션 메커니즘을 시작합니다. 따라서 레크리에이션 메커니즘은 Activity의 구현 된 메소드를 통해 데이터를 시작하기위한 콜백 인터페이스 호출과 함께 "오래된"프래그먼트의 onAttach, onCreateView 등을 호출합니다. 그러나이 메소드는 Activity의 onCreate 메소드를 통해 작성된 새로 작성된 단편을 가리 킵니다.

발행물

어쩌면 나는 잘못된 패턴을 사용하고 있지만 Android 3 Pro 서적에도 그다지 많지 않습니다. 제발 , 제발 펀치를 줘 올바른 방법을 알려주세요. 많은 감사합니다!

암호

주요 활동

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity 일명 도우미

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

어댑터

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

파편

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

해결책

바보 같은 해결책은 putFragment를 사용하여 onSaveInstanceState (호스트 활동)의 조각을 저장하고 getFragment를 통해 onCreate 안에 넣는 것입니다. 그러나 여전히 일이 그렇게 작동해서는 안된다는 이상한 느낌이 있습니다 ... 아래 코드를 참조하십시오 :

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

1
나는 매우 다른 접근법을 사용하거나 onSavedInstancestate를 통해 주 활동 (Dashboard) 조각을 저장하여 onCreate ()에서 사용하려고 시도 해야하는지 궁금합니다. 해당 조각을 저장하고 onCreate의 번들에서 가져 오는 적절한 방법이 있습니까? 그들은 parcelable ... 일하지 않는 것
Oleksii Malovanyi

2
두 번째 접근 방식이 작동합니다. "Sulution"을 참조하십시오. 그러나 그것은 추악한 코드 인 것 같습니다.
Oleksii Malovanyi

1
Android 태그를 정리하려는 노력을 위해 (자세한 내용은 meta.stackexchange.com/questions/100529/… ) 솔루션을 답변으로 게시하고 선택한 것으로 표시 하시겠습니까? 그렇게하면 대답하지 않은 질문으로 표시되지 않습니다 :)
Alexander Lucas

1
예, 괜찮습니다. ... 더 나은 나보다 떨어지게 희망
Oleksii Malovanyi

1
바보 같은 솔루션도 작동합니까? 그것은 널 포인터 예외를 준다 ..
Zhen Liu

답변:


451

FragmentPagerAdapterFragmentManager에 프래그먼트를 추가 할 때 프래그먼트가 배치 될 특정 위치를 기반으로 특수 태그를 사용합니다. FragmentPagerAdapter.getItem(int position)해당 위치의 조각이 존재하지 않는 경우에만 호출됩니다. 회전 한 후 Android는이 특정 위치에 대한 조각을 이미 생성 / 저장 했으므로 새 조각을 FragmentManager.findFragmentByTag()만들지 않고 로 다시 연결을 시도합니다 . 이 모든 것은 사용할 때 무료로 제공 FragmentPagerAdapter되므로 getItem(int)메소드 내부에 조각 초기화 코드를 사용하는 것이 일반적 입니다.

우리가를 사용하지 않더라도 FragmentPagerAdapter, 매번 새로운 조각을 만드는 것은 좋은 생각이 아닙니다 Activity.onCreate(Bundle). 알다시피 FragmentManager에 프래그먼트가 추가되면 회전 후 다시 생성되므로 다시 추가 할 필요가 없습니다. 프래그먼트로 작업 할 때 오류가 발생하는 일반적인 원인입니다.

프래그먼트로 작업 할 때 일반적인 방법은 다음과 같습니다.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

를 사용할 때 FragmentPagerAdapter조각 관리를 어댑터에 포기하고 위 단계를 수행하지 않아도됩니다. 기본적으로 현재 위치 앞뒤에 하나의 프래그먼트 만 미리로드합니다 (을 사용하지 않는 한 프래그먼트를 파괴하지는 않지만 FragmentStatePagerAdapter). 이것은 ViewPager.setOffscreenPageLimit (int)에 의해 제어됩니다 . 이로 인해 어댑터 외부의 프래그먼트에서 직접 호출하는 메소드는 유효하지 않을 수도 있습니다.

간단히 이야기하자면 putFragment나중에 참조를 얻는 데 사용할 수있는 솔루션 은 그리 미친 것이 아니며 어쨌든 조각을 사용하는 일반적인 방법과는 다릅니다. 프래그먼트가 사용자가 아닌 어댑터에 의해 추가되기 때문에 그렇지 않으면 참조를 얻기가 어렵습니다. offscreenPageLimit원하는 조각에 의존하기 때문에 원하는 조각을 항상로드 할 수있을만큼 충분히 높은지 확인하십시오 . 이것은 ViewPager의 게으른 로딩 기능을 우회하지만 응용 프로그램에서 원하는 것 같습니다.

또 다른 방법은 FragmentPageAdapter.instantiateItem(View, int)수퍼 호출에서 리턴 된 프래그먼트에 대한 참조를 리턴하기 전에이를 대체 하고 저장하는 것입니다 (이미 존재하는 경우 프래그먼트를 찾는 논리가 있음).

전체 그림을 보려면 FragmentPagerAdapter (짧은) 및 ViewPager (긴) 의 소스를 살펴보십시오 .


7
마지막 부분을 좋아했습니다. 조각에 대한 캐시가 있었고 내부의 캐시 논리에 넣기 (put)를 옮겼습니다 FragmentPageAdapter.instantiateItem(View, int). 마지막으로 회전 / 구성 변경에만 나타나고 나를 미치게하는 오래 지속되는 버그가 수정되었습니다.
Carlos Sobrinho

2
회전 변경 문제가 해결되었습니다. 어쩌면 내가 볼 수없는 것일 수도 있지만 문서화되어 있습니까? 즉, 태그를 사용하여 이전 상태에서 복구합니까? 아마도 답이 될 수도 있지만 Android 개발에 익숙하지 않습니다.

1
@ Industrial-antidepressant Fragment를 추가 할 컨테이너의 ID입니다 (예 : FrameLayout).
antonyt

1
나에게 그것은 FragmentPageAdapter.instantiateItem(ViewGroup, int)오히려 오히려 이었다 FragmentPageAdapter.instantiateItem(View, int).
표시 이름

1
Btw 조각이 화면에서 스 와이프 될 때 어떤 조각 수명주기 메소드가 호출되는지 알고 있습니까? 아니면 onDetach()다른 것입니까?
ygesher

36

나는 해결책을 제공하기 위해 원하는에 팽창 antonyt멋진 대답 및 재정의 언급 FragmentPageAdapter.instantiateItem(View, int)생성에 대한 참조를 저장 Fragments하면 나중에 작업을 할 수 있습니다. 이것은 또한 작동합니다 FragmentStatePagerAdapter; 자세한 내용은 참고 사항을 참조하십시오.


다음 은의 내부 집합에 의존하지 않는에 Fragments의해 반환되는 참조를 얻는 간단한 예입니다 . 핵심은 에서 대신 에 참조 를 재정의 하고 저장하는 입니다 .FragmentPagerAdaptertagsFragmentsinstantiateItem()getItem()

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

또는tags 클래스 멤버 변수 / 참조 대신 작업하는 것을 선호하는 경우 동일한 방식으로 세트를 Fragments가져올 수도 있습니다 . 참고 :을 만들 때 설정 되지 않으므로 적용 되지 않습니다 .tagsFragmentPagerAdapterFragmentStatePagerAdaptertagsFragments

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

이 방법은 내부 tag집합 을 모방하는 데 의존하지 않으며 FragmentPagerAdapter대신 적절한 API를 사용하여 검색합니다. 이 방법으로 tag향후 버전의 변경 사항 SupportLibrary이 여전히 안전합니다.


잊지 마십시오 당신의 디자인에 따라 것을 ActivityFragments당신이 일에 작업을 시도하고 또는 당신이 그렇게함으로써 그것을 위해 계정에 아직 존재하지 않을 수 있습니다 null귀하의 참조를 사용하기 전에 검사를.

또한 대신 작업하는 경우 많은 참조가있을 수 있고 하드 참조는 불필요하게 메모리에 유지 FragmentStatePagerAdapter하기 Fragments때문에 하드 참조를 유지하고 싶지 않습니다 . 대신 표준 Fragment참조 WeakReference대신 변수에 참조를 저장하십시오 . 이처럼 :

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}

메모리 부족시 안드로이드는 FragmentManager에 사용되지 않은 조각을 제거하도록 요청할 수 있습니다. 내가 맞다면, 두 번째 버전이 작동 할 것이고 첫 번째 버전은 실패 할 것입니다. (현재 안드로이드 버전이이 작업을 수행하는지는 모르겠지만 예상해야 할 것 같습니다.)
Moritz Both

1
FragmetPagerAdapter모든 화면 회전에서 onCreate in Activity를 만드는 것은 어떻 습니까? FragmentPagerAdapter
Bahman

재정의 instantiateItem()는 갈 길입니다. 이를 통해 화면 회전을 처리하고 활동 및 어댑터가 재개되면 기존 Fragment 인스턴스를 검색 할 수있었습니다. 나는 자신을 코드에 주석으로 상기시켰다. 회전 후에 getItem()는 호출되지 않는다; 이 메소드 만 instantiateItem()호출됩니다. instantiateItem()실제로 슈퍼 인스턴스는 회전 후 (필요한 경우) 새 인스턴스를 인스턴스화하는 대신 프래그먼트를 다시 연결합니다!
HelloImKevo

Fragment createdFragment = (Fragment) super.instantiateItem..첫 번째 솔루션에서 null 포인터가 나타납니다 .
AlexS

이 문제의 다양한 복제 / 변형 반복을 모두 수행 한 후에 이것이 가장 좋은 해결책이었습니다. (더 관련성 높은 제목으로 다른 Q와 상호 참조되었으므로 감사합니다!)
MandisaW

18

귀하의 질문에 대해 비교적 쉬운 다른 해결책을 찾았습니다.

FragmentPagerAdapter 소스 코드 에서 볼 수 있듯이 , 다음을 사용하여 생성 된 태그 아래 에 FragmentPagerAdapter상점에서 관리하는 조각이 있습니다 FragmentManager.

String tag="android:switcher:" + viewId + ":" + index;

(가) viewId는 IS container.getId()의는 container당신이다 ViewPager인스턴스입니다. 은 index단편의 위치이다. 따라서 객체 ID를 다음에 저장할 수 있습니다 outState.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

이 프래그먼트와 통신하려면 다음 FragmentManager과 같은 경우에서 얻을 수 있습니다 .

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")

21
흠, 이것이 내부 태그 명명 규칙에 대한 귀하의 답장으로 영원히 그대로 유지되는 좋은 방법이라고 생각하지 않습니다.
Dori

1
내부에 의존하지 않는 솔루션을 찾는 사람들은 내 대답을tag 시도해보십시오 .
Tony Chan

16

답변에 대한 많은 검색으로 인해이 스레드로 이어지기 때문에 약간 다른 경우에 대한 대체 솔루션을 제공하고 싶습니다.

필자의 경우 -동적으로 페이지를 작성 / 추가하고 ViewPager로 슬라이드하고 있지만 (onConfigurationChange) 회전하면 OnCreate가 다시 호출되므로 새 페이지가 생깁니다. 그러나 회전하기 전에 만들어진 모든 페이지를 계속 참조하고 싶습니다.

문제 -내가 만드는 각 조각에 대한 고유 식별자가 없으므로 참조 / 참조하는 유일한 방법은 회전 / 구성 변경 후 복원 할 배열에 참조를 저장하는 것입니다.

해결 방법 -핵심 개념은 활동 (조각을 표시)이 기존 조각에 대한 참조 배열을 관리하도록하는 것이 었습니다.이 활동은 onSaveInstanceState의 번들을 활용할 수 있기 때문입니다.

public class MainActivity extends FragmentActivity

이 활동 내에서 공개 페이지를 추적 할 개인 회원을 선언합니다.

private List<Fragment> retainedPages = new ArrayList<Fragment>();

onSaveInstanceState가 호출되어 onCreate에서 복원 될 때마다 업데이트됩니다.

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... 일단 저장되면 검색 할 수 있습니다 ...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

이것은 주요 활동에 필요한 변경 사항이므로 FragmentPagerAdapter 내에서 멤버와 메소드가 필요합니다.

public class ViewPagerAdapter extends FragmentPagerAdapter

동일한 구성 (MainActivity에서 위에 표시된대로)

private List<Fragment> _pages = new ArrayList<Fragment>();

이 동기화 (onSaveInstanceState에서 사용됨)는 메소드에서 구체적으로 지원됩니다.

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

마지막으로 프래그먼트 클래스에서

public class CustomFragment extends Fragment

이 모든 것이 작동하기 위해서는 두 가지 변화가있었습니다.

public class CustomFragment extends Fragment implements Serializable

그런 다음이를 onCreate에 추가하면 조각이 파괴되지 않습니다.

setRetainInstance(true);

나는 여전히 Fragments와 Android 라이프 사이클에 머리를 감고있는 과정에 있으므로이 방법에는 중복 / 결함이있을 수 있습니다. 그러나 그것은 나를 위해 일하고 내 경우와 비슷한 경우에 다른 사람들에게 도움이되기를 바랍니다.


2
setRetainInstance (true)의 경우 +1-내가 찾은 흑 마법!
declension

FragmentStatePagerAdapter (v13)를 사용하면 훨씬 쉬워졌습니다. 상태 복원 및 릴리스를 처리합니다.
정육점

명확한 설명과 아주 좋은 해결책. 감사합니다!
Bruno Bieri

8

내 솔루션은 매우 무례하지만 작동합니다. 보유 데이터에서 내 조각을 동적으로 생성 PageAdapter하기 때문에 호출하기 전에 모든 조각을 제거한 super.onSaveInstanceState()다음 활동 생성시 다시 만듭니다.

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

에서 이들을 제거 할 수 없습니다 onDestroy(). 그렇지 않으면이 예외가 발생합니다.

java.lang.IllegalStateException: 후에이 작업을 수행 할 수 없습니다 onSaveInstanceState

페이지 어댑터의 코드는 다음과 같습니다.

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

onCreate()조각을 만든 후에 만 현재 페이지를 저장하고에 복원합니다 .

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  

이유를 모르지만 notifyDataSetChanged (); 앱 작동 중단
Malachiasz

4

저게 뭐야 BasePagerAdapter? 표준 호출기 어댑터 중 하나를 사용해야합니다. FragmentPagerAdapter또는 FragmentStatePagerAdapter더 이상 필요하지 않은 조각을 ViewPager보존하거나 (이전) 상태를 저장하고 (후자) 다시 작성하려는 경우에 따라 또는 다시 필요했습니다.

사용 ViewPager을 위한 샘플 코드는 여기 에서 찾을 수 있습니다.

FragmentManager프레임 워크에서 상태를 저장하고 호출기에서 작성한 모든 활성 조각을 복원 하기 때문에 활동 인스턴스에서보기 호출기의 조각 관리가 약간 복잡 하다는 것은 사실입니다. 이 모든 것은 실제로 초기화 할 때 어댑터가 복원 된 조각과 다시 연결해야한다는 것을 의미합니다. 코드를 FragmentPagerAdapter보거나 FragmentStatePagerAdapter수행 방법을 확인할 수 있습니다 .


1
내 질문에 BasePagerAdapter 코드를 사용할 수 있습니다. 보시다시피 TitleProvider 구현을 위해 FragmentPagerAdapter를 확장 합니다. 그래서 모든 것이 이미 안드로이드 개발자가 제안한 접근 방식으로 작동하고 있습니다.
Oleksii Malovanyi

"FragmentStatePagerAdapter"를 몰랐습니다. 당신은 말 그대로 시간을 절약했습니다. 감사.
크노소스

2

FragmentStatePagerAdapter와 관련하여 문제가있는 경우 조각의 상태를 올바르게 복원하지 못하는 경우 ... 예 : FragmentStatePagerAdapter가 상태에서 복원하는 대신 FragmentStatePagerAdapter에서 새 조각을 생성하는 중입니다 ...

전화하기 ViewPager.setOffscreenPageLimit()전에 반드시 전화하십시오ViewPager.setAdapter(fragmentStatePagerAdapter)

호출하면 ViewPager.setOffscreenPageLimit()... ViewPager는 즉시 어댑터를 찾고 조각을 가져 오려고 시도합니다. 이는 ViewPager가 savedInstanceState에서 Fragments를 복원 할 수 있기 전에 발생할 수 있습니다 (따라서 SavedInstanceState에서 새 초기화되어 다시 초기화 할 수없는 새 Fragments를 작성 함).


0

나는이 간단하고 우아한 솔루션을 생각해 냈습니다. 액티비티가 프래그먼트 작성을 담당하고 어댑터가이를 제공한다고 가정합니다.

이것은 어댑터의 코드입니다 ( mFragments활동에 의해 유지되는 프래그먼트 목록을 제외하고는 이상하지 않습니다 )

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

이 스레드의 모든 문제는 "오래된"조각에 대한 참조를 얻는 것이므로 Activity의 onCreate에서이 코드를 사용합니다.

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

물론 조각이 특정 클래스의 인스턴스인지 확인하는 등 필요한 경우이 코드를 추가로 미세 조정할 수 있습니다.


0

방향 변경 후 프래그먼트를 가져 오려면 .getTag ()를 사용해야합니다.

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

조금 더 처리하려면 viewPagerId 및 FragmentClass를 사용하여 모든 위치에서 조각을 가져 오기 위해 PageAdapter에 대한 자체 ArrayList를 작성했습니다.

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

@Override
public int getCount() {
    return this.fragmentPages.size();
}


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

따라서 조각으로 MyPageArrayList를 작성하십시오.

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

viewPager에 추가하십시오.

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

이 후 오리엔테이션 후에 클래스를 사용하여 올바른 조각을 변경할 수 있습니다.

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));

-30

더하다:

   @SuppressLint("ValidFragment")

수업 전에.

다음과 같이 작동하지 않습니다.

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