ลบ Fragment Page ออกจาก ViewPager ใน Android


138

ฉันกำลังพยายามเพิ่มและลบ Fragments จาก ViewPager แบบไดนามิกเพิ่มงานโดยไม่มีปัญหา แต่การลบไม่ทำงานอย่างที่คาดไว้

ทุกครั้งที่ฉันต้องการลบรายการปัจจุบันรายการสุดท้ายจะถูกลบ

ฉันยังพยายามใช้ FragmentStatePagerAdapter หรือส่งคืน POSITION_NONE ในวิธีการ getItemPosition ของอะแดปเตอร์

ผมทำอะไรผิดหรือเปล่า?

นี่คือตัวอย่างพื้นฐาน:

MainActivity.java

public class MainActivity extends FragmentActivity implements TextProvider {

    private Button mAdd;
    private Button mRemove;
    private ViewPager mPager;

    private MyPagerAdapter mAdapter;

    private ArrayList<String> mEntries = new ArrayList<String>();

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

        mEntries.add("pos 1");
        mEntries.add("pos 2");
        mEntries.add("pos 3");
        mEntries.add("pos 4");
        mEntries.add("pos 5");

        mAdd = (Button) findViewById(R.id.add);
        mRemove = (Button) findViewById(R.id.remove);
        mPager = (ViewPager) findViewById(R.id.pager);

        mAdd.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                addNewItem();
            }
        });

        mRemove.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                removeCurrentItem();
            }
        });

        mAdapter = new MyPagerAdapter(this.getSupportFragmentManager(), this);

        mPager.setAdapter(mAdapter);

    }

    private void addNewItem() {
        mEntries.add("new item");
        mAdapter.notifyDataSetChanged();
    }

    private void removeCurrentItem() {
        int position = mPager.getCurrentItem();
        mEntries.remove(position);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public String getTextForPosition(int position) {
        return mEntries.get(position);
    }
    @Override
    public int getCount() {
        return mEntries.size();
    }


    private class MyPagerAdapter extends FragmentPagerAdapter {

        private TextProvider mProvider;

        public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
            super(fm);
            this.mProvider = provider;
        }

        @Override
        public Fragment getItem(int position) {
            return MyFragment.newInstance(mProvider.getTextForPosition(position));
        }

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

    }

}

TextProvider.java

public interface TextProvider {
    public String getTextForPosition(int position);
    public int getCount();
}

MyFragment.java

public class MyFragment extends Fragment {

    private String mText;

    public static MyFragment newInstance(String text) {
        MyFragment f = new MyFragment(text);
        return f;
    }

    public MyFragment() {
    }

    public MyFragment(String text) {
        this.mText = text;
    }

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

        View root = inflater.inflate(R.layout.fragment, container, false);

        ((TextView) root.findViewById(R.id.position)).setText(mText);

        return root;
    }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="add new item" />

    <Button
        android:id="@+id/remove"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="remove current item" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

</LinearLayout>

fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/position"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="35sp" />

</LinearLayout>

2
+1 สำหรับซอร์สโค้ด
wizurd

มันถูกต้องเพื่อใช้คอนสตรัคเริ่มต้นไม่ในMyFragment.java??
codeKiller

คำตอบ:


95

วิธีแก้ปัญหาของ Louth ไม่เพียงพอที่จะทำให้สิ่งต่าง ๆ ได้ผลสำหรับฉันเนื่องจากชิ้นส่วนที่มีอยู่ไม่ได้ถูกทำลาย แรงบันดาลใจจากคำตอบนี้ฉันพบว่าวิธีแก้ปัญหาคือแทนที่getItemId(int position)วิธีFragmentPagerAdapterการให้รหัสเฉพาะใหม่ทุกครั้งที่มีการเปลี่ยนแปลงตำแหน่งที่คาดหวังของ Fragment

รหัสแหล่งที่มา:

private class MyPagerAdapter extends FragmentPagerAdapter {

    private TextProvider mProvider;
    private long baseId = 0;

    public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
        super(fm);
        this.mProvider = provider;
    }

    @Override
    public Fragment getItem(int position) {
        return MyFragment.newInstance(mProvider.getTextForPosition(position));
    }

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


    //this is called when notifyDataSetChanged() is called
    @Override
    public int getItemPosition(Object object) {
        // refresh all fragments when data set changed
        return PagerAdapter.POSITION_NONE;
    }


    @Override
    public long getItemId(int position) {
        // give an ID different from position when position has been changed
        return baseId + position;
    }

    /**
     * Notify that the position of a fragment has been changed.
     * Create a new ID for each position to force recreation of the fragment
     * @param n number of items which have been changed
     */
    public void notifyChangeInPosition(int n) {
        // shift the ID returned by getItemId outside the range of all previous fragments
        baseId += getCount() + n;
    }
}

ตอนนี้เช่นถ้าคุณลบแท็บเดียวหรือทำการเปลี่ยนแปลงคำสั่งคุณควรโทรnotifyChangeInPosition(1)ก่อนโทรnotifyDataSetChanged()ซึ่งจะทำให้แน่ใจว่าชิ้นส่วนทั้งหมดจะถูกสร้างขึ้นใหม่

เหตุใดโซลูชันนี้จึงใช้งานได้

การเอาชนะ getItemPosition ():

เมื่อnotifyDataSetChanged()ถูกเรียกอะแด็ปเตอร์จะเรียกใช้notifyChanged()เมธอดViewPagerที่แนบมาด้วย ViewPagerแล้วตรวจสอบค่าส่งกลับโดยอะแดปเตอร์เป็นgetItemPosition()สำหรับแต่ละรายการลบรายการเหล่านั้นซึ่งผลตอบแทนPOSITION_NONE(ดูรหัสที่มา ) แล้ว repopulating

การเอาชนะ getItemId ():

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

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }

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

หมายเหตุคลาสนี้อยู่ระหว่างการออกแบบและพัฒนาในช่วงต้น API มีแนวโน้มที่จะเปลี่ยนแปลงในการอัปเดตไลบรารีความเข้ากันได้ในภายหลังซึ่งต้องการการเปลี่ยนแปลงรหัสแหล่งที่มาของแอพเมื่อรวบรวมกับเวอร์ชันที่ใหม่กว่า

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


1
ขอบคุณมากสำหรับคำตอบนี้! มีความพยายามอย่างมากที่จะแก้ไขปัญหานี้โดยไม่ใช้ FragmentStatePagerAdapter คำอธิบายที่ดีเช่นกัน คุณพบวิธีที่จะได้รับ getItem เพื่อทริกเกอร์เมื่อกลับมาที่ส่วน? ฉันได้ลองใช้ informDataSetChanged ที่ชาญฉลาดของคุณในหลาย ๆ สถานที่ (เช่น onStop) ไม่ประสบความสำเร็จ
u2tall

ฉันไม่เข้าใจคำถามของคุณจริงๆ ... โปรดโพสต์คำถามใหม่รวมถึงซอร์สโค้ดหากคุณไม่สามารถเข้าใจได้
Tim Rae

1
2559 การแก้ปัญหานี้ยังคงต้องใช้ในnotifyDataSetChanged()การทำงาน
Subin Sebastian

ว้าว! คำอธิบายที่น่าอัศจรรย์ ฉันมี viewpager ที่ค่อนข้างซับซ้อนซึ่งฉันลบและแทรกรายการแบบไดนามิกและจะไม่สามารถใช้งานได้หากไม่เข้าใจทั้งหมดนี้
rupps

ยังคงต้องการวิธีแก้ปัญหานี้เพื่อแทนที่ชิ้นส่วนเก่า แดงมัน
kopikaokao

291

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

เพื่อให้บรรลุผลตามที่คุณต้องการคุณต้องทำสองสิ่ง

  1. เปลี่ยน FragmentPagerAdapter เป็น FragmentStatePagerAdapter เหตุผลนี้คือ FragmentPagerAdapter จะเก็บมุมมองทั้งหมดที่โหลดลงในหน่วยความจำตลอดไป โดยที่ FragmentStatePagerAdapter จะแสดงมุมมองที่อยู่นอกมุมมองปัจจุบันและมุมมองที่ผ่านได้

  2. แทนที่เมธอดอะแด็ปเตอร์ getItemPosition (แสดงด้านล่าง) เมื่อเราเรียกmAdapter.notifyDataSetChanged();ViewPager จะสอบถามอะแดปเตอร์เพื่อพิจารณาว่ามีการเปลี่ยนแปลงอะไรบ้างในแง่ของการวางตำแหน่ง เราใช้วิธีนี้เพื่อบอกว่าทุกอย่างมีการเปลี่ยนแปลงเพื่อประมวลผลการวางตำแหน่งมุมมองทั้งหมดใหม่อีกครั้ง

และนี่คือรหัส ...

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

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

}

1
ตกลงสิ่งนี้ดูเหมือนจะเป็นวิธีแก้ปัญหาที่ง่ายที่สุดสำหรับปัญหานี้ แม้ว่าการแก้ไขปัญหาอื่น ๆ สองสามอย่างจะกล่าวถึงในรายงานข้อผิดพลาดที่นี่: code.google.com/p/android/issues/detail?id=19110และที่นี่: code.google.com/p/android/issues/detail?id= 19001
Subin Sebastian

@Louth: ฉันใช้ ViewPager กับ TabsAdapter เช่นยื่นออกมาจาก FragmentStatePagerAdapter (ตามตัวอย่างในหน้า Google Ref: developer.android.com/reference/android/support/v4/view/... ) คำถามติดตามของฉันคือคุณจะลบแท็บได้อย่างไร? ขอบคุณ
gcl1

4
@Louth เมื่อเราเรียกการแจ้งเตือน DataSetChanged () viewpager จะสร้างแฟรกเมนต์ใหม่ แต่อันที่เก่ากว่านั้นยังอยู่ในหน่วยความจำ และปัญหาของฉันคือ; พวกเขาได้รับสายสำหรับเหตุการณ์ onOptionsItemSelected ของพวกเขา ฉันจะกำจัดชิ้นส่วนเก่าได้อย่างไร
syloc

1
ขอบคุณคุณเพียงแค่แทนที่ FragmentPagerAdapter เป็น FragmentStatePagerAdapter แก้ปัญหาของฉัน หลังจากนั้นฉันเพิ่งลบ AllViews () บน ViewPager และโหลดชิ้นส่วนอีกครั้งแบบไดนามิก
Michal

17
ทำไมคุณกลับมาที่ POSITION_NONE เสมอแทนที่จะมองว่ามุมมอง / ส่วน / วัตถุที่กำหนดยังคงมีอยู่และถูกต้องหรือไม่ วิธีนี้ควรตอบคำถาม "เฮ้ฉันจะนำมันกลับมาใช้ใหม่ได้ไหม" - และมักพูดว่า "ไม่!" แค่หมายถึงการพักผ่อนหย่อนใจของทุกสิ่ง! สิ่งนี้ไม่จำเป็นในหลาย ๆ กรณี
Zordid

14

วิธีแก้ไขปัญหาการทำงานของฉันเพื่อลบหน้าส่วนจากเพจเจอร์มุมมอง

public class MyFragmentAdapter extends FragmentStatePagerAdapter {

    private ArrayList<ItemFragment> pages;

    public MyFragmentAdapter(FragmentManager fragmentManager, ArrayList<ItemFragment> pages) {
        super(fragmentManager);
        this.pages = pages;
    }

    @Override
    public Fragment getItem(int index) {
        return pages.get(index);
    }

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

    @Override
    public int getItemPosition(Object object) {
        int index = pages.indexOf (object);

        if (index == -1)
            return POSITION_NONE;
        else
            return index;
    }
}

และเมื่อฉันต้องการลบบางหน้าโดยดัชนีฉันทำ

pages.remove(position); // ArrayList<ItemFragment>
adapter.notifyDataSetChanged(); // MyFragmentAdapter

นี่คือการเริ่มต้นอะแดปเตอร์ของฉัน

MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);

คุณช่วยแบ่งปันรหัสเพื่อเพิ่มส่วนย่อยได้ไหม
VVB

แต่ในขณะที่เพิ่มมันสะท้อนช้าหลังจากรูดสองครั้ง / สามครั้ง
VVB

หากคุณไม่ได้ลบไอเท็มมันจะส่งคืนเฉพาะไอเท็มเท่านั้นคุณกำลังทดสอบบน vm หรืออุปกรณ์จริงหรือไม่?
Vasil Valchev

ลองใช้วิธีแก้ไขปัญหาอื่น ๆ ตอบคะแนนโหวตให้มากขึ้น มุมมองเพจเจอร์มีระบบ cashing ที่ซับซ้อนมากเพื่อประสิทธิภาพที่ดีขึ้นในการ swip ดังนั้นแฮ็กนี้จึงไม่ได้รับการจัดการตามวัตถุประสงค์ดั้งเดิมของเพจเจอร์เล็กน้อย ...
Vasil Valchev

11

แฟรกเมนต์ต้องถูกลบออกแล้ว แต่ปัญหาคือสถานะการบันทึก viewpager

ลอง

myViewPager.setSaveFromParentEnabled(false);

ไม่มีอะไรทำงาน แต่สิ่งนี้แก้ไขปัญหาได้!

ไชโย!


2

ฉันมีความคิดของเพียงแค่คัดลอกรหัสที่มาจากในชั้นเรียนที่กำหนดเองที่มีชื่อว่าandroid.support.v4.app.FragmentPagerAdpater CustumFragmentPagerAdapterนี่ทำให้ฉันมีโอกาสที่จะแก้ไขinstantiateItem(...)ดังนั้นทุกครั้งที่มันถูกเรียกมันจะลบ / ทำลายชิ้นส่วนที่แนบมาในปัจจุบันก่อนที่มันจะเพิ่มชิ้นส่วนใหม่ที่ได้รับจากgetItem()วิธีการ

เพียงปรับเปลี่ยนinstantiateItem(...)ด้วยวิธีต่อไปนี้:

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);

    // remove / destroy current fragment
    if (fragment != null) {
        mCurTransaction.remove(fragment);
    }

    // get new fragment and add it
    fragment = getItem(position);
    mCurTransaction.add(container.getId(), fragment,    makeFragmentName(container.getId(), itemId));

    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

1

คุณสามารถรวมทั้งสองอย่างเข้าด้วยกันเพื่อให้ดีขึ้น:

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){

      if(Any_Reason_You_WantTo_Update_Positions) //this includes deleting or adding pages
 return PagerAdapter.POSITION_NONE;
    }
else
return PagerAdapter.POSITION_UNCHANGED; //this ensures high performance in other operations such as editing list items.

}

ใช้งานได้เฉพาะเมื่อคุณลบแท็บทางขวาสุดเพื่อลบแท็บด้านในหรือแท็บด้านซ้ายคุณจะต้องเรียงลำดับแท็บใหม่และส่งคืนตำแหน่งใหม่ตามที่ระบุไว้ในเอกสารคู่มือผู้พัฒนา / view / …
BitByteDog

1

สำหรับผู้อ่านในอนาคต!

ตอนนี้คุณสามารถใช้ViewPager2สำหรับการเพิ่มแบบไดนามิกการแยกส่วนออกจาก viewpager

การอ้างอิงแบบฟอร์มAPI การอ้างอิง

ViewPager2 จะแทนที่ ViewPager โดยจัดการกับจุดปวดของรุ่นก่อนส่วนใหญ่รวมถึงการสนับสนุนเค้าโครงจากขวาไปซ้ายการวางแนวตั้งแนวตั้งคอลเลกชัน Fragment ที่สามารถแก้ไขได้ ฯลฯ

ลองดูMutableCollectionFragmentActivity.ktในgooglesample / android-viewpager2เพื่อดูตัวอย่างของการเพิ่มการลบแฟรกเมนต์แบบไดนามิกออกจาก viewpager


สำหรับข้อมูลของคุณ:

บทความ:

การอ้างอิง API

บันทึกประจำรุ่น

ตัวอย่าง Repo: https://github.com/googlesamples/android-viewpager2


ฉันมีเมนเฟรมเป็นเศษ 2 ส่วนและแฟรกเมนต์อื่น ฉันกำลังเพิ่มรูปแบบอื่น ๆ หลังจากตำแหน่งที่ 5 ทุกครั้งและไม่อยู่ในรายการอาร์เรย์ดังนั้นจะลบรูปแบบอื่นได้อย่างไรหากไม่อยู่ในรายการ ฉันใช้ viewpager2
b devloper

0

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

        if (newPos == PagerAdapter.POSITION_NONE) {
            mItems.remove(i);
            i--;
            ... not related code
            mAdapter.destroyItem(this, ii.position, ii.object);

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

 public int getItemPosition(Object object) {
    for (int i = 0; i < mFragmentsReferences.size(); i ++) {
        WeakReference<Fragment> reference = mFragmentsReferences.get(i);
        if (reference != null && reference.get() != null) {
            Fragment fragment = reference.get();
            if (fragment == object) {
                return i;
            }
        }
    }
    return POSITION_NONE;
}

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

แต่นี่ยังไม่เพียงพอ เรายังคงมีขั้นตอนสำคัญที่ต้องดำเนินการ ในซอร์สโค้ดของ FragmentStatePagerAdapter มีอาเรย์ชื่อ "mFragments" จะแคชแฟรกเมนต์ที่ไม่ถูกทำลาย และในฟังก์ชั่น instantiateItem

if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);
        if (f != null) {
            return f;
        }
    }

มันส่งคืนชิ้นส่วนแคชโดยตรงเมื่อพบว่าชิ้นส่วนแคชไม่เป็นโมฆะ ดังนั้นจึงมีปัญหา จากตัวอย่างเรามาลบหนึ่งหน้าในตำแหน่งที่ 2 อันดับแรกเราจะลบส่วนนั้นออกจากรายการอ้างอิงอาร์เรย์ของเราเอง ดังนั้นใน getItemPosition มันจะส่งคืน POSITION_NONE สำหรับแฟรกเมนต์นั้นจากนั้นแฟรกเมนต์นั้นจะถูกทำลายและลบออกจาก "mFragments"

 mFragments.set(position, null);

ตอนนี้แฟรกเมนต์ที่ตำแหน่ง 3 จะอยู่ที่ตำแหน่ง 2 และรายการอินสแตนซ์ที่มีพารามิเตอร์ตำแหน่ง 3 จะถูกเรียกใช้ ในเวลานี้รายการที่สามใน "mFramgents" ไม่ใช่โมฆะดังนั้นมันจะส่งคืนโดยตรง แต่ที่จริงแล้วสิ่งที่คืนมาคือส่วนที่ 2 ดังนั้นเมื่อเราเปลี่ยนเป็นหน้า 3 เราจะพบหน้าว่าง

เมื่อต้องการแก้ไขปัญหานี้ คำแนะนำของฉันคือคุณสามารถคัดลอกซอร์สโค้ดของ FragmentStatePagerAdapter ไปยังโครงการของคุณและเมื่อคุณเพิ่มหรือลบการดำเนินการคุณควรเพิ่มและลบองค์ประกอบในรายการอาร์เรย์ "mFragments"

สิ่งต่างๆจะง่ายขึ้นหากคุณใช้ PagerAdapter แทน FragmentStatePagerAdapter โชคดี.


0

เพิ่มหรือลบส่วนใน viewpager แบบไดนามิก
เรียกใช้ setupViewPager (viewPager) เมื่อเริ่มต้นกิจกรรม หากต้องการโหลดการเรียกใช้แฟรกเมนต์ที่แตกต่างกัน viewViewPagerCustom (viewPager) เช่นเมื่อกดปุ่มเพื่อโทร: setupViewPagerCustom (viewPager);

    private void setupViewPager(ViewPager viewPager)
{
    ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
    adapter.addFrag(new fragmnet1(), "HOME");
    adapter.addFrag(new fragmnet2(), "SERVICES");

    viewPager.setAdapter(adapter);
}

private void setupViewPagerCustom(ViewPager viewPager)
{

    ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());

    adapter.addFrag(new fragmnet3(), "Contact us");
    adapter.addFrag(new fragmnet4(), "ABOUT US");


    viewPager.setAdapter(adapter);
}

//Viewpageradapter, handles the views

static class ViewPagerAdapter extends FragmentStatePagerAdapter
{
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

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

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

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

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

    public void addFrag(Fragment fragment, String title){
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

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

0

FragmentStatePagerAdapterผมมีปัญหาบางอย่างกับ หลังจากลบรายการ:

  • มีรายการอื่นที่ใช้สำหรับตำแหน่ง (รายการที่ไม่ได้อยู่ในตำแหน่ง แต่ไปยังตำแหน่งถัดจากตำแหน่ง)
  • หรือบางส่วนไม่ได้โหลด (มีเพียงพื้นหลังเปล่าที่มองเห็นได้ในหน้านั้น)

หลังจากการทดลองหลายครั้งฉันพบวิธีแก้ปัญหาต่อไปนี้

public class SomeAdapter extends FragmentStatePagerAdapter {

    private List<Item> items = new ArrayList<Item>();

    private boolean removing;

    @Override
    public Fragment getItem(int position) {
        ItemFragment fragment = new ItemFragment();
        Bundle args = new Bundle();
        // use items.get(position) to configure fragment
        fragment.setArguments(args);
        return fragment;
    }

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

    @Override
    public int getItemPosition(Object object) {
        if (removing) {
            return PagerAdapter.POSITION_NONE;
        }

        Item item = getItemOfFragment(object);

        int index = items.indexOf(item);
        if (index == -1) {
            return POSITION_NONE;
        } else {
            return index;
        }
    }

   public void addItem(Item item) {
        items.add(item);
        notifyDataSetChanged();
    }

    public void removeItem(int position) {
        items.remove(position);

        removing = true;
        notifyDataSetChanged();
        removing = false;
    }
}

วิธีนี้ใช้แฮ็คในกรณีที่ลบรายการเท่านั้น มิฉะนั้น (เช่นเมื่อเพิ่มรายการ) มันจะรักษาความสะอาดและประสิทธิภาพของรหัสดั้งเดิม

แน่นอนจากด้านนอกของอะแดปเตอร์คุณเรียกใช้เพียง addItem / removeItem ไม่จำเป็นต้องโทรแจ้งให้ทราบ DataSetChanged ()


0

ลองวิธีนี้ ฉันใช้ databinding สำหรับการดูที่มีผลผูกพัน คุณสามารถใช้ฟังก์ชัน "findViewById ()" ทั่วไปได้

public class ActCPExpense extends BaseActivity implements View.OnClickListener,  {
private static final String TAG = ActCPExpense.class.getSimpleName();
private Context mContext;
private ActCpLossBinding mBinding;

private ViewPagerAdapter adapter;



@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    try {
        setContentView(R.layout.act_cp_loss);
        mBinding = DataBindingUtil.setContentView(this, R.layout.act_cp_loss);
        mContext = ActCPExpense.this;
        initViewsAct();


    } catch (Exception e) {
        LogUtils.LOGE(TAG, e);
    }

}


private void initViewsAct() {

    adapter = new ViewPagerAdapter(getSupportFragmentManager());
    adapter.addFragment(FragmentCPPayee.newInstance(), "Title");
    mBinding.viewpager.setAdapter(adapter);
    mBinding.tab.setViewPager(mBinding.viewpager);



}



@Override
public boolean onOptionsItemSelected(MenuItem itemActUtility) {
    int i = itemActUtility.getItemId();
    if (i == android.R.id.home) {
        onBackPressed();

    } 

    return super.onOptionsItemSelected(itemActUtility);
}

@Override
public void onClick(View view) {
    super.onClick(view);
    int id = view.getId();
    if (id == R.id.btnAdd) {
        addFragment();

    } else if (id == R.id.btnDelete) {
        removeFragment();

    }


}
    private void addFragment(){
    adapter.addFragment(FragmentCPPayee.newInstance("Title");
    adapter.notifyDataSetChanged();
    mBinding.tab.setViewPager(mBinding.viewpager);
}
private void removeFragment(){
    adapter.removeItem(mBinding.viewpager.getCurrentItem());
    mBinding.tab.setViewPager(mBinding.viewpager);
}


class ViewPagerAdapter extends FragmentStatePagerAdapter {
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();


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

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


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

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


    public void addFragment(Fragment fragment, String title) {
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);

    }

    public void removeItem(int pos) {

        destroyItem(null, pos, mFragmentList.get(pos));
        mFragmentList.remove(pos);
        mFragmentTitleList.remove(pos);
        adapter.notifyDataSetChanged();
        mBinding.viewpager.setCurrentItem(pos - 1, false);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "Title " + String.valueOf(position + 1);
    }
}


}

0

ฉันเพิ่มฟังก์ชั่น "clearFragments" และฉันใช้ฟังก์ชั่นนั้นเพื่อล้างอะแดปเตอร์ก่อนที่จะตั้งค่าแฟรกเมนต์ใหม่ สิ่งนี้เรียกการกระทำที่ถูกลบของ Fragments อย่างเหมาะสม คลาส pagerAdapter ของฉัน:

private class ChartPagerAdapter extends FragmentPagerAdapter{
    private ArrayList<Fragment> fragmentList;

    ChartPagerAdapter(FragmentManager fm){
        super(fm);
        fragmentList = new ArrayList<>();
    }

    void setFragments(ArrayList<? extends Fragment> fragments){
        fragmentList.addAll(fragments);
    }

    void clearFragments(){
        for(Fragment fragment:fragmentList)
            getChildFragmentManager().beginTransaction().remove(fragment).commit();
        fragmentList.clear();
    }

    @Override
    public Fragment getItem(int i) {
        return fragmentList.get(i);
    }

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

}

0

ฉันแก้ไขปัญหานี้ด้วยขั้นตอนเหล่านี้

1- ใช้ FragmentPagerAdapter

2- ในแต่ละส่วนสร้างรหัสสุ่ม

fragment.id = new Random().nextInt();

3- แทนที่ getItemPosition ในอะแดปเตอร์

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

getItemId 4 แทนที่ในอะแดปเตอร์

 @Override
public long getItemId(int position) {
    return mDatasetFragments.get(position).id;
}

5- ตอนนี้ลบรหัสคือ

 adapter.mDatasetFragments.remove(< item to delete position >);
 adapter.notifyDataSetChanged();

สิ่งนี้ได้ผลกับฉันฉันหวังว่าจะช่วยได้


0

รหัสรุ่นสุดท้ายของฉันแก้ไขข้อผิดพลาดทั้งหมด ฉันใช้เวลา 3 วัน

อัปเดต 2020/07/18: ฉันมีการเปลี่ยนแปลงรหัสแหล่งที่มาจำนวนมากและแก้ไขข้อบกพร่องมากมาย แต่ฉันไม่ได้สัญญาว่ามันจะยังคงทำงานในวันนี้

https://github.com/lin1987www/FragmentBuilder/blob/master/commonLibrary/src/main/java/android/support/v4/app/FragmentStatePagerAdapterFix.java

public class FragmentStatePagerAdapterFix extends PagerAdapter {
    private static final String TAG = FragmentStatePagerAdapterFix.class.getSimpleName();
    private static final boolean DEBUG = false;

    private WeakReference<FragmentActivity> wrFragmentActivity;
    private WeakReference<Fragment> wrParentFragment;
    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;

    protected ArrayList<Fragment> mFragments = new ArrayList<>();
    protected ArrayList<FragmentState> mFragmentStates = new ArrayList<>();
    protected ArrayList<String> mFragmentTags = new ArrayList<>();
    protected ArrayList<String> mFragmentClassNames = new ArrayList<>();
    protected ArrayList<Bundle> mFragmentArgs = new ArrayList<>();

    private Fragment mCurrentPrimaryItem = null;
    private boolean[] mTempPositionChange;

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

    public FragmentActivity getFragmentActivity() {
        return wrFragmentActivity.get();
    }

    public Fragment getParentFragment() {
        return wrParentFragment.get();
    }

    public FragmentStatePagerAdapterFix(FragmentActivity activity) {
        mFragmentManager = activity.getSupportFragmentManager();
        wrFragmentActivity = new WeakReference<>(activity);
        wrParentFragment = new WeakReference<>(null);
    }

    public FragmentStatePagerAdapterFix(Fragment fragment) {
        mFragmentManager = fragment.getChildFragmentManager();
        wrFragmentActivity = new WeakReference<>(fragment.getActivity());
        wrParentFragment = new WeakReference<>(fragment);
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass) {
        add(fragClass, null, null);
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args) {
        add(fragClass, args, null);
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass, String tag) {
        add(fragClass, null, tag);
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args, String tag) {
        add(fragClass, args, tag, getCount());
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args, String tag, int position) {
        mFragments.add(position, null);
        mFragmentStates.add(position, null);
        mFragmentTags.add(position, tag);
        mFragmentClassNames.add(position, fragClass.getName());
        mFragmentArgs.add(position, args);
        mTempPositionChange = new boolean[getCount()];
    }

    public void remove(int position) {
        if (position < getCount()) {
            mTempPositionChange = new boolean[getCount()];
            for (int i = position; i < mTempPositionChange.length; i++) {
                mTempPositionChange[i] = true;
            }
            mFragments.remove(position);
            mFragmentStates.remove(position);
            mFragmentTags.remove(position);
            mFragmentClassNames.remove(position);
            mFragmentArgs.remove(position);
        }
    }

    public void clear(){
        mFragments.clear();
        mFragmentStates.clear();
        mFragmentTags.clear();
        mFragmentClassNames.clear();
        mFragmentArgs.clear();
    }

    @Override
    public void startUpdate(ViewGroup container) {
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment;
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        fragment = mFragments.get(position);
        if (fragment != null) {
            return fragment;
        }
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        FragmentState fs = mFragmentStates.get(position);
        if (fs != null) {
            fragment = fs.instantiate(getFragmentActivity(), getParentFragment());
            // Fix bug
            // http://stackoverflow.com/questions/11381470/classnotfoundexception-when-unmarshalling-android-support-v4-view-viewpagersav
            if (fragment.mSavedFragmentState != null) {
                fragment.mSavedFragmentState.setClassLoader(fragment.getClass().getClassLoader());
            }
        }
        if (fragment == null) {
            fragment = Fragment.instantiate(getFragmentActivity(), mFragmentClassNames.get(position), mFragmentArgs.get(position));
        }
        if (DEBUG) {
            Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        }
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        mFragments.set(position, fragment);
        mFragmentStates.set(position, null);
        mCurTransaction.add(container.getId(), fragment, mFragmentTags.get(position));
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) {
            Log.v(TAG, "Removing item #" + position + ": f=" + object
                    + " v=" + ((Fragment) object).getView());
        }
        if (position < getCount()) {
            FragmentState fragmentState = new FragmentState(fragment);
            Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(fragment);
            if (savedState != null) {
                fragmentState.mSavedFragmentState = savedState.mState;
            }
            mFragmentStates.set(position, fragmentState);
            mFragments.set(position, null);
        }
        mCurTransaction.remove(fragment);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitAllowingStateLoss();
            mCurTransaction = null;
            mFragmentManager.executePendingTransactions();
            // Fix: Fragment is added by transaction. BUT didn't add to FragmentManager's mActive.
            for (Fragment fragment : mFragments) {
                if (fragment != null) {
                    fixActiveFragment(mFragmentManager, fragment);
                }
            }
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment) object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        Bundle state = null;
        // 目前顯示的 Fragments
        for (int i = 0; i < mFragments.size(); i++) {
            Fragment f = mFragments.get(i);
            if (f != null && f.isAdded()) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                mFragmentManager.putFragment(state, key, f);
            }
        }
        if (mFragmentStates.size() > 0) {
            if (state == null) {
                state = new Bundle();
            }
            FragmentState[] fs = new FragmentState[mFragmentStates.size()];
            mFragmentStates.toArray(fs);
            state.putParcelableArray("states_fragment", fs);
        }
        return state;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle) state;
            bundle.setClassLoader(loader);
            Parcelable[] fs = bundle.getParcelableArray("states_fragment");
            mFragments.clear();
            mFragmentStates.clear();
            mFragmentTags.clear();
            mFragmentClassNames.clear();
            mFragmentArgs.clear();
            if (fs != null) {
                for (int i = 0; i < fs.length; i++) {
                    FragmentState fragmentState = (FragmentState) fs[i];
                    mFragmentStates.add(fragmentState);
                    if (fragmentState != null) {
                        mFragmentArgs.add(fragmentState.mArguments);
                        mFragmentTags.add(fragmentState.mTag);
                        mFragmentClassNames.add(fragmentState.mClassName);
                    } else {
                        mFragmentArgs.add(null);
                        mFragmentTags.add(null);
                        mFragmentClassNames.add(null);
                    }
                    mFragments.add(null);
                }
            }
            Iterable<String> keys = bundle.keySet();
            for (String key : keys) {
                if (key.startsWith("f")) {
                    int index = Integer.parseInt(key.substring(1));
                    Fragment f = mFragmentManager.getFragment(bundle, key);
                    if (f != null) {
                        f.setMenuVisibility(false);
                        mFragments.set(index, f);
                        mFragmentArgs.set(index, f.mArguments);
                        mFragmentTags.set(index, f.mTag);
                        mFragmentClassNames.set(index, f.getClass().getName());
                    } else {
                        Log.w(TAG, "Bad fragment at key " + key);
                    }
                }
            }
            // If restore will change
            notifyDataSetChanged();
        }
    }

    public static void fixActiveFragment(FragmentManager fragmentManager, Fragment fragment) {
        FragmentManagerImpl fm = (FragmentManagerImpl) fragmentManager;
        if (fm.mActive != null) {
            int index = fragment.mIndex;
            Fragment origin = fm.mActive.get(index);
            if (origin != null) {
                if ((origin.mIndex != fragment.mIndex) || !(origin.equals(fragment))) {
                    Log.e(TAG,
                            String.format("fixActiveFragment: Not Equal! Origin: %s %s, Fragment: %s $s",
                                    origin.getClass().getName(), origin.mIndex,
                                    fragment.getClass().getName(), fragment.mIndex
                            ));
                }
            }
            fm.mActive.set(index, fragment);
        }
    }

    // Fix
    // http://stackoverflow.com/questions/10396321/remove-fragment-page-from-viewpager-in-android
    @Override
    public int getItemPosition(Object object) {
        int index = mFragments.indexOf(object);
        if (index < 0) {
            return PagerAdapter.POSITION_NONE;
        }
        boolean isPositionChange = mTempPositionChange[index];
        int result = PagerAdapter.POSITION_UNCHANGED;
        if (isPositionChange) {
            result = PagerAdapter.POSITION_NONE;
        }
        return result;
    }
}

ฉันมีเมนเฟรมเป็นเศษ 2 ส่วนและแฟรกเมนต์อื่น ฉันกำลังเพิ่มรูปแบบอื่น ๆ หลังจากตำแหน่งที่ 5 ทุกครั้งและไม่อยู่ในรายการอาร์เรย์ดังนั้นจะลบรูปแบบอื่นได้อย่างไรหากไม่อยู่ในรายการ ฉันใช้ viewpager2
b devloper

ลิงค์ github ไม่ทำงาน
b devloper

-1

คุณสามารถแทนที่destroyItemวิธี

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
}

-9

ฉันหวังว่านี่จะช่วยสิ่งที่คุณต้องการ

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_UNCHANGED;
    }
}

9
ตรงข้ามกับคำตอบที่ได้รับการยอมรับ - สิ่งนี้สามารถทำงานได้อย่างไร?
Zordid
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.