ViewPager và các đoạn - cách nào đúng để lưu trữ trạng thái của đoạn?


492

Các đoạn dường như rất hay để tách logic UI thành một số mô-đun. Nhưng cùng với ViewPagervòng đời của nó vẫn còn mù mờ đối với tôi. Vì vậy, những suy nghĩ của Đạo sư rất cần thiết!

Biên tập

Xem giải pháp ngu ngốc dưới đây ;-)

Phạm vi

Hoạt động chính có một ViewPagermảnh. Các đoạn đó có thể triển khai logic khác nhau một chút cho các hoạt động (chìm) khác, vì vậy dữ liệu của các đoạn được điền thông qua giao diện gọi lại bên trong hoạt động. Và mọi thứ hoạt động tốt trong lần ra mắt đầu tiên, nhưng! ...

Vấn đề

Khi hoạt động được tạo lại (ví dụ: thay đổi hướng) ViewPager, các mảnh vỡ cũng vậy. Mã (bạn sẽ tìm thấy bên dưới) nói rằng mỗi khi hoạt động được tạo, tôi cố gắng tạo ViewPagerbộ điều hợp đoạn mới giống như đoạn (có thể đây là vấn đề) nhưng FragmentManager đã lưu trữ tất cả các đoạn này ở đâu đó (ở đâu?) Và bắt đầu cơ chế giải trí cho những người. Vì vậy, cơ chế giải trí gọi đoạn "cũ" là onAttach, onCreateView, v.v. bằng lệnh gọi giao diện gọi lại của tôi để bắt đầu dữ liệu thông qua phương thức được triển khai của Activity. Nhưng phương thức này chỉ ra đoạn mới được tạo thông qua phương thức onCreate của Activity.

Vấn đề

Có thể tôi đang sử dụng sai mẫu nhưng ngay cả sách Android 3 Pro cũng không có nhiều về nó. Vì vậy, xin vui lòng , cho tôi một hai cú đấm và chỉ ra làm thế nào để làm điều đó đúng cách. Cảm ơn nhiều!

Hoạt động chính

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 aka người trợ giúp

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

Bộ chuyển đổi

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
}

}

Miếng

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... */
}

Giải pháp

Giải pháp ngu ngốc là lưu các đoạn bên trong onSaveInstanceState (của máy chủ Hoạt động) với putFragment và đưa chúng vào bên trong onCreate thông qua getFragment. Nhưng tôi vẫn có một cảm giác kỳ lạ rằng mọi thứ không nên hoạt động như vậy ... Xem mã dưới đây:

    @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
Bây giờ tôi tự hỏi: tôi nên sử dụng một cách tiếp cận rất khác hoặc cố gắng lưu các đoạn hoạt động chính (Bảng điều khiển) thông qua onSattedInstancestate để sử dụng chúng trong onCreate (). Có cách nào thích hợp để lưu những mảnh vỡ đó và lấy chúng từ gói trong onCreate không? Chúng dường như không thể phân phối được ...
Oleksii Malovanyi

2
Cách tiếp cận thứ 2 hoạt động - xem "Sự ô nhiễm". Nhưng nó có vẻ là một đoạn mã xấu xí phải không?
Oleksii Malovanyi

1
Vì nỗ lực dọn dẹp thẻ Android (chi tiết tại đây: meta.stackexchange.com/questions/100529/ mẹo ), bạn có phiền khi đăng giải pháp của mình dưới dạng câu trả lời và đánh dấu là giải pháp đã chọn không? Bằng cách đó, nó sẽ không xuất hiện dưới dạng một câu hỏi chưa được trả lời :)
Alexander Lucas

1
Vâng, nghĩ rằng nó ổn. Hy vọng cho smth tốt hơn của tôi ...
Oleksii Malovanyi

1
Liệu giải pháp câm thậm chí hoạt động? Nó cho tôi một ngoại lệ con trỏ null ..
Zhen Liu

Câu trả lời:


451

Khi FragmentPagerAdapterthêm một đoạn vào FragmentManager, nó sẽ sử dụng một thẻ đặc biệt dựa trên vị trí cụ thể mà đoạn đó sẽ được đặt. FragmentPagerAdapter.getItem(int position)chỉ được gọi khi một đoạn cho vị trí đó không tồn tại. Sau khi xoay, Android sẽ nhận thấy rằng nó đã được tạo / lưu một đoạn cho vị trí cụ thể này và do đó, nó chỉ đơn giản là cố gắng kết nối lại với nó FragmentManager.findFragmentByTag(), thay vì tạo một vị trí mới. Tất cả điều này đều miễn phí khi sử dụng FragmentPagerAdaptervà là lý do tại sao thường có mã khởi tạo đoạn của bạn bên trong getItem(int)phương thức.

Ngay cả khi chúng tôi không sử dụng a FragmentPagerAdapter, thì cũng không nên tạo ra một mảnh mới mỗi lần vào Activity.onCreate(Bundle). Như bạn đã nhận thấy, khi một đoạn được thêm vào FragmentManager, nó sẽ được tạo lại cho bạn sau khi xoay và không cần phải thêm lại. Làm như vậy là một nguyên nhân phổ biến của lỗi khi làm việc với các mảnh.

Một cách tiếp cận thông thường khi làm việc với các đoạn là:

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

    ...

}

Khi sử dụng a FragmentPagerAdapter, chúng tôi từ bỏ quản lý phân đoạn cho bộ điều hợp và không phải thực hiện các bước trên. Theo mặc định, nó sẽ chỉ tải trước một Fragment ở phía trước và phía sau vị trí hiện tại (mặc dù nó không phá hủy chúng trừ khi bạn đang sử dụng FragmentStatePagerAdapter). Điều này được kiểm soát bởi ViewPager.setPackscreenPageLimit (int) . Do đó, các phương thức gọi trực tiếp trên các đoạn bên ngoài bộ chuyển đổi không được đảm bảo là hợp lệ, bởi vì chúng thậm chí có thể không còn tồn tại.

Để cắt ngắn một câu chuyện dài, giải pháp của bạn để sử dụng putFragmentđể có thể có được một tài liệu tham khảo sau đó không phải là quá điên rồ, và không giống như cách thông thường để sử dụng các đoạn dù sao (ở trên). Rất khó để có được một tài liệu tham khảo khác bởi vì đoạn được thêm bởi bộ điều hợp, và không phải cá nhân bạn. Chỉ cần đảm bảo rằng nó offscreenPageLimitđủ cao để tải các mảnh mong muốn của bạn mọi lúc, vì bạn dựa vào nó hiện diện. Điều này bỏ qua khả năng tải lười biếng của ViewPager, nhưng dường như là những gì bạn mong muốn cho ứng dụng của mình.

Một cách tiếp cận khác là ghi đè FragmentPageAdapter.instantiateItem(View, int)và lưu tham chiếu đến đoạn được trả về từ siêu cuộc gọi trước khi trả lại (nó có logic để tìm đoạn, nếu đã có).

Để có bức ảnh đầy đủ hơn, hãy xem một số nguồn của FragmentPagerAd CHƯƠNG (ngắn) và ViewPager (dài).


7
Yêu phần cuối cùng. Có một bộ đệm cho các đoạn và di chuyển logic đặt trong bộ đệm vào bên trong FragmentPageAdapter.instantiateItem(View, int). Cuối cùng đã sửa một lỗi kéo dài chỉ xuất hiện trong thay đổi xoay / cấu hình và khiến tôi phát điên ...
Carlos Sobrinho

2
Điều này đã khắc phục sự cố tôi gặp phải khi thay đổi vòng quay. Có lẽ tôi không thể nhìn thấy, nhưng điều này có được ghi lại không? tức là sử dụng thẻ để phục hồi từ trạng thái trước? Nó có thể là một câu trả lời rõ ràng, nhưng tôi khá mới đối với phát triển Android.

1
@ Thuốc chống trầm cảm công nghiệp Đó là id của container (ví dụ: FrameLayout) mà Fragment sẽ được thêm vào.
antonyt

1
btw, đối với tôi nó là FragmentPageAdapter.instantiateItem(ViewGroup, int)hơn FragmentPageAdapter.instantiateItem(View, int).
Tên hiển thị

1
Btw bạn có biết phương thức vòng đời mảnh (nếu có) được gọi khi đoạn bị xóa khỏi màn hình không? Là nó onDetach(), hay cái gì khác?
ygesher

36

Tôi muốn đưa ra một giải pháp mà nở trên antonyt's câu trả lời tuyệt vời và đề cập đến trọng FragmentPageAdapter.instantiateItem(View, int)để lưu tài liệu tham khảo để tạo ra Fragments, do đó bạn có thể làm việc trên chúng sau này. Điều này cũng nên làm việc với FragmentStatePagerAdapter; xem ghi chú để biết chi tiết


Đây là một ví dụ đơn giản về cách lấy tham chiếu đến Fragmentstrả về FragmentPagerAdaptermà không phụ thuộc vào bộ nội bộ tagstrên Fragments. Điều quan trọng là ghi đè instantiateItem()và lưu tài liệu tham khảo trong đó thay vì trong 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();
        }
    }
}

hoặc nếu bạn thích làm việc với tagsthay vì các biến / tham chiếu của thành viên lớp, Fragmentsbạn cũng có thể lấy tagstập hợp FragmentPagerAdaptertheo cách tương tự: LƯU Ý: điều này không áp dụng FragmentStatePagerAdaptervì nó không được đặt tagskhi tạo Fragments.

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

Lưu ý rằng phương pháp này không dựa vào bắt chước nội tagbộ bằng FragmentPagerAdaptervà thay vào đó sử dụng API thích hợp để lấy chúng. Bằng cách này, ngay cả khi những tagthay đổi trong các phiên bản trong tương lai của SupportLibrarybạn vẫn sẽ an toàn.


Đừng quên rằng tùy thuộc vào thiết kế của bạn Activity, Fragmentsbạn đang cố gắng làm việc có thể tồn tại hoặc chưa tồn tại, vì vậy bạn phải tính đến điều đó bằng cách nullkiểm tra trước khi sử dụng tài liệu tham khảo của mình.

Ngoài ra, nếu thay vào đó bạn đang làm việc cùng FragmentStatePagerAdapter, thì bạn không muốn giữ các tài liệu tham khảo cứng cho bạn Fragmentsvì bạn có thể có nhiều tài liệu tham khảo và các tài liệu tham khảo cứng sẽ không cần thiết giữ chúng trong bộ nhớ. Thay vào đó lưu các Fragmenttham chiếu trong WeakReferencecác biến thay vì các tiêu chuẩn. Như thế này:

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

Android thiếu bộ nhớ có thể yêu cầu FragmentManager phá hủy các mảnh không sử dụng, phải không? Nếu tôi đúng, phiên bản thứ hai của bạn sẽ hoạt động, phiên bản đầu tiên có thể thất bại. (Không biết các phiên bản Android hiện tại có làm điều này không, nhưng có vẻ như chúng ta phải mong đợi điều đó.)
Moritz Cả

1
những gì về việc tạo một FragmetPagerAdapteronCreate của Activity trên mỗi vòng quay màn hình. Điều này có sai không vì nó có thể bỏ qua việc sử dụng lại các đoạn đã được thêm vàoFragmentPagerAdapter
Bahman

Ghi đè instantiateItem()là con đường để đi; điều này giúp tôi xử lý các xoay màn hình và truy xuất các phiên bản Mảnh vỡ hiện có của tôi sau khi Hoạt động và Bộ điều hợp được nối lại; Tôi đã để lại cho mình những bình luận trong mã như một lời nhắc nhở: Sau khi xoay vòng, getItem()KHÔNG được gọi; chỉ có phương pháp này instantiateItem()được gọi. Việc thực hiện siêu thực instantiateItem()sự gắn lại các đoạn sau khi xoay (nếu cần), thay vì khởi tạo các thể hiện mới!
HelloImKevo

Tôi đang nhận được con trỏ null Fragment createdFragment = (Fragment) super.instantiateItem..trong giải pháp đầu tiên.
AlexS

Sau khi tìm kiếm tất cả các lần lặp lại / biến thể khác nhau của vấn đề này, đây là giải pháp tốt nhất. (Tham chiếu chéo từ một Q khác với tiêu đề phù hợp hơn, vì vậy cảm ơn bạn vì điều đó!)
MandisaW

18

Tôi tìm thấy một giải pháp tương đối dễ dàng cho câu hỏi của bạn.

Như bạn có thể thấy từ mã nguồn FragmentPagerAdOG , các đoạn được quản lý bởi FragmentPagerAdaptercửa hàng trong FragmentManagerthẻ được tạo bằng:

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

Đây viewIdcontainer.getId(), ví dụ containercủa bạn ViewPager. Đây indexlà vị trí của mảnh. Do đó bạn có thể lưu id đối tượng vào 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);

Nếu bạn muốn liên lạc với đoạn này, bạn có thể lấy nếu từ FragmentManager, chẳng hạn như:

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

21
hmmm Tôi không nghĩ rằng đây là một cách tốt để đi vì bạn trả lời một quy ước đặt tên thẻ nội bộ mà không có nghĩa là sẽ giữ nguyên như vậy mãi mãi.
Dori

1
Đối với những người tìm kiếm một giải pháp không dựa vào nội bộ tag, hãy xem xét thử câu trả lời của tôi .
Tony Chan

16

Tôi muốn đưa ra một giải pháp thay thế cho một trường hợp hơi khác, vì nhiều tìm kiếm cho câu trả lời của tôi tiếp tục dẫn tôi đến chủ đề này.

Trường hợp của tôi - Tôi đang tạo / thêm các trang một cách linh hoạt và trượt chúng vào ViewPager, nhưng khi được xoay (onConfigurationChange) tôi kết thúc với một trang mới vì dĩ nhiên OnCreate được gọi lại. Nhưng tôi muốn tiếp tục tham khảo tất cả các trang đã được tạo trước khi xoay vòng.

Vấn đề - Tôi không có số nhận dạng duy nhất cho mỗi phân đoạn tôi tạo, vì vậy cách duy nhất để tham chiếu là bằng cách nào đó lưu trữ các tham chiếu trong Mảng để được khôi phục sau khi thay đổi xoay / cấu hình.

Giải pháp thay thế - Khái niệm chính là có Activity (hiển thị các Đoạn) cũng quản lý mảng tham chiếu đến các Đoạn hiện có, vì hoạt động này có thể sử dụng Gói trong onSaveInstanceState

public class MainActivity extends FragmentActivity

Vì vậy, trong Hoạt động này, tôi tuyên bố một thành viên riêng để theo dõi các trang đang mở

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

Điều này được cập nhật mỗi lần onSaveInstanceState được gọi và khôi phục trong onCreate

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

... Vì vậy, một khi nó được lưu trữ, nó có thể được lấy ra ...

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

Đây là những thay đổi cần thiết cho hoạt động chính và vì vậy tôi cần các thành viên và phương thức trong FragmentPagerAd CHƯƠNG của tôi để hoạt động này, vì vậy trong phạm vi

public class ViewPagerAdapter extends FragmentPagerAdapter

một cấu trúc giống hệt nhau (như được hiển thị ở trên trong MainActivity)

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

và đồng bộ hóa này (như được sử dụng ở trên trong onSaveInstanceState) được hỗ trợ cụ thể bằng các phương thức

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

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

Và cuối cùng, trong lớp phân mảnh

public class CustomFragment extends Fragment

để tất cả điều này hoạt động, có hai thay đổi, đầu tiên

public class CustomFragment extends Fragment implements Serializable

và sau đó thêm phần này vào onCreate để Fragment không bị phá hủy

setRetainInstance(true);

Tôi vẫn đang trong quá trình xoay quanh vòng đời của Fragment và Android, vì vậy hãy cẩn thận ở đây là có thể có sự dư thừa / không hiệu quả trong phương pháp này. Nhưng nó hiệu quả với tôi và tôi hy vọng có thể hữu ích cho những người khác với những trường hợp tương tự như tôi.


2
+1 cho setRetainInstance (đúng) - ma thuật đen mà tôi đang tìm kiếm!
giảm

Nó thậm chí còn dễ dàng hơn bằng cách sử dụng FragmentStatePagerAd CHƯƠNG (v13). Thỏa thuận nào cho bạn với việc khôi phục và phát hành nhà nước.
Đồ tể

Mô tả rõ ràng và giải pháp rất tốt đẹp. Cảm ơn bạn!
Bruno Bieri

8

Giải pháp của tôi rất thô lỗ nhưng hoạt động: là các mảnh của tôi được tạo động từ dữ liệu được giữ lại, tôi chỉ cần xóa tất cả các đoạn khỏi PageAdaptertrước khi gọi super.onSaveInstanceState()và sau đó tạo lại chúng khi tạo hoạt động:

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

Bạn không thể xóa chúng trong onDestroy(), nếu không bạn sẽ có ngoại lệ này:

java.lang.IllegalStateException: Không thể thực hiện hành động này sau onSaveInstanceState

Đây là mã trong bộ điều hợp trang:

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

Tôi chỉ lưu trang hiện tại và khôi phục nó trong onCreate(), sau khi các mảnh đã được tạo.

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

Tôi không biết tại sao, nhưng thông báoDataSetChanged (); khiến ứng dụng bị sập
Malachiasz

4

Đó là cái BasePagerAdaptergì Bạn nên sử dụng một trong các bộ điều hợp máy nhắn tin tiêu chuẩn - FragmentPagerAdapterhoặc FragmentStatePagerAdapter, tùy thuộc vào việc bạn muốn những mảnh vỡ không còn cần thiết ViewPagerđể giữ xung quanh (cái trước) hay được lưu trạng thái của chúng (cái sau) và được tạo lại nếu cần thiết một lần nữa.

Mã mẫu để sử dụng ViewPagercó thể được tìm thấy ở đây

Đúng là việc quản lý các đoạn trong máy nhắn tin xem qua các trường hợp hoạt động hơi phức tạp, bởi vì FragmentManagertrong khung làm việc có nhiệm vụ lưu trạng thái và khôi phục bất kỳ đoạn hoạt động nào mà máy nhắn tin đã thực hiện. Tất cả điều này thực sự có nghĩa là bộ điều hợp khi khởi tạo cần phải đảm bảo nó kết nối lại với bất kỳ đoạn được khôi phục nào. Bạn có thể nhìn vào mã cho FragmentPagerAdapterhoặc FragmentStatePagerAdapterđể xem làm thế nào được thực hiện.


1
Mã BasePagerAdOG có sẵn trong câu hỏi của tôi. Như người ta có thể thấy nó chỉ đơn giản là mở rộng FragmentPagerAd CHƯƠNG cho mục đích triển khai TitleProvider . Vì vậy, mọi thứ đã được làm việc với cách tiếp cận đề xuất Android dev.
Oleksii Malovanyi

Tôi không biết về "FragmentStatePagerAdOG". Bạn đã cứu tôi theo nghĩa đen hàng giờ. Cảm ơn.
Knossos

2

Nếu bất cứ ai gặp vấn đề với FragmentStatePagerAd CHƯƠNG của họ không khôi phục chính xác trạng thái của các mảnh vỡ của nó ... tức là ... các mảnh mới đang được FragmentStatePagerAd CHƯƠNG tạo ra thay vì khôi phục chúng từ trạng thái ...

Hãy chắc chắn rằng bạn gọi ViewPager.setOffscreenPageLimit()TRƯỚC KHI bạn gọiViewPager.setAdapter(fragmentStatePagerAdapter)

Khi gọi ViewPager.setOffscreenPageLimit()... ViewPager sẽ ngay lập tức tìm đến bộ điều hợp của nó và cố gắng lấy các mảnh vỡ của nó. Điều này có thể xảy ra trước khi ViewPager có cơ hội khôi phục các Fragment từ yetInstanceState (do đó tạo ra các Fragment mới không thể được khởi tạo lại từ SavingInstanceState vì chúng là mới).


0

Tôi đã đưa ra giải pháp đơn giản và thanh lịch này. Nó giả định rằng hoạt động chịu trách nhiệm tạo ra các Mảnh vỡ và Bộ điều hợp chỉ phục vụ chúng.

Đây là mã của bộ điều hợp (không có gì lạ ở đây, ngoại trừ thực tế đó mFragmentslà danh sách các đoạn được duy trì bởi Hoạt động)

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

Toàn bộ vấn đề của chủ đề này là nhận được một tham chiếu của các đoạn "cũ", vì vậy tôi sử dụng mã này trong onCreate của Activity.

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

Tất nhiên bạn có thể tinh chỉnh thêm mã này nếu cần, ví dụ: đảm bảo các đoạn là các thể hiện của một lớp cụ thể.


0

Để có được các đoạn sau khi thay đổi hướng, bạn phải sử dụng .getTag ().

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

Để xử lý nhiều hơn một chút, tôi đã viết ArrayList của riêng mình cho PageAd CHƯƠNG của tôi để lấy đoạn bằng viewPagerId và FragmentClass ở bất kỳ Vị trí nào:

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

Vì vậy, chỉ cần tạo MyPageArrayList với các đoạn:

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

và thêm chúng vào viewPager:

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

sau này, bạn có thể nhận được sau khi định hướng thay đổi đoạn chính xác bằng cách sử dụng lớp của nó:

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

-30

thêm vào:

   @SuppressLint("ValidFragment")

trước lớp học của bạn

Nó không hoạt động làm một cái gì đó như thế này:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
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.