สนับสนุน FragmentPagerAdapter มีการอ้างอิงถึงแฟรกเมนต์เก่า


109

ข้อมูลล่าสุด:

ฉันได้ จำกัด ปัญหาของฉันให้แคบลงจนเป็นปัญหากับ fragmentManager ที่เก็บอินสแตนซ์ของแฟรกเมนต์เก่าและ viewpager ของฉันไม่ซิงค์กับ FragmentManager ของฉัน เห็นปัญหานี้ ... http://code.google.com/p/android/issues/detail?id=19211#makechanges ฉันยังไม่ทราบวิธีแก้ปัญหานี้ ข้อเสนอแนะใด ๆ ...

ฉันพยายามแก้ไขข้อบกพร่องนี้มานานแล้วและความช่วยเหลือใด ๆ จะได้รับการชื่นชมอย่างมาก ฉันใช้ FragmentPagerAdapter ซึ่งยอมรับรายการชิ้นส่วนดังนี้:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

การนำไปใช้เป็นมาตรฐาน ฉันใช้ไลบรารีการคำนวณ ActionBarSherlock และ v4 สำหรับ Fragments

ปัญหาของฉันคือหลังจากออกจากแอปและเปิดแอปพลิเคชั่นอื่น ๆ และกลับมาส่วนย่อยจะสูญเสียการอ้างอิงกลับไปที่ FragmentActivity (เช่นgetActivity() == null) ฉันไม่สามารถเข้าใจได้ว่าเหตุใดจึงเกิดขึ้น ฉันพยายามตั้งค่าด้วยตนเองsetRetainInstance(true);แต่ไม่ได้ผล ฉันคิดว่าสิ่งนี้เกิดขึ้นเมื่อ FragmentActivity ของฉันถูกทำลายอย่างไรก็ตามสิ่งนี้ยังคงเกิดขึ้นหากฉันเปิดแอปก่อนที่ฉันจะได้รับข้อความบันทึก มีความคิดบ้างไหม?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

อะแดปเตอร์:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

เศษชิ้นส่วนชิ้นหนึ่งของฉันถูกปล้น แต่ฉันได้ทำการแก้ไขทุกอย่างที่ถูกถอดออกและยังใช้งานไม่ได้ ...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


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

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

เมธอดอัพเดตถูกเรียกเมื่อผู้ใช้โต้ตอบกับแฟรกเมนต์อื่นและ getActivity ส่งคืนค่าว่าง นี่คือวิธีการที่ส่วนอื่นเรียก ...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

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


27
Fragment ของ Android ห่วย!
Hamidreza Sadegh

13
พระเจ้าฉันรักข้อความบันทึกของคุณ: D
oli.G

คำตอบ:


120

คุณกำลังประสบปัญหาเนื่องจากคุณกำลังสร้างอินสแตนซ์และเก็บข้อมูลอ้างอิงไปยังPagerAdapter.getItemแฟรกเมนต์ของคุณภายนอกและกำลังพยายามใช้การอ้างอิงเหล่านั้นโดยไม่ขึ้นอยู่กับ ViewPager ดังที่ Seraph กล่าวไว้คุณรับประกันได้ว่ามีการสร้างอินสแตนซ์ / เพิ่มส่วนย่อยใน ViewPager ในช่วงเวลาใดเวลาหนึ่งซึ่งควรถือเป็นรายละเอียดการใช้งาน ViewPager ขี้เกียจโหลดหน้าเว็บ โดยค่าเริ่มต้นจะโหลดเฉพาะหน้าปัจจุบันและหน้าหนึ่งไปทางซ้ายและขวา

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

ตอนนี้ให้พิจารณาว่าคุณได้ดูหน้าบางส่วน Fragments A, B และ C คุณรู้ว่าสิ่งเหล่านี้ถูกเพิ่มไปยังตัวจัดการส่วนย่อยแล้ว เนื่องจากคุณกำลังใช้งานFragmentPagerAdapterและไม่ได้ใช้FragmentStatePagerAdapterงานชิ้นส่วนเหล่านี้จะยังคงถูกเพิ่ม (แต่อาจแยกออกจากกัน) เมื่อคุณเลื่อนไปยังหน้าอื่น ๆ

พิจารณาว่าคุณใช้พื้นหลังแอปพลิเคชันของคุณแล้วแอปพลิเคชันจะถูกฆ่า เมื่อคุณกลับมา Android จะจำได้ว่าคุณเคยมี Fragments A, B และ C ในตัวจัดการแฟรกเมนต์ดังนั้นมันจึงสร้างมันขึ้นมาใหม่ให้คุณแล้วจึงเพิ่มมันเข้าไป อย่างไรก็ตามสิ่งที่เพิ่มลงในตัวจัดการแฟรกเมนต์ตอนนี้ไม่ใช่สิ่งที่คุณมีในรายการแฟรกเมนต์ในกิจกรรมของคุณ

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

ตอนนี้กลับไปที่ปัญหาของคุณเกี่ยวกับกิจกรรมที่ขาดหายไป การโทรpagerAdapter.getItem(1)).update(id, name)หลังจากทั้งหมดนี้เกิดขึ้นจะส่งคืนแฟรกเมนต์ในรายการของคุณซึ่งยังไม่ได้เพิ่มลงในตัวจัดการแฟรกเมนต์ดังนั้นจึงไม่มีการอ้างอิงกิจกรรม ฉันขอแนะนำว่าวิธีการอัปเดตของคุณควรแก้ไขโครงสร้างข้อมูลที่ใช้ร่วมกันบางอย่าง (อาจจัดการโดยกิจกรรม) จากนั้นเมื่อคุณย้ายไปยังเพจใดเพจหนึ่งระบบจะดึงข้อมูลเองตามข้อมูลที่อัปเดตนี้


1
ฉันชอบโซลูชันของคุณเนื่องจากมันสวยงามมากและมีแนวโน้มที่จะ refactor โค้ดของฉัน แต่อย่างที่คุณบอกว่าฉันต้องการเก็บตรรกะและข้อมูล 100% ไว้ในส่วน โซลูชันของคุณต้องการการเก็บรักษาข้อมูลทั้งหมดใน FragmentActivity จากนั้นใช้แต่ละ Fragment เพื่อจัดการ Logic ในการแสดงผล อย่างที่ฉันบอกว่าฉันชอบสิ่งนี้ แต่ปฏิสัมพันธ์ระหว่างชิ้นส่วนนั้นหนักมากและมันอาจจะน่ารำคาญในการจัดการ ยังไงก็ตามขอบคุณสำหรับคำอธิบายโดยละเอียด คุณอธิบายเรื่องนี้ได้ดีกว่าที่ฉันทำได้
Maurycy

28
สั้น: อย่าอ้างอิงถึง Fragment นอกอะแดปเตอร์
passsy

2
อินสแตนซ์กิจกรรมเข้าถึงอินสแตนซ์ FragmentPagerAdapter ได้อย่างไรหากไม่ได้สร้างอินสแตนซ์ FragmentPagerAdapter อินสแตนซ์ในอนาคตจะไม่เพียงคืนสถานะ FragmentPagerAdapter และอินสแตนซ์แฟรกเมนต์ทั้งหมดหรือไม่ FragmentPagerAdapter ควรใช้อินเตอร์เฟสแฟรกเมนต์ทั้งหมดเพื่อจัดการการสื่อสารระหว่างแฟรกเมนต์หรือไม่
Eric H.

ข้อความว่า "การจัดการกับมันเป็นเรื่องยากเช่นกันที่จะได้รับการอ้างอิงเนื่องจากมีการเพิ่มแท็กที่คุณไม่รู้จักนี่คือการออกแบบคุณไม่ได้รับการสนับสนุนจากการยุ่งกับชิ้นส่วนที่เพจเจอร์ของมุมมองกำลังจัดการ .” เป็นเท็จ มันง่ายมากที่จะได้รับการอ้างอิงโดยการโทรinstantiateItemและคุณควรทำเช่นนั้นในonCreateกิจกรรมของคุณ ดูรายละเอียดที่นี่: stackoverflow.com/questions/14035090/…
morgwai

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

108

ฉันพบวิธีง่ายๆที่เหมาะกับฉัน

ทำให้อะแด็ปเตอร์แฟรกเมนต์ของคุณขยาย FragmentStatePagerAdapter แทน FragmentPagerAdapter และแทนที่เมธอด onSave เพื่อคืนค่า null

@Override
public Parcelable saveState()
{
    return null;
}

วิธีนี้ป้องกันไม่ให้ Android สร้างส่วนย่อยใหม่


วันหนึ่งต่อมาฉันพบทางออกอื่นที่ดีกว่า

เรียกsetRetainInstance(true)หาชิ้นส่วนทั้งหมดของคุณและบันทึกการอ้างอิงไปยังที่ใดที่หนึ่ง ฉันทำสิ่งนั้นในตัวแปรคงที่ในกิจกรรมของฉันเพราะมันถูกประกาศเป็น singleTask และส่วนย่อยสามารถคงเดิมได้ตลอดเวลา

วิธีนี้ Android จะไม่สร้างชิ้นส่วนขึ้นมาใหม่ แต่ใช้อินสแตนซ์เดียวกัน


4
ขอบคุณขอบคุณขอบคุณมากฉันไม่มีคำพูดที่จะขอบคุณ Mik จริงๆฉันกำลังไล่ตามปัญหานี้ตั้งแต่ 10 วันที่แล้วและลองหลายวิธีแล้ว แต่สี่สายเวทย์มนตร์นี้ช่วยชีวิตฉันได้ :)

7
การมีการอ้างอิงแบบคงที่ไปยังส่วนย่อยและ / หรือกิจกรรมเป็นสิ่งที่เสี่ยงมากที่ต้องทำเนื่องจากอาจทำให้หน่วยความจำรั่วไหลได้ง่ายมาก แน่นอนว่าหากคุณระวังคุณสามารถจัดการได้อย่างง่ายดายโดยตั้งค่าเป็นโมฆะเมื่อไม่จำเป็นอีกต่อไป
นักพัฒนา Android

สิ่งนี้ได้ผลสำหรับฉัน ส่วนย่อยและมุมมองตามลำดับจะเก็บลิงก์ไว้หลังจากแอปขัดข้องและรีบูต ขอบคุณ!
swebal

ฉันใช้setRetainInstance(true)กับFragmentPagerAdapter. ทั้งหมดใช้งานได้ดี แต่เมื่อฉันหมุนอุปกรณ์อะแดปเตอร์ยังคงมีชิ้นส่วนอยู่ แต่ไม่แสดงชิ้นส่วน ไม่เรียกวิธีการวงจรชีวิตของชิ้นส่วนอย่างใดอย่างหนึ่ง ใครสามารถช่วย?
Jonas

1
คุณช่วยวันของฉันไว้ !!! ค้นหาจุดบกพร่องนี้มา 3 วันแล้ว ฉันมี Viewpager ที่มีส่วนย่อย 2 ชิ้นในกิจกรรม SingleTask และเปิดใช้งานการตั้งค่าสถานะ "ไม่เก็บกิจกรรม" ขอบคุณมาก!!!!
เมทริกซ์

29

ฉันแก้ไขปัญหานี้โดยการเข้าถึงส่วนของฉันโดยตรงผ่าน FragmentManager แทนที่จะผ่าน FragmentPagerAdapter เช่นนั้น ก่อนอื่นฉันต้องหาแท็กของแฟรกเมนต์อัตโนมัติที่สร้างโดย FragmentPagerAdapter ...

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

จากนั้นฉันก็ได้รับการอ้างอิงถึงส่วนนั้นและทำในสิ่งที่ฉันต้องการเช่นนั้น ...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

ภายในชิ้นส่วนของฉันฉันตั้งค่าsetRetainInstance(false);เพื่อให้ฉันสามารถเพิ่มค่าในบันเดิลที่บันทึกไว้ด้วยตนเอง

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

จากนั้นใน OnCreate ฉันคว้าคีย์นั้นและกู้คืนสถานะของส่วนตามความจำเป็น วิธีแก้ปัญหาที่ง่ายซึ่งยาก (อย่างน้อยสำหรับฉัน) ที่จะคิดออก


ฉันมีปัญหาคล้ายกับคุณ แต่ฉันไม่ค่อยเข้าใจคำอธิบายของคุณคุณสามารถให้รายละเอียดเพิ่มเติมได้หรือไม่? ฉันมีและอะแดปเตอร์ที่เก็บชิ้นส่วนในรายการ แต่เมื่อฉันกลับมาใช้งานแอพของฉันจากแอพล่าสุดแอพขัดข้องเนื่องจากมีบางส่วนแยกออกมา ปัญหาอยู่ที่นี่stackoverflow.com/questions/11631408/…
Georgy Gobozov

3
'ของฉัน' ของคุณคืออะไรในการอ้างอิงส่วน?
Josh

เราทุกคนรู้ว่าโซลูชันนี้ไม่ใช่แนวทางที่ดี แต่เป็นวิธีที่ง่ายที่สุดในการใช้งานFragmentByTagใน ViewPager
Youngjae

ต่อไปนี้เป็นวิธีเข้าถึงชิ้นส่วนโดยไม่ต้องอาศัยความเข้ากันได้กับวิธีการกำหนดแท็กภายใน: stackoverflow.com/questions/14035090/…
morgwai

26

โซลูชันการทดสอบการทำงานระดับโลก

getSupportFragmentManager()เก็บการอ้างอิงว่างไว้บางครั้งและดูเพจเจอร์ไม่ได้สร้างใหม่เนื่องจากพบการอ้างอิงไปยังแฟรกเมนต์เดียวกัน ดังนั้นการใช้งานนี้getChildFragmentManager()จะช่วยแก้ปัญหาด้วยวิธีง่ายๆ

อย่าทำสิ่งนี้:

new PagerAdapter(getSupportFragmentManager(), fragments);

ทำเช่นนี้:

new PagerAdapter(getChildFragmentManager() , fragments);


6
สิ่งนี้จะเป็นไปได้ก็ต่อเมื่อคุณโฮสต์ (และสร้างอินสแตนซ์) เพจเจอร์อะแดปเตอร์ภายใน Fragment และไม่อยู่ในกิจกรรมของคุณ ในกรณีนี้คุณถูกต้องคุณควรใช้ childFragmentManager เป็นค่าเริ่มต้น การใช้ SupportFragmentManager จะผิดไปโดยปริยาย
Klitos G.

เป๊ะมาก. แก้ไขปัญหาของฉันโดยไม่ต้องใช้แฮ็ก ขอบคุณ! เวอร์ชัน Kotlin ของฉันเปลี่ยนจากFragmentStatePagerAdapter(activity!!.supportFragmentManager)ให้ดูง่ายขึ้นFragmentStatePagerAdapter(childFragmentManager):)
ฯลฯ

7

อย่าพยายามโต้ตอบระหว่างส่วนย่อยใน ViewPager คุณไม่สามารถรับประกันได้ว่ามีชิ้นส่วนอื่น ๆ ที่แนบมาหรือแม้แต่มีอยู่ แทนที่จะเปลี่ยนชื่อแถบการกระทำจากส่วนย่อยคุณสามารถทำได้จากกิจกรรมของคุณ ใช้รูปแบบอินเทอร์เฟซมาตรฐานสำหรับสิ่งนี้:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}

แน่นอนว่าจะรวมรหัสในขณะนี้ ... ฉันกำลังบันทึกวิธีการเหล่านั้นอยู่แล้ว onDetach ถูกเรียกเมื่อ onStop ถูกเรียกใน fragmentActivity onAttach ถูกเรียกก่อน onCreate of the FragmentActivity
Maurycy

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

ตกลงดังนั้นฉันจึงทดสอบสิ่งนี้อย่างสมบูรณ์และแสดงความคิดเห็นกับรหัสทั้งหมดของฉัน ... ยกเว้นการติดต่อกลับเพื่อเปลี่ยนชื่อ เมื่อแอปเปิดตัวครั้งแรกและฉันย้ายไปที่หน้าจอนั้นชื่อเรื่องจะเปลี่ยนไป np หลังจากโหลดแอพพลิเคชั่นหลาย ๆ ตัวกลับมาชื่อเรื่องจะไม่เปลี่ยนอีกต่อไป ดูเหมือนว่ากำลังได้รับการติดต่อกลับในอินสแตนซ์กิจกรรมเก่า ฉันนึกคำอธิบายอื่นไม่ออก
Maurycy

ฉัน marrowed ลงนี้จะเป็นปัญหากับ fragmentManager และปัญหานี้ที่ ... code.google.com/p/android/issues/detail?id=19211 ฉันยังไม่รู้วิธีแก้ปัญหานี้
Maurycy

5

คุณสามารถลบชิ้นส่วนเมื่อทำลาย viewpager ในกรณีของฉันฉันลบมันออกonDestroyView()จากส่วนของฉัน:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}

ขอบคุณโซลูชันของคุณใช้ได้ผล จำเป็นต้องใช้ซึ่งViewPagerขึ้นอยู่กับchildFragmentManagerอะแดปเตอร์ด้วย (ไม่ใช่fragmentManager) นอกจากนี้ตัวแปรอื่นยังใช้งานได้: ไม่ใช้onDestroyViewแต่ลบส่วนย่อยก่อนViewPagerสร้างอะแด็ปเตอร์
CoolMind

4

หลังจากไม่กี่ชั่วโมงในการค้นหาปัญหาที่คล้ายกันฉันคิดว่ามีทางออกอื่น อย่างน้อยมันก็ใช้ได้สำหรับฉันและฉันต้องเปลี่ยนสองสามบรรทัด

นี่คือปัญหาที่ฉันพบฉันมีกิจกรรมกับเพจเจอร์มุมมองที่ใช้ FragmentStatePagerAdapter ที่มีสอง Fragment ทุกอย่างทำงานได้ดีจนกว่าฉันจะบังคับให้กิจกรรมถูกทำลาย (ตัวเลือกสำหรับนักพัฒนา) หรือฉันหมุนหน้าจอ ฉันจะอ้างอิงถึงสองส่วนหลังจากที่สร้างขึ้นในเมธอด getItem

เมื่อถึงจุดนั้นกิจกรรมจะถูกสร้างขึ้นอีกครั้งและทุกอย่างทำงานได้ดี ณ จุดนี้ แต่ฉันสูญเสียการอ้างอิงถึง Fragmetns ของฉันเนื่องจาก getItem ไม่ได้รับการเรียกอีกครั้ง

นี่คือวิธีที่ฉันแก้ไขปัญหานั้นภายใน FragmentStatePagerAdapter:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object aux = super.instantiateItem(container, position);

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

คุณจะไม่ได้รับการเรียกใช้ getItem อีกหากอะแดปเตอร์มีการอ้างอิงถึงภายในอยู่แล้วและคุณไม่ควรเปลี่ยนแปลงสิ่งนั้น แต่คุณสามารถรับชิ้นส่วนที่กำลังใช้อยู่ได้โดยดูวิธีการอื่น ๆ นี้ instantiateItem () ซึ่งจะถูกเรียกสำหรับแต่ละส่วนของคุณ

หวังว่าจะช่วยทุกคน


เกิดอะไรขึ้นถ้าคุณมีเศษมาก? คุณต้องการบันทึกข้อมูลอ้างอิงสำหรับแต่ละรายการหรือไม่? ความจำมันไม่แย่เหรอ
นักพัฒนา Android

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

0

เนื่องจาก FragmentManager จะดูแลการกู้คืน Fragment ของคุณให้คุณทันทีที่เรียกเมธอด onResume () ฉันจึงมีการเรียก Fragment ไปยังกิจกรรมและเพิ่มตัวเองลงในรายการ ในอินสแตนซ์ของฉันฉันกำลังจัดเก็บข้อมูลทั้งหมดนี้ไว้ในการใช้งาน PagerAdapter แต่ละส่วนรู้ตำแหน่งของมันเนื่องจากมีการเพิ่มเข้าไปในอาร์กิวเมนต์แฟรกเมนต์ในการสร้าง ตอนนี้เมื่อใดก็ตามที่ฉันต้องการจัดการชิ้นส่วนที่ดัชนีเฉพาะสิ่งที่ฉันต้องทำคือใช้รายการจากอะแดปเตอร์ของฉัน

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

อะแดปเตอร์

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

@Override
public int getCount() {
    return COUNT;
}

@Override
public Fragment getItem(int position) {
    return SummaryTabletFragment.newInstance(position);
}

@Override
public void onPageScrollStateChanged(int state) {}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

ส่วนย่อย

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

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

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}

0

วิธีการแก้ปัญหาของฉัน: staticฉันจะตั้งเกือบทุกดู ตอนนี้แอปของฉันโต้ตอบได้สมบูรณ์แบบ ความสามารถในการเรียกวิธีการแบบคงที่จากทุกที่อาจไม่ใช่รูปแบบที่ดี แต่ทำไมต้องเล่นกับโค้ดที่ใช้ไม่ได้? ฉันอ่านคำถามมากมายและคำตอบของพวกเขาที่นี่ใน SO และไม่มีวิธีแก้ปัญหาใดที่นำความสำเร็จมาให้ (สำหรับฉัน)

ฉันรู้ว่ามันอาจทำให้หน่วยความจำรั่วและเสียฮีปและรหัสของฉันจะไม่พอดีกับโปรเจ็กต์อื่น ๆ แต่ฉันไม่รู้สึกกลัวเกี่ยวกับเรื่องนี้ - ฉันทดสอบแอปบนอุปกรณ์และเงื่อนไขที่แตกต่างกันไม่มีปัญหาเลย Android ดูเหมือนว่าแพลตฟอร์มจะสามารถจัดการสิ่งนี้ได้ UI ได้รับการรีเฟรชทุกวินาทีและแม้กระทั่งบนอุปกรณ์ S2 ICS (4.0.3) แอปก็สามารถจัดการกับเครื่องหมายทางภูมิศาสตร์ได้หลายพันรายการ


0

ฉันประสบปัญหาเดียวกัน แต่ ViewPager ของฉันอยู่ใน TopFragment ซึ่งสร้างและตั้งค่าอะแดปเตอร์โดยใช้setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

ฉันแก้ไขปัญหานี้โดยการแทนที่onAttachFragment(Fragment childFragment)ใน TopFragment เช่นนี้:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

ดังที่ทราบกันดีอยู่แล้ว (ดูคำตอบด้านบน) เมื่อ childFragmentManager สร้างตัวเองขึ้นมาใหม่มันยังสร้างแฟรกเมนต์ที่อยู่ใน viewPager
ส่วนที่สำคัญคือหลังจากนั้นเขาเรียกonAttachFragmentและตอนนี้เรามีการอ้างอิงถึงส่วนที่สร้างขึ้นใหม่!

หวังว่านี่จะช่วยให้ทุกคนได้รับ Q เก่า ๆ เช่นฉัน :)


0

ฉันแก้ไขปัญหาโดยบันทึกชิ้นส่วนใน SparceArray:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}

0

เพียงเพื่อให้คุณรู้ว่า...

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

ฉันใช้ ViewPager เพื่อนำทางต้นไม้ของรายการ (เลือกรายการและเพจเจอร์ของมุมมองจะเคลื่อนไหวโดยเลื่อนไปทางขวาและสาขาถัดไปจะปรากฏขึ้นนำทางย้อนกลับและ ViewPager จะเลื่อนไปในทิศทางตรงกันข้ามเพื่อกลับไปยังโหนดก่อนหน้า) .

ปัญหาเกิดขึ้นเมื่อฉันดันและป๊อปชิ้นส่วนออกจากส่วนท้ายของ FragmentStatePagerAdapter ฉลาดพอที่จะสังเกตว่ารายการนั้นเปลี่ยนไปและฉลาดพอที่จะสร้างและแทนที่ส่วนย่อยเมื่อรายการมีการเปลี่ยนแปลง แต่ไม่ฉลาดพอที่จะละทิ้งสถานะแฟรกเมนต์หรือฉลาดพอที่จะตัดทอนสถานะแฟรกเมนต์ที่บันทึกไว้ภายในเมื่อขนาดของอะแด็ปเตอร์เปลี่ยนไป ดังนั้นเมื่อคุณป๊อปไอเท็มและดันรายการใหม่ไปที่ส่วนท้ายแฟรกเมนต์สำหรับไอเท็มใหม่จะได้รับสถานะที่บันทึกไว้ของแฟรกเมนต์สำหรับไอเท็มเก่าซึ่งทำให้โค้ดของฉันเสียหาย ส่วนย่อยของฉันมีข้อมูลที่อาจต้องใช้งานมากในการดึงข้อมูลใหม่จากอินเทอร์เน็ตดังนั้นการไม่บันทึกสถานะจึงไม่ใช่ตัวเลือก

ฉันไม่มีวิธีแก้ปัญหาที่ชัดเจน ฉันใช้สิ่งนี้:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

วิธีแก้ปัญหาที่ไม่สมบูรณ์เนื่องจากอินสแตนซ์แฟรกเมนต์ใหม่ยังคงได้รับบันเดิลที่บันทึกไว้ แต่อย่างน้อยก็ไม่มีข้อมูลเก่า


0

เนื่องจากผู้คนมักไม่อ่านความคิดเห็นนี่คือคำตอบที่ส่วนใหญ่ซ้ำกับสิ่งที่ฉันเขียนไว้ที่นี่ :

สาเหตุที่แท้จริงของปัญหาคือความจริงที่ว่าระบบหุ่นยนต์ไม่เรียกgetItemจะได้รับชิ้นส่วนที่แสดงจริง instantiateItemแต่ FragmentManagerวิธีการนี้เป็นครั้งแรกพยายามที่จะค้นหาและนำมาใช้เช่นชิ้นส่วนสำหรับแท็บที่กำหนดใน เฉพาะในกรณีที่การค้นหานี้ล้มเหลว (ซึ่งจะเกิดขึ้นในครั้งแรกเมื่อFragmentManagerสร้างขึ้นใหม่เท่านั้น) จากนั้นgetItemจะถูกเรียกใช้ ด้วยเหตุผลที่ชัดเจนที่จะไม่สร้างชิ้นส่วนใหม่ (ซึ่งอาจมีน้ำหนักมาก) เช่นทุกครั้งที่ผู้ใช้หมุนอุปกรณ์
ในการแก้ปัญหานี้แทนที่จะสร้างแฟรกเมนต์Fragment.instantiateในกิจกรรมของคุณคุณควรทำด้วยpagerAdapter.instantiateItemและการเรียกทั้งหมดเหล่านี้ควรถูกล้อมรอบด้วยstartUpdate/finishUpdateการเรียกเมธอดที่เริ่ม / คอมมิตธุรกรรมตามลำดับgetItem ควรเป็นสถานที่ที่ชิ้นส่วนถูกสร้างขึ้นโดยใช้ตัวสร้างตามลำดับ

List<Fragment> fragments = new Vector<Fragment>();

@Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        adapter.finishUpdate(viewPager);
}

class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

        @Override public int getCount() {return 2;}

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.