android fragment- วิธีบันทึกสถานะการดูในแฟรกเมนต์เมื่อแฟรกเมนต์อื่นถูกดันบนสุดของมัน


137

ใน Android FragAจะมีการเพิ่มแฟรกเมนต์(พูด) ลงใน backstack และแฟรกเมนต์อื่น (พูดFragB) จะขึ้นไปด้านบน ตอนนี้กดปุ่มกลับFragAมาที่ด้านบนและonCreateView()เรียกว่า ตอนนี้ฉันมีFragAสถานะที่เฉพาะเจาะจงก่อนFragBถูกผลักขึ้นไปบนมัน

คำถามของฉันคือฉันFragAจะคืนค่าสู่สถานะก่อนหน้าได้อย่างไร มีวิธีการบันทึกสถานะ (เช่นพูดใน Bundle) และถ้าเป็นเช่นนั้นแล้ววิธีการที่ฉันควรจะแทนที่?

คำตอบ:


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 แต่ดูเหมือนว่าวิธีแก้ปัญหาของคุณ;) OnActivityCreated ถูกเรียกใช้หลังจากที่ Fragment ส่งคืนจาก Back stack


18
ฉันไม่สามารถทำให้เรื่องนี้ทำงานได้ ฉันกำลังเพิ่มแฟรกเมนต์ผ่านโครงร่าง xml ต้องเปลี่ยน mCurCheckPosition ให้เป็นแบบสแตติกแล้วก็ใช้งานได้ แต่รู้สึกว่าแฮ็ค
scottyab

57
มันไม่ได้เรียกว่า SaveInstanceState - ทำไมถึงเป็นอย่างนั้น? ดังนั้นวิธีนี้ใช้ไม่ได้ผล
เที่ยงคืน

10
วิธีการนี้จะใช้งานได้จริงหรือไม่ในกรณีที่เราต้องการรักษาสถานะของแฟรกเมนต์ขณะที่กลับมาจากแฟรกเมนต์อื่นในกิจกรรมเดียวกัน onSaveInstanceState () ได้รับการเรียกบนกิจกรรม Activity onPause / onStop เท่านั้น ตามเอกสารประกอบ: "เช่นเดียวกับกิจกรรมคุณสามารถรักษาสถานะของแฟรกเมนต์โดยใช้ Bundle ในกรณีที่กระบวนการของกิจกรรมถูกทำลายและคุณจำเป็นต้องกู้คืนสถานะแฟรกเมนต์เมื่อกิจกรรมถูกสร้างขึ้นใหม่คุณสามารถบันทึกสถานะในระหว่าง การเรียกกลับของ onSaveInstanceState () ของแฟรกเมนต์และกู้คืนระหว่าง onCreate (), onCreateView () หรือ onActivityCreated ()
Paramvir Singh

26
สำหรับบันทึกวิธีการนี้เป็นสิ่งที่ผิดและไม่ควรอยู่ใกล้ที่มีการโหวตด้วย onSaveInstanceStateจะถูกเรียกเฉพาะเมื่อกิจกรรมที่เกี่ยวข้องนั้นปิดตัวลงเช่นกัน
Martin Konecny

16
onSaveInstanceState () ถูกเรียกว่า onle เมื่อการเปลี่ยนแปลงการตั้งค่าเกิดขึ้นและกิจกรรมถูกทำลายคำตอบนี้เป็นสิ่งที่ผิด
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);
    }
}

วิธีที่สองที่น้อยกว่า แต่น้อย - ถือตัวแปรใน singletons

ที่สาม - อย่าreplace()แยกชิ้นส่วน แต่add()/ show()/ hide()พวกเขาแทน


3
ทางออกที่ดีที่สุดเมื่อFragment.onSaveInstanceState()ไม่เคยถูกเรียก เพียงบันทึกข้อมูลของคุณเองไปยังอาร์กิวเมนต์รวมถึงรายการในมุมมองรายการหรือเพียงแค่ ID ของพวกเขา (ถ้าคุณมีตัวจัดการข้อมูลอื่น ๆ รวมศูนย์) ไม่จำเป็นต้องบันทึกตำแหน่งมุมมองรายการ - ที่บันทึกและเรียกคืนโดยอัตโนมัติ
John Pang

ผมพยายามที่จะใช้ตัวอย่างของคุณใน app ของฉัน แต่นี้อยู่เสมอString persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY); nullมีปัญหาอะไร?
fragon

ใช้getArguments()แฟรกเมนต์ที่เป็นวิธีที่แน่นอนที่จะไปรวมถึงชิ้นส่วนที่ซ้อนกันใน ViewPager ฉันใช้ 1 กิจกรรมและสลับชิ้นส่วนเข้า / ออกจำนวนมากและทำงานได้อย่างสมบูรณ์แบบ นี่คือการทดสอบง่ายๆสำหรับคุณที่จะหาวิธีแก้ปัญหาที่เสนอ: 1) เริ่มจาก Fragment A ถึง Fragment B; 2) เปลี่ยนการวางแนวอุปกรณ์สองครั้ง; 3) กดปุ่มย้อนกลับบนอุปกรณ์
แอนดี้เอช

ฉันลองวิธีแรก แต่ก็ไม่ได้ผลสำหรับฉัน getArguments () ส่งคืนค่า null เสมอ นอกจากนี้ยังมีเหตุผลเนื่องจากแฟรกเมนต์ถูกแทนที่และใน onCreate () คุณตั้งค่าบันเดิลใหม่เพื่อให้บันเดิลเก่าหายไป ฉันกำลังทำอะไรอยู่และฉันคิดผิด?
Zvi

@Zvi ฉันเขียนรหัสนี้เมื่อ 1,5 ปีก่อนและจำรายละเอียดทั้งหมดไม่ได้ แต่อย่างที่ฉันจำได้ว่าการแทนที่จะไม่สร้างแฟรกเมนต์ใหม่ชิ้นส่วนจะสร้างขึ้นใหม่เฉพาะเมื่อคุณสร้างอินสแตนซ์ใหม่จากรหัสของคุณ ในกรณีนี้เห็นได้ชัดว่านวกรรมิกเรียกและsetArguments(new Bundle());เขียนทับ Bundle เก่า ดังนั้นให้แน่ใจว่าคุณสร้างชิ้นส่วนเพียงครั้งเดียวแล้วใช้อินสแตนซ์นี้แทนการสร้างขึ้นใหม่ทุกครั้ง
Fyodor Volchyok

20

เพิ่งสังเกตว่าถ้าคุณทำงานกับ Fragments โดยใช้ ViewPager มันค่อนข้างง่าย คุณจะต้องเรียกวิธีนี้setOffscreenPageLimit()เท่านั้น: .

สอดคล้องกับเอกสาร:

กำหนดจำนวนหน้าที่ควรเก็บไว้ที่ด้านใดด้านหนึ่งของหน้าปัจจุบันในลำดับชั้นการดูในสถานะว่าง เพจที่เกินขีด จำกัด นี้จะถูกสร้างขึ้นใหม่จากอะแดปเตอร์เมื่อจำเป็น

ปัญหาที่คล้ายกันที่นี่


5
มันแตกต่างกัน setOffScreenPageLimit ทำหน้าที่เหมือนแคช (หมายถึงจำนวนเพจที่ ViewPager ต้องจัดการในช่วงเวลาที่กำหนด) แต่ไม่ได้ใช้เพื่อบันทึกสถานะของ Fragment
Renaud Mathieu

ในกรณีของฉันมันทำงานกับ setOffscreenPageLimit () - แม้ว่าชิ้นส่วนจะถูกทำลาย แต่สถานะมุมมองถูกบันทึกและเรียกคืน
Davincho

ขอบคุณช่วยฉันด้วย
เอลียาห์

หลายปีต่อมาและนี่ก็ยังเกี่ยวข้องกันมาก แม้ว่ามันจะไม่ได้ตอบคำถามจริงๆ แต่ก็แก้ปัญหาได้
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บางอย่างจะเช่นคุณสมบัติคงที่ทั่วโลกตัวแปรด้าย non-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();

สิ่งที่ควรคำนึงถึงก็คือในขณะที่สิ่งนี้รักษาลำดับตามธรรมชาติสำหรับชิ้นส่วนเองคุณยังอาจต้องจัดการกับกิจกรรมที่ถูกทำลายและสร้างการเปลี่ยนแปลงปฐมนิเทศ (config) ในการหลีกเลี่ยงปัญหานี้ใน AndroidManifest.xml สำหรับโหนดของคุณ:

android:configChanges="orientation|screenSize"

ใน Android 3.0 และสูงกว่านั้น screenSizeนั้นจำเป็นต้องมี

โชคดี


transaction.addToBackStack (button_id + "stack_item"); // บรรทัดนี้ทำอะไร button_id อยู่ที่นี่
raghu_3

button_id เป็นเพียงตัวแปรที่สร้างขึ้น อาร์กิวเมนต์สตริงที่ส่งผ่านไปยัง addToBackStack เป็นเพียงชื่อเสริมสำหรับสถานะ backstack - คุณสามารถตั้งค่าเป็น null หากคุณจัดการ backstack เพียงครั้งเดียว
rmirabelle

ฉันกำลังมีปัญหาคล้ายกับสิ่งนี้ด้วยชิ้นส่วนคุณช่วยกรุณาดูได้ที่ - stackoverflow.com/questions/22468977/…
raghu_3

1
ไม่เคยเพิ่มandroid:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsในรายการของคุณ แทนที่จะเรียนรู้วิธีบันทึกและกู้คืนสถานะตัวอย่างเช่นจากที่นี่: speakerdeck.com/cyrilmottier/ …
Marcin Koziński

และเมื่อคุณได้เรียนรู้ว่าสถานะการออมที่ไม่มั่นคงอย่างไม่น่าเชื่อและวิธีการแก้ปัญหาที่นำเสนอแก้ไขปัญหาได้อย่างหมดจดที่สุดในสถานการณ์ของคุณไปข้างหน้าและใช้งานได้โดยไม่ต้องกังวลกับ downvotes จากคนที่ไม่เห็นด้วย ;-)
rmirabelle

5

ทางออกที่ดีที่สุดที่ฉันพบอยู่ด้านล่าง:

onSavedInstanceState (): มักจะเรียกภายในส่วนเมื่อกิจกรรมจะปิด (ย้ายกิจกรรมจากที่หนึ่งไปยังอีกหรือเปลี่ยนแปลงการกำหนดค่า) ดังนั้นหากเรากำลังเรียกใช้แฟรกเมนต์หลายชิ้นในกิจกรรมเดียวกันเราต้องใช้วิธีการต่อไปนี้:

ใช้ OnDestroyView () ของแฟรกเมนต์และบันทึกอ็อบเจ็กต์ทั้งหมดภายในเมธอดนั้น จากนั้น OnActivityCreated (): ตรวจสอบว่าวัตถุนั้นเป็นโมฆะหรือไม่ (เพราะวิธีนี้เรียกใช้ทุกครั้ง) ตอนนี้เรียกคืนสถานะของวัตถุที่นี่

มันใช้งานได้เสมอ!


4

หากคุณกำลังจัดการการเปลี่ยนแปลงการกำหนดค่าในกิจกรรมส่วนของคุณที่ระบุไว้ในรายการหุ่นยนต์เช่นนี้

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

ดังนั้นonSaveInstanceStateชิ้นส่วนจะไม่ถูกเรียกใช้และsavedInstanceStateวัตถุจะเป็นโมฆะเสมอ


1

ฉันไม่คิดว่าonSaveInstanceStateเป็นทางออกที่ดี มันใช้สำหรับกิจกรรมที่ถูกทำลาย

จาก android 3.0 Fragmen ได้รับการจัดการโดย FragmentManager เงื่อนไขคือ: การทำแผนที่กิจกรรมหนึ่งชิ้นส่วนแมนนี่เมื่อมีการเพิ่มส่วน (ไม่แทนที่: มันจะสร้างขึ้นใหม่) ใน backStack มุมมองจะถูกทำลาย เมื่อย้อนกลับไปยังหน้าสุดท้ายมันจะแสดงเหมือนเดิม

ดังนั้นฉันคิดว่าแฟรกเมนต์ตัวจัดการและธุรกรรมดีพอที่จะจัดการได้


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.