แทนที่ส่วนใน ViewPager


268

ฉันพยายามที่จะใช้ส่วนที่มีการใช้ViewPager FragmentPagerAdapterสิ่งที่ฉันกำลังมองหาที่จะบรรลุคือการแทนที่ส่วนที่อยู่ในหน้าแรกของViewPagerด้วยอีกอันหนึ่ง

เพจเจอร์ประกอบด้วยสองหน้า คนแรกเป็นที่หนึ่งที่สองคือFirstPagerFragment SecondPagerFragmentคลิกที่ปุ่มหน้าแรก ฉันต้องการแทนที่FirstPagerFragmentด้วย NextFragment

มีรหัสของฉันด้านล่าง

public class FragmentPagerActivity extends FragmentActivity {

    static final int NUM_ITEMS = 2;

    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_pager);

        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);

    }


    /**
     * Pager Adapter
     */
    public static class MyAdapter extends FragmentPagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

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

        @Override
        public Fragment getItem(int position) {

            if(position == 0) {
                return FirstPageFragment.newInstance();
            } else {
                return SecondPageFragment.newInstance();
            }

        }
    }


    /**
     * Second Page FRAGMENT
     */
    public static class SecondPageFragment extends Fragment {

        public static SecondPageFragment newInstance() {
            SecondPageFragment f = new SecondPageFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            return inflater.inflate(R.layout.second, container, false);

        }
    }

    /**
     * FIRST PAGE FRAGMENT
     */
    public static class FirstPageFragment extends Fragment {

        Button button;

        public static FirstPageFragment newInstance() {
            FirstPageFragment f = new FirstPageFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            View root = inflater.inflate(R.layout.first, container, false);
            button = (Button) root.findViewById(R.id.button);
            button.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    FragmentTransaction trans = getFragmentManager().beginTransaction();
                                    trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
                    trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
                    trans.addToBackStack(null);
                    trans.commit();

                }

            });

            return root;
        }

        /**
     * Next Page FRAGMENT in the First Page
     */
    public static class NextFragment extends Fragment {

        public static NextFragment newInstance() {
            NextFragment f = new NextFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            return inflater.inflate(R.layout.next, container, false);

        }
    }
}

... และนี่คือไฟล์ xml

fragment_pager.xml

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

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

</LinearLayout>

first.xml

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

  <Button android:id="@+id/button"
     android:layout_width="wrap_content" android:layout_height="wrap_content"
     android:text="to next"/>

</LinearLayout>

ตอนนี้ปัญหา ... ฉันควรใช้รหัสใดใน

trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());

?

ถ้าฉันใช้R.id.first_fragment_root_idการเปลี่ยนจะใช้งานได้ แต่ Hierarchy Viewer จะแสดงพฤติกรรมแปลก ๆ ดังต่อไปนี้

ที่จุดเริ่มต้นสถานการณ์คือ

หลังจากการเปลี่ยนสถานการณ์คือ

อย่างที่คุณเห็นมีบางอย่างผิดปกติฉันคาดว่าจะพบสถานะเดียวกับที่แสดงในภาพแรกหลังจากฉันเปลี่ยนชิ้นส่วน


คุณสร้างไดอะแกรมเลย์เอาต์ด้วยตนเองหรือคุณทำเอง
Jeroen Vannevel

@ ก๋วยเตี๋ยว: ฉันต้องทำแบบเดียวกัน คำถามของคุณสร้างเธรดที่ยาวพร้อมคำตอบที่แตกต่างกันมากมาย สิ่งที่ได้ผลดีที่สุดในที่สุด? ขอบคุณมาก!
narb

1
@JeroenVannevel คุณอาจจะคิดว่ามันออกมาแล้ว แต่ที่แผนภาพรูปแบบคือdeveloper.android.com/tools/performance/hierarchy-viewer/...
Ankit Popli

มีปัญหาบางอย่างต้องการความช่วยเหลือจากคุณstackoverflow.com/questions/40149039/…
อัลเบิร์

คำตอบ:


162

มีวิธีแก้ไขปัญหาอื่นที่ไม่ต้องการแก้ไขซอร์สโค้ดViewPagerและFragmentStatePagerAdapterและใช้ได้กับFragmentPagerAdapterคลาสพื้นฐานที่ผู้เขียนใช้

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

ก่อนอื่นเพื่อทำการViewPagerrepopulate หน้าคุณต้องเรียกnotifyDataSetChanged()ที่อยู่ในคลาสฐานของอะแดปเตอร์ของคุณ

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

นี่ก็หมายความว่าอะแดปเตอร์ของคุณมักจะต้องตระหนักถึงการที่ส่วนที่ควรจะแสดงในตำแหน่งที่ 0, หรือFirstPageFragment NextFragmentวิธีหนึ่งในการทำเช่นนี้คือการจัดหาผู้ฟังเมื่อสร้างFirstPageFragmentซึ่งจะถูกเรียกเมื่อถึงเวลาเปลี่ยนชิ้นส่วน ผมคิดว่านี่เป็นสิ่งที่ดี แต่จะให้จับอะแดปเตอร์ส่วนของชิ้นส่วนทั้งหมดที่สวิทช์และโทรไปและViewPagerFragmentManager

ประการที่สามFragmentPagerAdapterแคชชิ้นส่วนที่ใช้แล้วโดยใช้ชื่อซึ่งได้มาจากตำแหน่งดังนั้นหากมีชิ้นส่วนที่ตำแหน่ง 0 จะไม่ถูกแทนที่แม้ว่าคลาสนั้นจะใหม่ มีวิธีแก้ไขอยู่สองวิธี แต่วิธีที่ง่ายที่สุดคือใช้remove()ฟังก์ชันFragmentTransactionซึ่งจะลบแท็กออกด้วย

นั่นเป็นข้อความจำนวนมากนี่คือรหัสที่ควรใช้ในกรณีของคุณ:

public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {
            if (mFragmentAtPos0 == null)
            {
                mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()
                {
                    public void onSwitchToNextFragment()
                    {
                        mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
                        mFragmentAtPos0 = NextFragment.newInstance();
                        notifyDataSetChanged();
                    }
                });
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

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

    @Override
    public int getItemPosition(Object object)
    {
        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }
}

public interface FirstPageFragmentListener
{
    void onSwitchToNextFragment();
}

หวังว่านี่จะช่วยให้ทุกคน!


24
ใช้งานได้สำหรับฉันด้วย แต่จะใช้ปุ่มย้อนกลับเพื่อแสดงส่วนแรกได้อย่างไร
user1159819

6
คำอธิบายที่ยอดเยี่ยม แต่คุณสามารถโพสต์ซอร์สโค้ดแบบเต็มได้โดยเฉพาะคลาส FirstPageFragment และ SecondPageFragment
georgiecasey

6
คุณจะเพิ่ม FirstPageFragmentListener ใน Bundle ใน newInstance () ได้อย่างไร
miguel

4
คุณสามารถเพิ่มรหัสที่คุณจัดการกับFirstPageFragment.newInstance()ผู้ฟังพารามิเตอร์ได้หรือไม่
MiguelHincapieC

6
สิ่งนี้ใช้ได้กับฉันถ้าฉันเรียก commitNow () แทนที่จะส่ง () เมื่อนำแฟรกเมนต์ออก ไม่แน่ใจว่าทำไมกรณีการใช้งานของฉันแตกต่างกัน แต่หวังว่านี่จะช่วยประหยัดเวลาได้บ้าง
Ian Lovejoy

37

ตั้งแต่วันที่ 13 พฤศจิกายน 2012 การเปลี่ยนชิ้นส่วนใน ViewPager ดูเหมือนจะง่ายขึ้นมาก Google เปิดตัว Android 4.2 พร้อมการสนับสนุนชิ้นส่วนที่ซ้อนกันและได้รับการสนับสนุนในAndroid Support Library v11 ใหม่เพื่อให้สามารถใช้งานได้จนถึง 1.6

มันค่อนข้างคุ้นเคยกับวิธีปกติในการเปลี่ยนชิ้นส่วนยกเว้นว่าคุณใช้ getChildFragmentManager ดูเหมือนว่าจะทำงานยกเว้นสแต็คแบ็คสแต็คที่ซ้อนกันจะไม่โผล่เมื่อผู้ใช้คลิกปุ่มย้อนกลับ ตามวิธีแก้ไขปัญหาในคำถามที่เชื่อมโยงนั้นคุณจะต้องเรียก popBackStackImmediate () ด้วยตนเองใน child manager ของแฟรกเมนต์ ดังนั้นคุณต้องแทนที่ onBackPressed () ของกิจกรรม ViewPager ที่คุณจะได้รับส่วนย่อยปัจจุบันของ ViewPager และเรียกใช้ getChildFragmentManager () popBackStackImmediate ()

การได้รับแฟรกเมนต์ที่กำลังแสดงอยู่นั้นค่อนข้างแฮ็คด้วยฉันใช้"android: switcher: VIEWPAGER_ID: INDEX" ที่สกปรกแต่คุณยังสามารถติดตาม ViewPager ทั้งหมดได้ด้วยตัวคุณเองตามที่อธิบายไว้ในโซลูชันที่สองในหน้านี้

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

HomeActivity.java

 public class HomeActivity extends SherlockFragmentActivity {
FragmentAdapter mAdapter;
ViewPager mPager;
TabPageIndicator mIndicator;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    mAdapter = new FragmentAdapter(getSupportFragmentManager());
    mPager = (ViewPager)findViewById(R.id.pager);
    mPager.setAdapter(mAdapter);
    mIndicator = (TabPageIndicator)findViewById(R.id.indicator);
    mIndicator.setViewPager(mPager);
}

// This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update.
@Override
public void onBackPressed() {
    Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem());
    if (fragment != null) // could be null if not instantiated yet
    {
        if (fragment.getView() != null) {
            // Pop the backstack on the ChildManager if there is any. If not, close this activity as normal.
            if (!fragment.getChildFragmentManager().popBackStackImmediate()) {
                finish();
            }
        }
    }
}

class FragmentAdapter extends FragmentPagerAdapter {        
    public FragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
        case 0:
            return ListProductsFragment.newInstance();
        case 1:
            return ListActiveSubstancesFragment.newInstance();
        case 2:
            return ListProductFunctionsFragment.newInstance();
        case 3:
            return ListCropsFragment.newInstance();
        default:
            return null;
        }
    }

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

 }
}

ListProductsFragment.java

public class ListProductsFragment extends SherlockFragment {
private ListView list;

public static ListProductsFragment newInstance() {
    ListProductsFragment f = new ListProductsFragment();
    return f;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View V = inflater.inflate(R.layout.list, container, false);
    list = (ListView)V.findViewById(android.R.id.list);
    list.setOnItemClickListener(new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view,
            int position, long id) {
          // This is important bit
          Fragment productDetailFragment = FragmentProductDetail.newInstance();
          FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
          transaction.addToBackStack(null);
          transaction.replace(R.id.products_list_linear, productDetailFragment).commit();
        }
      });       
    return V;
}
}

7
เป็นไปได้ไหมที่คุณจะอัปโหลดรหัสไปยัง GitHub? ขอบคุณ
Gautham

4
@georgiecasey: ฉันดาวน์โหลด API 17, Android 4.2 ซึ่งอนุญาตให้ฉันเริ่มใช้ getChildFragmentManager () แต่ฉันต้องหายไปจากการแก้ปัญหาของคุณเพราะตอนนี้ฉันกำลังทับเศษเล็กเศษน้อยเก่า + ใหม่บนหน้าจอ หมายเหตุ: ฉันพยายามที่จะใช้วิธีการแก้ปัญหาของคุณในคอนเสิร์ตด้วยโค้ด Google ตัวอย่าง TabsAdapter จากdeveloper.android.com/reference/android/support/v4/view/... ขอบคุณสำหรับคำแนะนำและ / หรือรหัสอ้างอิง
gcl1

27
R.id.products_list_linear อ้างถึงอะไร กรุณาลิงค์ไปยังรหัสของคุณตามที่เสนอ
howettl

1
@howettl ID อ้างอิงถึงสมาชิกแฟรกเมนต์ด้านนอก กุญแจสำคัญในการแก้ปัญหานี้คือการใช้ชิ้นส่วนซ้อนกัน (ในแบบที่คุณไม่ต้องจัดการกับอะแดปเตอร์ viewpager)
Indrek Kueue

2
สิ่งนี้เคยทำให้มันเป็น github หรือไม่?
บารุค

32

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

นี่จะเป็น FragmentPagerAdapter:

public static class MyAdapter extends FragmentPagerAdapter {
    private final class CalendarPageListener implements
            CalendarPageFragmentListener {
        public void onSwitchToNextFragment() {
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                    .commit();
            if (mFragmentAtPos0 instanceof FirstFragment){
                mFragmentAtPos0 = NextFragment.newInstance(listener);
            }else{ // Instance of NextFragment
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            notifyDataSetChanged();
        }
    }

    CalendarPageListener listener = new CalendarPageListener();;
    private Fragment mFragmentAtPos0;
    private FragmentManager mFragmentManager;

    public MyAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
    }

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

    @Override
    public int getItemPosition(Object object) {
        if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
        if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }

    @Override
    public Fragment getItem(int position) {
        if (position == 0)
            return Portada.newInstance();
        if (position == 1) { // Position where you want to replace fragments
            if (mFragmentAtPos0 == null) {
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            return mFragmentAtPos0;
        }
        if (position == 2)
            return Clasificacion.newInstance();
        if (position == 3)
            return Informacion.newInstance();

        return null;
    }
}

public interface CalendarPageFragmentListener {
    void onSwitchToNextFragment();
}

ในการเปลี่ยนชิ้นส่วนทดแทนให้กำหนดฟิลด์สแตติกของชนิดCalendarPageFragmentListenerและกำหนดค่าเริ่มต้นผ่านnewInstanceวิธีการของแฟรกเมนต์ที่สอดคล้องกันและการโทรFirstFragment.pageListener.onSwitchToNextFragment()หรือตามNextFragment.pageListener.onSwitchToNextFragment()ลำดับ


คุณจะทำการทดแทนอย่างไร เป็นไปไม่ได้ที่จะเริ่มต้นฟังโดยไม่ต้องเอาชนะวิธี onSwitchToNext
Leandros

1
คุณจะไม่สูญเสียสถานะที่บันทึกไว้ใน mFragmentAtPos0 หากคุณหมุนอุปกรณ์หรือไม่
seb

@seb ฉันปรับปรุงโซลูชันนี้เพื่อบันทึกmFragmentAtPos0การอ้างอิงเมื่อบันทึกสถานะกิจกรรม มันไม่ใช่ทางออกที่หรูหราที่สุด แต่ใช้ได้ผล
mdelolmo

คุณสามารถดูคำตอบของฉัน มันจะแทนที่ชิ้นส่วนและกลับไปเป็นชิ้นแรก
Lyd

@mdelolmo คุณสามารถดูคำถามเกี่ยวกับ ViewPager นี้ได้ไหม ขอขอบคุณstackoverflow.com/questions/27937250/…
Hoa Vu

21

ฉันได้ติดตั้งโซลูชันสำหรับ:

  • การเปลี่ยนชิ้นส่วนแบบไดนามิกภายในแท็บ
  • การบำรุงรักษาประวัติต่อแท็บ
  • การทำงานกับการเปลี่ยนแปลงการวางแนว

เทคนิคในการบรรลุเป้าหมายดังต่อไปนี้:

  • ใช้เมธอด INFORMDataSetChanged () เพื่อใช้การแทนที่แฟรกเมนต์
  • ใช้ตัวจัดการชิ้นส่วนสำหรับระยะหลังเท่านั้นและห้ามเปลี่ยนชิ้นส่วน
  • รักษาประวัติโดยใช้รูปแบบของที่ระลึก (สแต็ค)

รหัสของอะแดปเตอร์มีดังต่อไปนี้:

public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {

/** The sherlock fragment activity. */
private final SherlockFragmentActivity mActivity;

/** The action bar. */
private final ActionBar mActionBar;

/** The pager. */
private final ViewPager mPager;

/** The tabs. */
private List<TabInfo> mTabs = new LinkedList<TabInfo>();

/** The total number of tabs. */
private int TOTAL_TABS;

private Map<Integer, Stack<TabInfo>> history = new HashMap<Integer, Stack<TabInfo>>();

/**
 * Creates a new instance.
 *
 * @param activity the activity
 * @param pager    the pager
 */
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
    super(activity.getSupportFragmentManager());
    activity.getSupportFragmentManager();
    this.mActivity = activity;
    this.mActionBar = activity.getSupportActionBar();
    this.mPager = pager;
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}

/**
 * Adds the tab.
 *
 * @param image         the image
 * @param fragmentClass the class
 * @param args          the arguments
 */
public void addTab(final Drawable image, final Class fragmentClass, final Bundle args) {
    final TabInfo tabInfo = new TabInfo(fragmentClass, args);
    final ActionBar.Tab tab = mActionBar.newTab();
    tab.setTabListener(this);
    tab.setTag(tabInfo);
    tab.setIcon(image);

    mTabs.add(tabInfo);
    mActionBar.addTab(tab);

    notifyDataSetChanged();
}

@Override
public Fragment getItem(final int position) {
    final TabInfo tabInfo = mTabs.get(position);
    return Fragment.instantiate(mActivity, tabInfo.fragmentClass.getName(), tabInfo.args);
}

@Override
public int getItemPosition(final Object object) {
    /* Get the current position. */
    int position = mActionBar.getSelectedTab().getPosition();

    /* The default value. */
    int pos = POSITION_NONE;
    if (history.get(position).isEmpty()) {
        return POSITION_NONE;
    }

    /* Checks if the object exists in current history. */
    for (Stack<TabInfo> stack : history.values()) {
        TabInfo c = stack.peek();
        if (c.fragmentClass.getName().equals(object.getClass().getName())) {
            pos = POSITION_UNCHANGED;
            break;
        }
    }
    return pos;
}

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

@Override
public void onPageScrollStateChanged(int arg0) {
}

@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}

@Override
public void onPageSelected(int position) {
    mActionBar.setSelectedNavigationItem(position);
}

@Override
public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction ft) {
    TabInfo tabInfo = (TabInfo) tab.getTag();
    for (int i = 0; i < mTabs.size(); i++) {
        if (mTabs.get(i).equals(tabInfo)) {
            mPager.setCurrentItem(i);
        }
    }
}

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

public void replace(final int position, final Class fragmentClass, final Bundle args) {
    /* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();
}

/**
 * Updates the tabs.
 *
 * @param tabInfo
 *          the new tab info
 * @param position
 *          the position
 */
private void updateTabs(final TabInfo tabInfo, final int position) {
    mTabs.remove(position);
    mTabs.add(position, tabInfo);
    mActionBar.getTabAt(position).setTag(tabInfo);
}

/**
 * Creates the history using the current state.
 */
public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

/**
 * Called on back
 */
public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

/**
 * Returns if the history is empty.
 *
 * @param position
 *          the position
 * @return  the flag if empty
 */
private boolean historyIsEmpty(final int position) {
    return history == null || history.isEmpty() || history.get(position).isEmpty();
}

private boolean isLastItemInHistory(final int position) {
    return history.get(position).size() == 1;
}

/**
 * Returns the previous state by the position provided.
 *
 * @param position
 *          the position
 * @return  the tab info
 */
private TabInfo getPrevious(final int position) {
    TabInfo currentTabInfo = history.get(position).pop();
    if (!history.get(position).isEmpty()) {
        currentTabInfo = history.get(position).peek();
    }
    return currentTabInfo;
}

/** The tab info class */
private static class TabInfo {

    /** The fragment class. */
    public Class fragmentClass;

    /** The args.*/
    public Bundle args;

    /**
     * Creates a new instance.
     *
     * @param fragmentClass
     *          the fragment class
     * @param args
     *          the args
     */
    public TabInfo(Class fragmentClass, Bundle args) {
        this.fragmentClass = fragmentClass;
        this.args = args;
    }

    @Override
    public boolean equals(final Object o) {
        return this.fragmentClass.getName().equals(o.getClass().getName());
    }

    @Override
    public int hashCode() {
        return fragmentClass.getName() != null ? fragmentClass.getName().hashCode() : 0;
    }

    @Override
    public String toString() {
        return "TabInfo{" +
                "fragmentClass=" + fragmentClass +
                '}';
    }
}

ครั้งแรกที่คุณเพิ่มแท็บทั้งหมดเราต้องเรียกเมธอด createHistory () เพื่อสร้างประวัติเริ่มต้น

public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

ทุกครั้งที่คุณต้องการแทนที่แฟรกเมนต์เป็นแท็บเฉพาะที่คุณโทรหา: แทนที่ (ตำแหน่งสุดท้ายสุดท้าย, แฟรกเมนต์คลาสสุดท้าย, คลาส Bundle สุดท้าย)

/* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();

เมื่อกดกลับคุณต้องโทรหา back () วิธีการ:

public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

วิธีแก้ปัญหานี้ทำงานได้กับแถบแอ็คชันเชอร์ล็อคและด้วยการปัดนิ้ว


มันเยี่ยมมาก! คุณสามารถวางมันบน GitHub ได้หรือไม่?
Nicramus

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

ไม่มีใครมีปัญหากับการใช้งานนี้หรือไม่? ฉันมีพวกเขา 2 คน 1. หลังแรกไม่ได้ลงทะเบียน - ฉันต้องคลิกกลับสองครั้งเพื่อลงทะเบียน 2. หลังจากฉันคลิกย้อนกลับ (สองครั้ง) บนแท็บ a และไปที่แท็บ b เมื่อฉันกลับไปที่แท็บ a จะแสดงเฉพาะเนื้อหาของแท็บ b
yaronbic

7

tl; dr:ใช้แฟรกเมนต์โฮสต์ที่รับผิดชอบในการแทนที่เนื้อหาโฮสต์และติดตามประวัติการนำทางด้านหลัง (เช่นในเบราว์เซอร์)

เนื่องจากกรณีการใช้งานของคุณประกอบด้วยแท็บจำนวนคงที่โซลูชันของฉันทำงานได้ดี: แนวคิดคือการเติม ViewPager ด้วยอินสแตนซ์ของคลาสแบบกำหนดเองHostFragmentที่สามารถแทนที่เนื้อหาที่โฮสต์และเก็บประวัติการนำทางของตัวเองกลับมาได้ ในการแทนที่แฟรกเมนต์ที่โฮสต์คุณทำการโทรไปที่เมธอดhostfragment.replaceFragment():

public void replaceFragment(Fragment fragment, boolean addToBackstack) {
    if (addToBackstack) {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
    } else {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
    }
}

วิธีการทั้งหมดนั้นคือการแทนที่โครงร่างเฟรมด้วย id R.id.hosted_fragmentด้วยแฟรกเมนต์ที่จัดเตรียมให้กับเมธอด

ตรวจสอบการสอนของฉันในหัวข้อนี้สำหรับรายละเอียดเพิ่มเติมและตัวอย่างการทำงานที่สมบูรณ์ใน GitHub!


แม้ว่าสิ่งนี้จะดูสมส่วน แต่ฉันคิดว่าในความเป็นจริงแล้ววิธีที่ดีที่สุด (ในแง่ของโครงสร้างและความง่ายในการบำรุงรักษา)
Sira Lam

ฉันลงเอยที่ NPE : / ไม่พบมุมมองที่จะถูกแทนที่
Fariz Fakkel

6

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

สิ่งที่เป็นที่FragmentPagerAdapterระดับคือการใช้รหัสรายการในการจัดเก็บชิ้นส่วนที่เก็บไว้ชั่วคราวเพื่อFragmentManager ด้วยเหตุผลนี้คุณต้องแทนที่เมธอด getItemId (ตำแหน่ง int)เพื่อให้ส่งคืนเช่นตำแหน่งสำหรับหน้าระดับบนและตำแหน่ง 100 +สำหรับหน้ารายละเอียด ไม่เช่นนั้นแฟรกเมนต์ระดับบนสุดที่สร้างไว้ก่อนหน้านี้จะถูกส่งคืนจากแคชแทนที่จะเป็นแฟรกเมนต์ระดับรายละเอียด

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

public class TabsActivity extends FragmentActivity {

  public static final int PAGE_COUNT = 3;
  public static final int FIRST_PAGE = 0;
  public static final int SECOND_PAGE = 1;
  public static final int THIRD_PAGE = 2;

  /**
   * Opens a new inferior page at specified tab position and adds the current page into back
   * stack.
   */
  public void startPage(int position, Fragment content) {
    // Replace page adapter fragment at position.
    mPagerAdapter.start(position, content);
  }

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

    // Initialize basic layout.
    this.setContentView(R.layout.tabs_activity);

    // Add tab fragments to view pager.
    {
      // Create fragments adapter.
      mPagerAdapter = new PagerAdapter(pager);
      ViewPager pager = (ViewPager) super.findViewById(R.id.tabs_view_pager);
      pager.setAdapter(mPagerAdapter);

      // Update active tab in tab bar when page changes.
      pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int index, float value, int nextIndex) {
          // Not used.
        }

        @Override
        public void onPageSelected(int index) {
          RadioGroup tabs_radio_group = (RadioGroup) TabsActivity.this.findViewById(
            R.id.tabs_radio_group);
          switch (index) {
            case 0: {
              tabs_radio_group.check(R.id.first_radio_button);
            }
            break;
            case 1: {
              tabs_radio_group.check(R.id.second_radio_button);
            }
            break;
            case 2: {
              tabs_radio_group.check(R.id.third_radio_button);
            }
            break;
          }
        }

        @Override
        public void onPageScrollStateChanged(int index) {
          // Not used.
        }
      });
    }

    // Set "tabs" radio group on checked change listener that changes the displayed page.
    RadioGroup radio_group = (RadioGroup) this.findViewById(R.id.tabs_radio_group);
    radio_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
      @Override
      public void onCheckedChanged(RadioGroup radioGroup, int id) {
        // Get view pager representing tabs.
        ViewPager view_pager = (ViewPager) TabsActivity.this.findViewById(R.id.tabs_view_pager);
        if (view_pager == null) {
          return;
        }

        // Change the active page.
        switch (id) {
          case R.id.first_radio_button: {
            view_pager.setCurrentItem(FIRST_PAGE);
          }
          break;
          case R.id.second_radio_button: {
            view_pager.setCurrentItem(SECOND_PAGE);
          }
          break;
          case R.id.third_radio_button: {
            view_pager.setCurrentItem(THIRD_PAGE);
          }
          break;
        }
      });
    }
  }

  @Override
  public void onBackPressed() {
    if (!mPagerAdapter.back()) {
      super.onBackPressed();
    }
  }

  /**
   * Serves the fragments when paging.
   */
  private class PagerAdapter extends FragmentPagerAdapter {

    public PagerAdapter(ViewPager container) {
      super(TabsActivity.this.getSupportFragmentManager());

      mContainer = container;
      mFragmentManager = TabsActivity.this.getSupportFragmentManager();

      // Prepare "empty" list of fragments.
      mFragments = new ArrayList<Fragment>(){};
      mBackFragments = new ArrayList<Fragment>(){};
      for (int i = 0; i < PAGE_COUNT; i++) {
        mFragments.add(null);
        mBackFragments.add(null);
      }
    }

    /**
     * Replaces the view pager fragment at specified position.
     */
    public void replace(int position, Fragment fragment) {
      // Get currently active fragment.
      Fragment old_fragment = mFragments.get(position);
      if (old_fragment == null) {
        return;
      }

      // Replace the fragment using transaction and in underlaying array list.
      // NOTE .addToBackStack(null) doesn't work
      this.startUpdate(mContainer);
      mFragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
        .remove(old_fragment).add(mContainer.getId(), fragment)
        .commit();
      mFragments.set(position, fragment);
      this.notifyDataSetChanged();
      this.finishUpdate(mContainer);
    }

    /**
     * Replaces the fragment at specified position and stores the current fragment to back stack
     * so it can be restored by #back().
     */
    public void start(int position, Fragment fragment) {
      // Remember current fragment.
      mBackFragments.set(position, mFragments.get(position));

      // Replace the displayed fragment.
      this.replace(position, fragment);
    }

    /**
     * Replaces the current fragment by fragment stored in back stack. Does nothing and returns
     * false if no fragment is back-stacked.
     */
    public boolean back() {
      int position = mContainer.getCurrentItem();
      Fragment fragment = mBackFragments.get(position);
      if (fragment == null) {
        // Nothing to go back.
        return false;
      }

      // Restore the remembered fragment and remove it from back fragments.
      this.replace(position, fragment);
      mBackFragments.set(position, null);
      return true;
    }

    /**
     * Returns fragment of a page at specified position.
     */
    @Override
    public Fragment getItem(int position) {
      // If fragment not yet initialized, create its instance.
      if (mFragments.get(position) == null) {
        switch (position) {
          case FIRST_PAGE: {
            mFragments.set(FIRST_PAGE, new DefaultFirstFragment());
          }
          break;
          case SECOND_PAGE: {
            mFragments.set(SECOND_PAGE, new DefaultSecondFragment());
          }
          break;
          case THIRD_PAGE: {
            mFragments.set(THIRD_PAGE, new DefaultThirdFragment());
          }
          break;
        }
      }

      // Return fragment instance at requested position.
      return mFragments.get(position);
    }

    /**
     * Custom item ID resolution. Needed for proper page fragment caching.
     * @see FragmentPagerAdapter#getItemId(int).
     */
    @Override
    public long getItemId(int position) {
      // Fragments from second level page hierarchy have their ID raised above 100. This is
      // important to FragmentPagerAdapter because it is caching fragments to FragmentManager with
      // this item ID key.
      Fragment item = mFragments.get(position);
      if (item != null) {
        if ((item instanceof NewFirstFragment) || (item instanceof NewSecondFragment) ||
          (item instanceof NewThirdFragment)) {
          return 100 + position;
        }
      }

      return position;
    }

    /**
     * Returns number of pages.
     */
    @Override
    public int getCount() {
      return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object)
    {
      int position = POSITION_UNCHANGED;
      if ((object instanceof DefaultFirstFragment) || (object instanceof NewFirstFragment)) {
        if (object.getClass() != mFragments.get(FIRST_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultSecondragment) || (object instanceof NewSecondFragment)) {
        if (object.getClass() != mFragments.get(SECOND_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultThirdFragment) || (object instanceof NewThirdFragment)) {
        if (object.getClass() != mFragments.get(THIRD_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      return position;
    }

    private ViewPager mContainer;
    private FragmentManager mFragmentManager;

    /**
     * List of page fragments.
     */
    private List<Fragment> mFragments;

    /**
     * List of page fragments to return to in onBack();
     */
    private List<Fragment> mBackFragments;
  }

  /**
   * Tab fragments adapter.
   */
  private PagerAdapter mPagerAdapter;
}

อืมมีปัญหาอื่นที่ใช้รหัสนี้ เมื่อฉันต้องการเริ่มกิจกรรมจากแฟรกเมนต์ที่ถูกลบและเพิ่ม aggain ฉันได้รับข้อผิดพลาด: "สถานะการประหยัดล้มเหลว: ใช้งาน NewFirstFragment {405478a0} ได้ล้างดัชนี: -1" (การติดตามสแต็กเต็ม: nopaste.info/547a23dfea.html ) เมื่อมองไปที่แหล่ง Android ฉันพบว่ามันมีบางอย่างเกี่ยวกับแบ็คสแต็ค แต่ฉันไม่รู้จะแก้ไขได้อย่างไร ไม่มีใครมีเงื่อนงำหรือไม่?
Blackhex

2
เจ๋งโดยใช้ <code> mFragmentManager.beginTransaction (). detach (old_fragment) .attach (แฟรกเมนต์) .commit (); </code> แทน <pre> mFragmentManager.beginTransaction () setTransition old_fragment) .add (mContainer.getId (), fragment) .commit (); </pre> ในตัวอย่างด้านบนดูเหมือนว่าจะแก้ไขปัญหาทั้งสอง :-)
Blackhex

1
หากคุณใช้วิธีนี้ตรวจสอบให้แน่ใจว่าคุณไม่ได้ให้ Android สร้างกิจกรรมของคุณใหม่เมื่อหมุนอุปกรณ์ มิฉะนั้น PagerAdapter จะถูกสร้างขึ้นใหม่และคุณจะสูญเสีย mBackFragments ของคุณ สิ่งนี้จะทำให้ FragmentManager สับสนอย่างจริงจัง การใช้ BackStack หลีกเลี่ยงปัญหานี้โดยเสียค่าใช้จ่ายในการติดตั้ง PagerAdapter ที่ซับซ้อนมากขึ้น (เช่นคุณไม่สามารถสืบทอดจาก FragmentPagerAdapter)
Norman

6

ฉันสร้าง ViewPager ด้วย 3 องค์ประกอบและองค์ประกอบย่อย 2 รายการสำหรับดัชนี 2 และ 3 และที่นี่สิ่งที่ฉันต้องการจะทำ ..

ป้อนคำอธิบายรูปภาพที่นี่

ฉันได้ดำเนินการนี้ด้วยความช่วยเหลือจากคำถามก่อนหน้านี้และคำตอบจาก StackOverFlow และนี่คือลิงค์

ViewPagerChildFragments


โครงการของคุณล้มเหลวหากคุณหมุนอุปกรณ์
Dory

6

เพื่อแทนที่เศษภายในViewPagerคุณสามารถย้ายรหัสแหล่งที่มาของViewPager, PagerAdapterและFragmentStatePagerAdapterการเรียนในโครงการของคุณและเพิ่มรหัสต่อไปนี้

เป็นViewPager:

public void notifyItemChanged(Object oldItem, Object newItem) {
    if (mItems != null) {
            for (ItemInfo itemInfo : mItems) {
                        if (itemInfo.object.equals(oldItem)) {
                                itemInfo.object = newItem;
                        }
                    }
       }
       invalidate();
    }

เป็น FragmentStatePagerAdapter:

public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) {
       startUpdate(container);

       // remove old fragment

       if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
       int position = getFragmentPosition(oldFragment);
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, null);
        mFragments.set(position, null);

        mCurTransaction.remove(oldFragment);

        // add new fragment

        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        mFragments.set(position, newFragment);
        mCurTransaction.add(container.getId(), newFragment);

       finishUpdate(container);

       // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
       handleGetItemInbalidated(container, oldFragment, newFragment);

       container.notifyItemChanged(oldFragment, newFragment);
    }

protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment);
protected abstract int  getFragmentPosition(Fragment fragment);

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

ตอนนี้เพื่อแทนที่การเรียกใช้แฟรกเมนต์

mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);

หากคุณสนใจในโครงการตัวอย่างขอให้ฉันแหล่งที่มา


เมธอด getFragmentPosition () และ handleGetItemInbalidated ควรเป็นอย่างไร () ฉันไม่สามารถอัปเดต getItem () เพื่อส่งคืน NewFragment .. โปรดช่วย ..

Hi! โปรดหาโปรเจ็กต์ทดสอบได้ที่ลิงค์ไฟล์
AndroidTeam ที่ Mail.Ru

1
โครงการทดสอบของคุณล้มเหลวหากคุณหมุนอุปกรณ์
seb

1
@ AndroidTeamAtMail.Ru การเชื่อมโยงดูเหมือนจะหมดอายุแล้วคุณจะกรุณาแบ่งปันรหัส
ซันนี่

คุณสามารถดูคำตอบของฉัน มันจะแทนที่ชิ้นส่วนและกลับไปเป็นชิ้นแรก
Lyd

4

ใช้งานได้ดีกับโซลูชันของ AndroidTeam แต่ฉันพบว่าฉันต้องการความสามารถในการกลับไปเหมือนเดิมFrgmentTransaction.addToBackStack(null) แต่การเพิ่มสิ่งนี้จะทำให้ Fragment ถูกแทนที่โดยไม่แจ้ง ViewPager การรวมโซลูชันที่ให้มากับการปรับปรุงเล็ก ๆ น้อย ๆ นี้จะช่วยให้คุณกลับสู่สถานะก่อนหน้าโดยเพียงแค่แทนที่onBackPressed()วิธีการของกิจกรรม ข้อเสียเปรียบที่ใหญ่ที่สุดคือมันจะย้อนกลับไปทีละครั้งเท่านั้นซึ่งอาจทำให้เกิดการคลิกกลับหลายครั้ง

private ArrayList<Fragment> bFragments = new ArrayList<Fragment>();
private ArrayList<Integer> bPosition = new ArrayList<Integer>();

public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) {
    startUpdate(container);

    // remove old fragment

    if (mCurTransaction == null) {
         mCurTransaction = mFragmentManager.beginTransaction();
     }
    int position = getFragmentPosition(oldFragment);
     while (mSavedState.size() <= position) {
         mSavedState.add(null);
     }

     //Add Fragment to Back List
     bFragments.add(oldFragment);

     //Add Pager Position to Back List
     bPosition.add(position);

     mSavedState.set(position, null);
     mFragments.set(position, null);

     mCurTransaction.remove(oldFragment);

     // add new fragment

     while (mFragments.size() <= position) {
         mFragments.add(null);
     }
     mFragments.set(position, newFragment);
     mCurTransaction.add(container.getId(), newFragment);

    finishUpdate(container);

    // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
    handleGetItemInvalidated(container, oldFragment, newFragment);

    container.notifyItemChanged(oldFragment, newFragment);
 }


public boolean popBackImmediate(ViewPager container){
    int bFragSize = bFragments.size();
    int bPosSize = bPosition.size();

    if(bFragSize>0 && bPosSize>0){
        if(bFragSize==bPosSize){
            int last = bFragSize-1;
            int position = bPosition.get(last);

            //Returns Fragment Currently at this position
            Fragment replacedFragment = mFragments.get(position);               
            Fragment originalFragment = bFragments.get(last);

            this.replaceFragments(container, replacedFragment, originalFragment);

            bPosition.remove(last);
            bFragments.remove(last);

            return true;
        }
    }

    return false;       
}

หวังว่านี่จะช่วยใครซักคน

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

    @Override
    protected int getFragmentPosition(Fragment fragment) {
            if(fragment.equals(originalFragment1)){
                return 0;
            }
            if(fragment.equals(replacementFragment1)){
                return 0;
            }
            if(fragment.equals(Fragment2)){
                return 1;
            }
        return -1;
    }

3

ในonCreateViewวิธีการของคุณcontainerเป็นจริงViewPagerตัวอย่าง

ดังนั้นเพียงแค่โทร

ViewPager vpViewPager = (ViewPager) container;
vpViewPager.setCurrentItem(1);

ViewPagerส่วนจะมีการเปลี่ยนแปลงในปัจจุบันของคุณ


1
จากการค้นหาหลายชั่วโมงมันเป็นโค้ดหนึ่งบรรทัดที่ฉันต้องการเพื่อให้แท็บทั้งหมดทำงานอย่างถูกต้อง vpViewPager.setCurrentItem(1);. ฉันผ่านตัวอย่างของแต่ละคนโดยไม่มีอะไรเกิดขึ้นในแต่ละครั้งจนกว่าฉันจะไปหาคุณในที่สุด ขอบคุณ.
แบรนดอน

1
ใจปลื้ม ... นี่มันวิเศษมาก !! ฉันไม่ได้ตระหนักว่าตัวแปรคอนเทนเนอร์เป็นตัวดูเอง คำตอบนี้จะต้องมีการสำรวจก่อนก่อนอื่นยาวทั้งหมด นอกจากนี้ยังไม่มีการส่งผ่านตัวแปรฟังให้กับตัวสร้างแฟรกเมนต์ซึ่งทำให้การใช้งานทั้งหมดง่ายขึ้นเมื่อ Android สร้างแฟรกเมนต์ใหม่โดยอัตโนมัติโดยใช้คอนสตรัคเตอร์ no-arg เมื่อเปลี่ยนแปลงการกำหนดค่า
Kevin Lee

3
นี่ไม่ใช่แค่เปลี่ยนเป็นแท็บที่มีอยู่ใช่หรือไม่ สิ่งนี้เกี่ยวข้องกับการเปลี่ยนชิ้นส่วนได้อย่างไร
behelit

2
เห็นด้วยกับ @behelit นี่ไม่ได้ตอบคำถามมันจะย้ายViewPagerไปยังหน้าอื่นมันจะไม่แทนที่ส่วนใด ๆ
Yoann Hercouet

2

นี่เป็นวิธีแก้ปัญหาของฉันที่ค่อนข้างง่าย กุญแจในการแก้ปัญหานี้คือการใช้FragmentStatePagerAdapterแทนการFragmentPagerAdapterเป็นอดีตจะลบชิ้นส่วนที่ไม่ได้ใช้สำหรับคุณในขณะที่ยังคงรักษาอินสแตนซ์ในภายหลัง ที่สองคือการใช้งานPOSITION_NONEใน getItem () ฉันใช้ลิสต์แบบง่ายเพื่อติดตามเศษของฉัน ความต้องการของฉันคือการแทนที่รายการทั้งหมดของชิ้นส่วนพร้อมกันด้วยรายการใหม่ แต่ด้านล่างสามารถแก้ไขได้อย่างง่ายดายเพื่อแทนที่ชิ้นส่วนแต่ละรายการ:

public class MyFragmentAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragmentList = new ArrayList<Fragment>();
    private List<String> tabTitleList = new ArrayList<String>();

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

    public void addFragments(List<Fragment> fragments, List<String> titles) {
        fragmentList.clear();
        tabTitleList.clear();
        fragmentList.addAll(fragments);
        tabTitleList.addAll(titles);
        notifyDataSetChanged();
    }

    @Override
    public int getItemPosition(Object object) {
        if (fragmentList.contains(object)) {
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int item) {
        if (item >= fragmentList.size()) {
            return null;
        }
        return fragmentList.get(item);
    }

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

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

2

ฉันยังแก้ปัญหาซึ่งทำงานกับสแต็ค มันขึ้นแบบแยกส่วนวิธีการเพื่อ u FragmentPagerAdapterไม่ได้มีการระบุในแต่ละส่วนและรายละเอียดส่วนในของคุณ มันสร้างจากตัวอย่างจาก ActionbarSherlock ซึ่งจะเกิดขึ้นถ้าฉันถูกต้องจากแอพ Google Demo

/**
 * This is a helper class that implements the management of tabs and all
 * details of connecting a ViewPager with associated TabHost.  It relies on a
 * trick.  Normally a tab host has a simple API for supplying a View or
 * Intent that each tab will show.  This is not sufficient for switching
 * between pages.  So instead we make the content part of the tab host
 * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
 * view to show as the tab content.  It listens to changes in tabs, and takes
 * care of switch to the correct paged in the ViewPager whenever the selected
 * tab changes.
 * 
 * Changed to support more Layers of fragments on each Tab.
 * by sebnapi (2012)
 * 
 */
public class TabsAdapter extends FragmentPagerAdapter
        implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
    private final Context mContext;
    private final TabHost mTabHost;
    private final ViewPager mViewPager;

    private ArrayList<String> mTabTags = new ArrayList<String>();
    private HashMap<String, Stack<TabInfo>> mTabStackMap = new HashMap<String, Stack<TabInfo>>();

    static final class TabInfo {
        public final String tag;
        public final Class<?> clss;
        public Bundle args;

        TabInfo(String _tag, Class<?> _class, Bundle _args) {
            tag = _tag;
            clss = _class;
            args = _args;
        }
    }

    static class DummyTabFactory implements TabHost.TabContentFactory {
        private final Context mContext;

        public DummyTabFactory(Context context) {
            mContext = context;
        }

        @Override
        public View createTabContent(String tag) {
            View v = new View(mContext);
            v.setMinimumWidth(0);
            v.setMinimumHeight(0);
            return v;
        }
    }

    public interface SaveStateBundle{
        public Bundle onRemoveFragment(Bundle outState);
    }

    public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        mContext = activity;
        mTabHost = tabHost;
        mViewPager = pager;
        mTabHost.setOnTabChangedListener(this);
        mViewPager.setAdapter(this);
        mViewPager.setOnPageChangeListener(this);
    }

    /**
     * Add a Tab which will have Fragment Stack. Add Fragments on this Stack by using
     * addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args)
     * The Stack will hold always the default Fragment u add here.
     * 
     * DON'T ADD Tabs with same tag, it's not beeing checked and results in unexpected
     * beahvior.
     * 
     * @param tabSpec
     * @param clss
     * @param args
     */
    public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args){
        Stack<TabInfo> tabStack = new Stack<TabInfo>();

        tabSpec.setContent(new DummyTabFactory(mContext));
        mTabHost.addTab(tabSpec);
        String tag = tabSpec.getTag();
        TabInfo info = new TabInfo(tag, clss, args);

        mTabTags.add(tag);                  // to know the position of the tab tag 
        tabStack.add(info);
        mTabStackMap.put(tag, tabStack);
        notifyDataSetChanged();
    }

    /**
     * Will add the Fragment to Tab with the Tag _tag. Provide the Class of the Fragment
     * it will be instantiated by this object. Proivde _args for your Fragment.
     * 
     * @param fm
     * @param _tag
     * @param _class
     * @param _args
     */
    public void addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args){
        TabInfo info = new TabInfo(_tag, _class, _args);
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
        if(frag instanceof SaveStateBundle){
            Bundle b = new Bundle();
            ((SaveStateBundle) frag).onRemoveFragment(b);
            tabStack.peek().args = b;
        }
        tabStack.add(info);
        FragmentTransaction ft = fm.beginTransaction();
        ft.remove(frag).commit();
        notifyDataSetChanged();
    }

    /**
     * Will pop the Fragment added to the Tab with the Tag _tag
     * 
     * @param fm
     * @param _tag
     * @return
     */
    public boolean popFragment(FragmentManager fm, String _tag){
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        if(tabStack.size()>1){
            tabStack.pop();
            Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
            FragmentTransaction ft = fm.beginTransaction();
            ft.remove(frag).commit();
            notifyDataSetChanged();
            return true;
        }
        return false;
    }

    public boolean back(FragmentManager fm) {
        int position = mViewPager.getCurrentItem();
        return popFragment(fm, mTabTags.get(position));
    }

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

    @Override
    public int getItemPosition(Object object) {
        ArrayList<Class<?>> positionNoneHack = new ArrayList<Class<?>>();
        for(Stack<TabInfo> tabStack: mTabStackMap.values()){
            positionNoneHack.add(tabStack.peek().clss);
        }   // if the object class lies on top of our stacks, we return default
        if(positionNoneHack.contains(object.getClass())){
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        Stack<TabInfo> tabStack = mTabStackMap.get(mTabTags.get(position));
        TabInfo info = tabStack.peek();
        return Fragment.instantiate(mContext, info.clss.getName(), info.args);
    }

    @Override
    public void onTabChanged(String tabId) {
        int position = mTabHost.getCurrentTab();
        mViewPager.setCurrentItem(position);
    }

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

    @Override
    public void onPageSelected(int position) {
        // Unfortunately when TabHost changes the current tab, it kindly
        // also takes care of putting focus on it when not in touch mode.
        // The jerk.
        // This hack tries to prevent this from pulling focus out of our
        // ViewPager.
        TabWidget widget = mTabHost.getTabWidget();
        int oldFocusability = widget.getDescendantFocusability();
        widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        mTabHost.setCurrentTab(position);
        widget.setDescendantFocusability(oldFocusability);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

}

เพิ่มสิ่งนี้สำหรับฟังก์ชั่นปุ่มย้อนกลับใน MainActivity ของคุณ:

@Override
public void onBackPressed() {
  if (!mTabsAdapter.back(getSupportFragmentManager())) {
    super.onBackPressed();
  }
}

หากคุณต้องการบันทึกสถานะ Fragmentเมื่อมันถูกลบ ให้ Fragment ของคุณใช้อินเทอร์เฟซที่SaveStateBundleส่งคืนในฟังก์ชันบันเดิลกับสถานะการบันทึกของคุณ รับกำหลังจาก instantiation this.getArguments()โดย

คุณสามารถยกตัวอย่างแท็บดังนี้:

mTabsAdapter.addTab(mTabHost.newTabSpec("firstTabTag").setIndicator("First Tab Title"),
                FirstFragmentActivity.FirstFragmentFragment.class, null);

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


ฉันได้รับข้อยกเว้น nullpointer เมื่อกดปุ่มย้อนกลับและไม่สามารถมองเห็นชิ้นส่วนที่ถูกแทนที่ของฉันได้ ดังนั้นฉันอาจทำสิ่งผิดปกติ: การโทรที่ถูกต้องเพื่อเปลี่ยนชิ้นส่วนที่ถูกต้องควรเป็นอย่างไร
Jeroen

อัปเดต: ฉันได้รับการแก้ไขส่วนใหญ่แล้วอย่างไรก็ตามถ้าฉันกดปุ่มย้อนกลับหลังจากเพิ่มแฟรกเมนต์ใหม่และจากนั้นไปที่แท็บอื่นและย้อนกลับฉันจะได้รับข้อยกเว้น nullpointer ดังนั้นฉันมีคำถาม 2 ข้อ: 1: ฉันจะลบประวัติของแท็บได้อย่างไรและให้แน่ใจว่ายังคงมีเพียงแฟรกเมนต์ล่าสุด (บนสุดของสแต็ก te) ล่าสุดเท่านั้น? 2: ฉันจะกำจัดข้อยกเว้น nullpointer ได้อย่างไร
Jeroen

@Jeroen 1: u สามารถรับ stack ที่ถูกต้องได้จาก mTabStackMap จากนั้นลบวัตถุ TabInfo ทุกอันที่อยู่ใต้ส่วนบนสุดของสแต็ก 2: พูดยากมากถามคำถามด้วย stacktrace แล้วฉันก็ลองหามัน คุณแน่ใจหรือว่าไม่ใช่ความล้มเหลวในรหัสของคุณ? สิ่งนี้เกิดขึ้นหรือไม่หากคุณเปลี่ยนไปใช้แท็บ (แล้วย้อนกลับ) ที่อยู่ในพื้นที่ใกล้เคียงโดยตรง (ขวาหรือซ้าย)
seb

ตกลงฉันพบปัญหาใหม่: ถ้าฉันหมุนหน้าจอแล้วสแต็กดูเหมือนจะว่างเปล่าอีกครั้งหรือไม่ ดูเหมือนว่าจะหลวมรายการก่อนหน้า ...
Jeroen

1
@seb ขอบคุณ ฉันใช้รหัสนี้ แต่ตอนนี้ชิ้นส่วนที่ซ้อนกันเป็นไปได้จึงไม่มีปัญหาอีกต่อไป :)
Garcon

2

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

ทางออกคือการสร้างผู้ฟังภายใน viewpager ของคุณที่จะจัดการกับการเปลี่ยนแปลงที่เกิดขึ้นภายนอกดังนั้นก่อนอื่นให้เพิ่มรหัสนี้ที่ด้านล่างของอะแดปเตอร์ของคุณ

public interface nextFragmentListener {
    public void fragment0Changed(String newFragmentIdentification);
}

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

private final class fragmentChangeListener implements nextFragmentListener {


    @Override
    public void fragment0Changed(String fragment) {
        //I will explain the purpose of fragment0 in a moment
        fragment0 = fragment;
        manager.beginTransaction().remove(fragAt0).commit();

        switch (fragment){
            case "searchFragment":
                fragAt0 = SearchFragment.newInstance(listener);
                break;
            case "searchResultFragment":
                fragAt0 = Fragment_Table.newInstance(listener);
                break;
        }

        notifyDataSetChanged();
    }

มีสองสิ่งสำคัญที่จะชี้ให้เห็นที่นี่:

  1. fragAt0 เป็นส่วน "ยืดหยุ่น" มันสามารถใช้กับชนิดของแฟรกเมนต์ที่คุณให้ สิ่งนี้ช่วยให้มันกลายเป็นเพื่อนที่ดีที่สุดของคุณในการเปลี่ยนชิ้นส่วนที่ตำแหน่ง 0 เป็นชิ้นส่วนที่คุณต้องการ
  2. สังเกตุผู้ฟังที่อยู่ในคอนสตรัคเตอร์ 'newInstance (listener) นี่คือวิธีที่คุณจะเรียกแฟรกเมนต์ 0 เปลี่ยนแปลง (String newFragmentIdentification) ` รหัสต่อไปนี้แสดงวิธีการสร้างผู้ฟังภายในแฟรกเมนต์ของคุณ

    คง nextFragmentListener listenerSearch;

        public static Fragment_Journals newInstance(nextFragmentListener listener){
                listenerSearch = listener;
                return new Fragment_Journals();
            }

คุณสามารถเรียกการเปลี่ยนแปลงภายในของคุณ onPostExecute

private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{

    protected Void doInBackground(Void... params){
        .
        .//some more operation
        .
    }
    protected void onPostExecute(Void param){

        listenerSearch.fragment0Changed("searchResultFragment");
    }

}

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

หนึ่งจะอยู่ในวิธีการแทนที่ getItem ของ viewpager

@Override
public Fragment getItem(int index) {

    switch (index) {
    case 0:
        //this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.  

        if(fragAt0 == null){

            switch(fragment0){
            case "searchFragment":
                fragAt0 = FragmentSearch.newInstance(listener);
                break;
            case "searchResultsFragment":
                fragAt0 = FragmentSearchResults.newInstance(listener);
                break;
            }
        }
        return fragAt0;
    case 1:
        // Games fragment activity
        return new CreateFragment();

    }

ตอนนี้ไม่มีชิ้นส่วนสุดท้ายนี้คุณยังจะได้หน้าว่าง ค่อนข้างง่อย แต่มันเป็นส่วนสำคัญของ viewPager คุณต้องแทนที่เมธอด getItemPosition ของ viewpager โดยทั่วไปวิธีการนี้จะส่งคืน POSITION_UNCHANGED ซึ่งจะบอกให้ผู้ดูทำทุกอย่างให้เหมือนกันดังนั้น getItem จะไม่ถูกเรียกให้วางชิ้นส่วนใหม่บนหน้า นี่คือตัวอย่างของสิ่งที่คุณสามารถทำได้

public int getItemPosition(Object object)
{
    //object is the current fragment displayed at position 0.  
    if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
        return POSITION_NONE;
    //this condition is for when you press back
    }else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
        return POSITION_NONE;
    }
        return POSITION_UNCHANGED
}

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

นี่คือนักเก็ตสำหรับจัดการปุ่มย้อนกลับ คุณวางสิ่งนี้ไว้ใน MainActivity ของคุณ

 public void onBackPressed() {
    if(mViewPager.getCurrentItem() == 0) {
        if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){
            ((Fragment_Table) pagerAdapter.getItem(0)).backPressed();
        }else if (pagerAdapter.getItem(0) instanceof FragmentSearch) {
            finish();
        }
    }

คุณจะต้องสร้างวิธีการที่เรียกว่า backPressed () ด้านใน FragmentSearchResults ที่เรียกใช้ Fragment0changed ควบคู่กับรหัสที่ฉันแสดงก่อนหน้านี้จะจัดการกดปุ่มย้อนกลับ ขอให้โชคดีกับรหัสของคุณเพื่อเปลี่ยนผู้ดู มันต้องใช้งานมากและเท่าที่ฉันได้พบไม่มีการดัดแปลงอย่างรวดเร็ว อย่างที่ฉันบอกไปแล้วว่าคุณกำลังสร้างอะแดปเตอร์ viewpager ที่กำหนดเองและปล่อยให้มันจัดการกับการเปลี่ยนแปลงที่จำเป็นทั้งหมดโดยใช้ listeners


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

เมื่อไม่นานมานี้ฉันได้ทำ Android dev มาแล้ว แต่ฉันเดาว่ามีข้อมูลบางส่วนที่ไม่มีแอปของคุณอีกต่อไป เมื่อคุณหมุนกิจกรรมทั้งหมดจะเริ่มต้นใหม่และอาจทำให้เกิดปัญหาได้หากคุณพยายามโหลดสถานะก่อนหน้าโดยไม่มีข้อมูลทั้งหมดที่คุณต้องการ (พูดจากกลุ่มใน onPause, onResume)
user3331142

2

นี่คือวิธีของฉันที่จะบรรลุเป้าหมายนั้น

ก่อนอื่นแท็บเพิ่มRoot_fragmentด้านviewPagerในที่คุณต้องการใช้fragmentเหตุการณ์คลิกปุ่ม ตัวอย่าง;

@Override
public Fragment getItem(int position) {
  if(position==0)
      return RootTabFragment.newInstance();
  else
      return SecondPagerFragment.newInstance();
}

ก่อนอื่นRootTabFragmentควรรวมFragmentLayoutไว้สำหรับการเปลี่ยนชิ้นส่วน

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/root_frame"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
</FrameLayout>

จากนั้นภายในRootTabFragment onCreateViewใช้fragmentChangeสำหรับคุณFirstPagerFragment

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, FirstPagerFragment.newInstance()).commit();

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

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, NextFragment.newInstance()).commit();

หวังว่านี่จะช่วยคุณได้


1

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

มีมัน:

public class DynamicPagerAdapter extends FragmentPagerAdapter {

private ArrayList<Page> mPages = new ArrayList<Page>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

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

public void replacePage(int position, Page page) {
    mPages.set(position, page);
    notifyDataSetChanged();
}

public void setPages(ArrayList<Page> pages) {
    mPages = pages;
    notifyDataSetChanged();
}

@Override
public Fragment getItem(int position) {
    if (mPages.get(position).mPageType == PageType.FIRST) {
        return FirstFragment.newInstance(mPages.get(position));
    } else {
        return SecondFragment.newInstance(mPages.get(position));
    }
}

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

@Override
public long getItemId(int position) {
    // return unique id
    return mPages.get(position).getId();
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment fragment = (Fragment) super.instantiateItem(container, position);
    while (mFragments.size() <= position) {
        mFragments.add(null);
    }
    mFragments.set(position, fragment);
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    mFragments.set(position, null);
}

@Override
public int getItemPosition(Object object) {
    PagerFragment pagerFragment = (PagerFragment) object;
    Page page = pagerFragment.getPage();
    int position = mFragments.indexOf(pagerFragment);
    if (page.equals(mPages.get(position))) {
        return POSITION_UNCHANGED;
    } else {
        return POSITION_NONE;
    }
}
}

หมายเหตุ: ในตัวอย่างนี้FirstFragmentและSecondFragmentขยาย PageFragment getPage()ระดับนามธรรมซึ่งมีวิธีการ


1

ฉันทำสิ่งที่คล้ายกับ wize แต่ในคำตอบของฉันคุณสามารถเปลี่ยนระหว่างสองชิ้นเมื่อใดก็ตามที่คุณต้องการ และด้วยคำตอบที่ฉันมีปัญหาบางอย่างเมื่อเปลี่ยนการวางแนวของหน้าจอสิ่งที่ต้องการ นี่คือ PagerAdapter ที่ดูเหมือนว่า:

    public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;
     private Map<Integer, String> mFragmentTags;
     private boolean isNextFragment=false;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
         mFragmentTags = new HashMap<Integer, String>();
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {


            if (isPager) {
                mFragmentAtPos0 = new FirstPageFragment();
            } else {
                mFragmentAtPos0 = new NextFragment();
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

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


 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object obj = super.instantiateItem(container, position);
        if (obj instanceof Fragment) {
            // record the fragment tag here.
            Fragment f = (Fragment) obj;
            String tag = f.getTag();
            mFragmentTags.put(position, tag);
        }
        return obj;
    }


    public void onChange(boolean isNextFragment) {

        if (mFragmentAtPos0 == null)
            mFragmentAtPos0 = getFragment(0);
        if (mFragmentAtPos0 != null)
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();


        if (!isNextFragment) {
            mFragmentAtFlashcards = new FirstPageFragment();
        } else {
            mFragmentAtFlashcards = new NextFragment();
        }

        notifyDataSetChanged();


    }


    @Override
    public int getItemPosition(Object object)
    {
        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
         if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }


    public Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}

ผู้ฟังที่ฉันนำไปใช้ในกิจกรรมคอนเทนเนอร์ของอะแดปเตอร์เพื่อนำไปไว้ในแฟรกเมนต์เมื่อทำการติดตั้งนี่คือกิจกรรม:

    public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {

//...

  @Override
    public void onChange(boolean isNextFragment) {
        if (pagerAdapter != null)
            pagerAdapter.onChange(isNextFragment);


    }

//...
}

จากนั้นในส่วนการวางฟังเมื่อแนบโทร:

public class FirstPageFragment extends Fragment{


private ChangeFragmentListener changeFragmentListener;


//...
 @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        changeFragmentListener = ((PagerContainerActivity) activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        changeFragmentListener = null;
    }
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}

และในที่สุดผู้ฟัง:

public interface changeFragmentListener {

    void onChange(boolean isNextFragment);

}

1

ฉันทำตามคำตอบโดย@wizeและ@mdelolmoและฉันได้ทางออก ขอบคุณตัน แต่ฉันปรับโซลูชันเหล่านี้เล็กน้อยเพื่อปรับปรุงการใช้หน่วยความจำ

ปัญหาที่ฉันสังเกตเห็น:

พวกเขาบันทึกอินสแตนซ์Fragmentที่จะถูกแทนที่ ในกรณีของฉันมันเป็นชิ้นส่วนที่ถือMapViewและฉันคิดว่ามันมีราคาแพง ดังนั้นฉันจะรักษาFragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)แทนFragmentตัวเอง

นี่คือการดำเนินการของฉัน

  public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {

    private SwitchFragListener mSwitchFragListener;
    private Switch mToggle;
    private int pagerAdapterPosChanged = POSITION_UNCHANGED;
    private static final int TOGGLE_ENABLE_POS = 2;


    public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) {
        super(fm);
        mToggle = toggle;

        mSwitchFragListener = new SwitchFragListener();
        mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mSwitchFragListener.onSwitchToNextFragment();
            }
        });
    }

    @Override
    public Fragment getItem(int i) {
        switch (i)
        {
            case TOGGLE_ENABLE_POS:
                if(mToggle.isChecked())
                {
                    return TabReplaceFragment.getInstance();
                }else
                {
                    return DemoTab2Fragment.getInstance(i);
                }

            default:
                return DemoTabFragment.getInstance(i);
        }
    }

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

    @Override
    public CharSequence getPageTitle(int position) {
        return "Tab " + (position + 1);
    }

    @Override
    public int getItemPosition(Object object) {

        //  This check make sures getItem() is called only for the required Fragment
        if (object instanceof TabReplaceFragment
                ||  object instanceof DemoTab2Fragment)
            return pagerAdapterPosChanged;

        return POSITION_UNCHANGED;
    }

    /**
     * Switch fragments Interface implementation
     */
    private final class SwitchFragListener implements
            SwitchFragInterface {

        SwitchFragListener() {}

        public void onSwitchToNextFragment() {

            pagerAdapterPosChanged = POSITION_NONE;
            notifyDataSetChanged();
        }
    }

    /**
     * Interface to switch frags
     */
    private interface SwitchFragInterface{
        void onSwitchToNextFragment();
    }
}

ลิงค์สาธิตที่นี่ .. https://youtu.be/l_62uhKkLyM

สำหรับวัตถุประสงค์ในการสาธิตให้ใช้ 2 แฟรกเมนต์TabReplaceFragmentและDemoTab2Fragmentที่ตำแหน่งที่สอง ในกรณีอื่น ๆ ทั้งหมดฉันใช้DemoTabFragmentอินสแตนซ์

คำอธิบาย:

ฉันผ่านจากกิจกรรมไปSwitch DemoCollectionPagerAdapterขึ้นอยู่กับสถานะของสวิตช์นี้เราจะแสดงส่วนที่ถูกต้อง เมื่อการตรวจสอบสวิทช์ที่มีการเปลี่ยนแปลงผมเรียกSwitchFragListener's onSwitchToNextFragmentวิธีการที่ฉันเปลี่ยนค่าของตัวแปรpagerAdapterPosChanged POSITION_NONEตรวจสอบข้อมูลเพิ่มเติมเกี่ยวกับPOSITION_NONE สิ่งนี้จะทำให้ getItem เป็นโมฆะและฉันมี logics เพื่อทำให้ชิ้นส่วนถูกต้องตรงนั้น ขออภัยหากคำอธิบายค่อนข้างยุ่ง

ขอขอบคุณอีกครั้งสำหรับ @wize และ @mdelolmo สำหรับแนวคิดดั้งเดิม

หวังว่านี่จะเป็นประโยชน์ :)

แจ้งให้เราทราบหากการดำเนินการนี้มีข้อบกพร่องใด ๆ นั่นจะเป็นประโยชน์อย่างมากสำหรับโครงการของฉัน


0

หลังจากการวิจัยฉันพบวิธีแก้ปัญหาด้วยรหัสสั้น ๆ ก่อนอื่นให้สร้างอินสแตนซ์สาธารณะในแฟรกเมนต์และเพียงลบแฟรกเมนต์ของคุณบน onSaveInstanceState หากแฟรกเมนต์ไม่ได้สร้างการเปลี่ยนแปลงการวางแนวใหม่

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.