phân đoạn android- Cách lưu các trạng thái lượt xem trong một đoạn khi một đoạn khác được đẩy lên trên nó


137

Trong Android, một đoạn (nói FragA) được thêm vào backstack và một đoạn khác (nói FragB) xuất hiện trên cùng. Bây giờ đánh trở lại FragAđến đầu và onCreateView()được gọi. Bây giờ tôi đã FragAở trong một trạng thái cụ thể trước khi FragBđược đẩy lên trên nó.

Câu hỏi của tôi là làm thế nào tôi có thể khôi phục lại FragAtrạng thái trước đó? Có cách nào để lưu trạng thái (như nói trong Gói) và nếu vậy thì tôi nên ghi đè phương thức nào?

Câu trả lời:


98

Trong hướng dẫn phân đoạn Ví dụ FragmentList bạn có thể tìm thấy:

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

Mà bạn có thể sử dụng sau này như thế này:

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

Tôi là người mới bắt đầu trong Fragment nhưng có vẻ như là giải pháp cho vấn đề của bạn;) OnActivityCreated được gọi sau khi mảnh vỡ quay trở lại từ stack back.


18
Tôi không thể làm điều này để làm việc được lưuInstanceState luôn là null. Tôi đang thêm đoạn qua bố cục xml. Phải thay đổi mCurCheckP vị trí thành tĩnh thì nó hoạt động, nhưng cảm thấy bị hack.
scottyab

57
nó không gọi onSaveInstanceState - tại sao vậy? Vì vậy, phương pháp này không hiệu quả.
nửa đêm

10
Cách tiếp cận này có thực sự hiệu quả trong trường hợp chúng ta muốn giữ lại trạng thái phân mảnh trong khi quay lại từ một đoạn khác trong cùng Hoạt động không? onSaveInstanceState () chỉ được gọi trong các sự kiện Activity onPause / onStop. Theo tài liệu: "Cũng giống như một hoạt động, bạn có thể giữ lại trạng thái của một mảnh bằng cách sử dụng Gói, trong trường hợp quy trình của hoạt động bị hủy và bạn cần khôi phục trạng thái phân đoạn khi hoạt động được tạo lại. Bạn có thể lưu trạng thái trong khi gọi lại đoạn mã onSaveInstanceState () và khôi phục nó trong thời gian onCreate (), onCreateView () hoặc onActivityCreated (). "
Paramvir Singh

26
Đối với hồ sơ, cách tiếp cận này là sai và không nên có nơi nào gần với số phiếu tăng. onSaveInstanceStatechỉ được gọi khi hoạt động tương ứng của nó cũng ngừng hoạt động.
Martin Konecny

16
onSaveInstanceState () được gọi là onle khi thay đổi cấu hình xảy ra và hoạt động bị hủy, câu trả lời này là sai
Tadas Valaitis

83

Mảnh vỡ onSaveInstanceState(Bundle outState)sẽ không bao giờ được gọi trừ khi hoạt động của mảnh vỡ tự gọi nó và mảnh vỡ kèm theo. Do đó, phương thức này sẽ không được gọi cho đến khi một cái gì đó (thường là xoay vòng) buộc hoạt động SaveInstanceStatevà khôi phục nó sau này. Nhưng nếu bạn chỉ có một hoạt động và một bộ lớn các phân đoạn bên trong nó (với việc sử dụng nhiều replace) và ứng dụng chỉ chạy trong một hoạt động định hướng thì onSaveInstanceState(Bundle outState)có thể không được gọi trong một thời gian dài.

Tôi biết ba cách giải quyết có thể.

Thứ nhất:

sử dụng các đối số của đoạn để giữ dữ liệu quan trọng:

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

Cách thứ hai nhưng ít phạm vi hơn - giữ các biến trong singletons

Thứ ba - không phải là replace()những mảnh vỡ mà add()/ show()/ hide()chúng thay vào đó.


3
Giải pháp tốt nhất khi Fragment.onSaveInstanceState()không bao giờ được gọi. Chỉ cần lưu dữ liệu của riêng bạn vào đối số, bao gồm các mục trong chế độ xem danh sách hoặc chỉ ID của chúng (nếu bạn có trình quản lý dữ liệu tập trung khác). Không cần lưu vị trí của chế độ xem danh sách - đã được lưu và khôi phục tự động.
John Pang

Tôi đã cố gắng sử dụng ví dụ của bạn trong ứng dụng của tôi, nhưng điều này: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);luôn luôn null. Có vấn đề gì vậy?
fragon

Sử dụng đoạn getArguments()này là HOÀN TOÀN theo cách này, bao gồm cả các đoạn được lồng trong ViewPager. Tôi sử dụng 1 hoạt động và trao đổi nhiều mảnh vào / ra và điều này hoạt động hoàn hảo. Đây là một thử nghiệm đơn giản để bạn khám phá mọi giải pháp được đề xuất: 1) đi từ Fragment A đến Fragment B; 2) thay đổi hướng thiết bị hai lần; 3) nhấn nút quay lại trên thiết bị.
Andy H.

Tôi đã thử cách tiếp cận đầu tiên nhưng nó không hiệu quả với tôi. getArgument () luôn trả về null. Nó cũng có ý nghĩa vì đoạn được thay thế và trong onCreate () bạn đặt Bundle mới để Bundle cũ bị mất. Tôi đang thiếu gì và tôi có sai không?
Zvi

@Zvi, tôi đã viết mã này 1,5 năm trước và không nhớ tất cả các chi tiết, nhưng như tôi nhớ, thay thế không tạo lại các đoạn, đoạn được tạo lại chỉ khi bạn đã tạo phiên bản mới từ mã của mình. Trong trường hợp này, rõ ràng, nhà xây dựng đã gọi và setArguments(new Bundle());ghi đè lên Bundle cũ. Vì vậy, hãy chắc chắn rằng bạn đã tạo phân đoạn chỉ một lần, và sau đó sử dụng ví dụ này thay vì tạo mới mỗi lần.
Fyodor Volchyok

20

Chỉ cần lưu ý rằng nếu bạn làm việc với Fragment bằng ViewPager, điều đó khá dễ dàng. Bạn chỉ cần gọi phương thức này : setOffscreenPageLimit().

Phù hợp với các tài liệu:

Đặt số lượng trang nên được giữ lại ở một trong hai bên của trang hiện tại trong cấu trúc phân cấp chế độ xem ở trạng thái không hoạt động. Các trang vượt quá giới hạn này sẽ được tạo lại từ bộ điều hợp khi cần.

Vấn đề tương tự ở đây


5
Cái này khác. set OfferScreenPageLimit hoạt động như một bộ đệm (nghĩa là có bao nhiêu trang mà ViewPager phải xử lý tại một thời điểm nhất định) nhưng không được sử dụng để lưu trạng thái của một Đoạn.
Renaud Mathieu

Trong trường hợp của tôi, nó hoạt động với setPackscreenPageLimit () - mặc dù các đoạn bị phá hủy, trạng thái xem đã được lưu và khôi phục.
Davincho

Cảm ơn, đã giúp tôi quá.
Ê-li

Nhiều năm sau và điều này vẫn còn liên quan. Mặc dù nó không thực sự trả lời câu hỏi, nhưng nó giải quyết được một vấn đề
Cá heo tối cao

19

Chỉ cần thổi phồng View của bạn một lần.

Ví dụ sau:

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

}


cũng nên giữ các đoạn hiện có trong mảng hoặc một cái gì đó
Amir

Tôi đã nghe nói rằng việc giữ một tham chiếu đến rootView của một đoạn là một thực tiễn tồi - có thể dẫn đến rò rỉ [cần dẫn nguồn]?
hươu cao cổ.guru

1
@ gi hươu.guru Fragmentđề cập đến quan điểm gốc của nó sẽ không làm điều đó. Trong khi tham chiếu của một số phần tử gốc GC sẽ, như thuộc tính tĩnh toàn cục, biến chủ đề không ui. Một Fragmentthể hiện không phải là gốc GC, vì vậy nó có thể là rác được thu thập. Vì vậy, sẽ xem gốc của nó.
Lym Zoy

Bạn cùng ngày của tôi.
Vijendra patidar

Ngọt ngào và đơn giản.
Rupam Das

8

Tôi đã làm việc với một vấn đề rất giống với điều này. Vì tôi biết rằng tôi sẽ thường xuyên quay trở lại một mảnh trước đó, tôi đã kiểm tra xem liệu mảnh .isAdded()đó có đúng không, và nếu vậy, thay vì làm transaction.replace()tôi chỉ làm một transaction.show(). Điều này giữ cho đoạn không bị tái tạo nếu nó đã có trên ngăn xếp - không cần tiết kiệm trạng thái.

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

Một lưu ý khác là trong khi điều này bảo tồn trật tự tự nhiên cho các mảnh vỡ, bạn vẫn có thể cần xử lý chính hoạt động bị phá hủy và được tạo lại khi thay đổi hướng (config). Để giải quyết vấn đề này trong AndroidManifest.xml cho nút của bạn:

android:configChanges="orientation|screenSize"

Trong Android 3.0 trở lên, screenSizerõ ràng là bắt buộc.

Chúc may mắn


giao dịch.addToBackStack (button_id + "stack_item"); // dòng này làm gì. Nút_id ở đây là gì?
raghu_3

button_id chỉ là một biến tạo nên. Đối số chuỗi được truyền cho addToBackStack chỉ là một tên tùy chọn cho trạng thái backstack - bạn có thể đặt nó thành null nếu bạn chỉ quản lý một backstack duy nhất.
rmirabelle

, tôi đang gặp vấn đề tương tự như vậy với các mảnh vỡ, bạn có thể vui lòng xem xét nó không - stackoverflow.com/questions/22468977/ chủ
raghu_3 18/03/14

1
Đừng bao giờ thêm android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsvào bảng kê khai của bạn. Thay vào đó, hãy tìm hiểu cách lưu và khôi phục trạng thái, ví dụ từ đây: loadeck.com/cyrilmottier/ Khăn
Marcin Koziński

Và sau đó, khi bạn đã học được trạng thái tiết kiệm cực kỳ khó sử dụng và giải pháp được trình bày sẽ giải quyết vấn đề một cách sạch sẽ nhất trong tình huống cụ thể của bạn, hãy tiếp tục và sử dụng nó, mà không cần quan tâm đến những người không đồng ý ;-)
rmirabelle

5

Giải pháp tốt nhất tôi tìm thấy là dưới đây:

onSattedInstanceState (): luôn được gọi bên trong đoạn khi hoạt động sẽ tắt (Di chuyển hoạt động từ cái này sang cái khác hoặc thay đổi cấu hình). Vì vậy, nếu chúng ta đang gọi nhiều đoạn trên cùng một hoạt động thì chúng ta phải sử dụng cách tiếp cận sau:

Sử dụng OnDestroyView () của đoạn và lưu toàn bộ đối tượng bên trong phương thức đó. Sau đó OnActivityCreated (): Kiểm tra xem đối tượng có null hay không (Bởi vì phương thức này gọi mỗi lần). Bây giờ khôi phục trạng thái của một đối tượng ở đây.

Nó hoạt động luôn!


4

nếu bạn đang xử lý các thay đổi cấu hình trong hoạt động phân đoạn của mình được chỉ định trong tệp kê khai Android như thế này

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

sau đó onSaveInstanceState đoạn này sẽ không được gọi và savedInstanceStateđối tượng sẽ luôn là null.


1

tôi không nghĩ onSaveInstanceState là một giải pháp tốt. nó chỉ sử dụng cho hoạt động đã bị phá hủy.

Từ Android 3.0, Fragmen đã được quản lý bởi FragmentManager, điều kiện là: một hoạt động ánh xạ các đoạn manny, khi đoạn được thêm vào (không thay thế: nó sẽ được tạo lại) trong backStack, chế độ xem sẽ bị hủy. khi trở lại cái cuối cùng, nó sẽ hiển thị như trước.

Vì vậy, tôi nghĩ rằng FragmentManger và giao dịch là đủ tốt để xử lý nó.


0

Tôi đã sử dụng một phương pháp lai cho các đoạn chứa chế độ xem danh sách. Nó dường như là hiệu suất vì tôi không thay thế đoạn hiện tại mà chỉ thêm đoạn mới và ẩn đoạn hiện tại. Tôi có phương pháp sau trong hoạt động lưu trữ các đoạn của mình:

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

Tôi sử dụng phương pháp này trong đoạn của mình (chứa chế độ xem danh sách) bất cứ khi nào một mục danh sách được nhấp / gõ (và do đó tôi cần khởi chạy / hiển thị đoạn chi tiết):

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()trả về một chuỗi các chuỗi mà tôi sử dụng làm thẻ cho các đoạn khác nhau khi tôi thêm một đoạn mới (xem transaction.addphương thức trongaddFragment phương thức trên).

Trong đoạn chứa khung nhìn danh sách, tôi thực hiện điều này trong phương thức onPause () của nó:

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

Sau đó, trong onCreateView của đoạn (thực ra là trong một phương thức được gọi trong onCreateView), tôi khôi phục trạng thái:

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

Cuối cùng, sau khi thử nhiều giải pháp phức tạp này, tôi chỉ cần lưu / khôi phục một giá trị duy nhất trong Mảnh vỡ của mình (nội dung của EditText), và mặc dù nó có thể không phải là giải pháp thanh lịch nhất, tạo ra SharedPreference và lưu trữ trạng thái của tôi ở đó làm việc cho tôi


0

Một cách đơn giản để giữ các giá trị của các trường trong các đoạn khác nhau trong một hoạt động

Tạo các trường hợp của các mảnh và thêm thay vì thay thế và loại bỏ

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

Sau đó, chỉ hiển thị và ẩn các đoạn thay vì thêm và xóa chúng một lần nữa

    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
Vui lòng xem xét bao gồm một số thông tin về câu trả lời của bạn, thay vì chỉ đăng mã. Chúng tôi cố gắng cung cấp không chỉ "bản sửa lỗi", mà còn giúp mọi người tìm hiểu. Bạn nên giải thích những gì sai trong mã gốc, những gì bạn đã làm khác đi và tại sao (các) thay đổi của bạn hoạt động.
Andrew Barber
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.