Thay thế Fragment bên trong ViewPager


268

Tôi đang cố gắng sử dụng Fragment với ViewPagerviệc sử dụng FragmentPagerAdapter. Những gì tôi đang tìm kiếm để đạt được là thay thế một đoạn, được định vị trên trang đầu tiên ViewPager, bằng một trang khác.

Máy nhắn tin gồm hai trang. Cái đầu tiên là FirstPagerFragmentcái thứ hai SecondPagerFragment. Nhấp vào nút của trang đầu tiên. Tôi muốn thay thế FirstPagerFragmentbằng NextFragment.

Có mã của tôi dưới đây.

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

        }
    }
}

... và đây là tập tin 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>

Bây giờ vấn đề ... tôi nên sử dụng ID nào trong

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

?

Nếu tôi sử dụng R.id.first_fragment_root_id, công cụ thay thế hoạt động, nhưng Trình xem phân cấp hiển thị một hành vi lạ, như bên dưới.

Lúc đầu tình hình là

sau khi thay thế tình hình là

Như bạn có thể thấy có gì đó không đúng, tôi hy vọng sẽ tìm thấy trạng thái tương tự như trong bức ảnh đầu tiên sau khi tôi thay thế đoạn.


Bạn đã tạo sơ đồ bố trí bằng cách nào đó hoặc bạn đã tự làm nó?
Jeroen Vannevel

@Noodles: Mình cần làm tương tự. Câu hỏi của bạn đã tạo ra một chủ đề dài với nhiều câu trả lời khác nhau. Điều gì làm việc tốt nhất cuối cùng? Cảm ơn nhiều!
narb

1
@JeroenVannevel bạn có thể đã tìm ra rồi, nhưng sơ đồ bố trí đó là developer.android.com/tools/performance/hierarchy-viewer/
Lỗi

có một số vấn đề cần sự giúp đỡ của bạn stackoverflow.com/questions/40149039/ từ
albert

Câu trả lời:


162

Có một giải pháp mà không cần thay đổi mã nguồn ViewPagerFragmentStatePagerAdapter, và nó làm việc với các FragmentPagerAdapterlớp cơ sở được sử dụng bởi các tác giả.

Tôi muốn bắt đầu bằng cách trả lời câu hỏi của tác giả về việc anh ấy nên sử dụng ID nào; nó là ID của container, tức là ID của máy nhắn tin xem. Tuy nhiên, như bạn có thể nhận thấy chính mình, sử dụng ID đó trong mã của bạn sẽ không có gì xảy ra. Tôi sẽ giải thích tại sao:

Trước hết, để tạo ViewPagerlại các trang, bạn cần gọi notifyDataSetChanged()nó nằm trong lớp cơ sở của bộ điều hợp.

Thứ hai, ViewPagersử dụng getItemPosition()phương pháp trừu tượng để kiểm tra trang nào sẽ bị hủy và trang nào nên được giữ. Việc triển khai mặc định của chức năng này luôn trả về POSITION_UNCHANGED, điều này khiến ViewPagercho tất cả các trang hiện tại và do đó không đính kèm trang mới của bạn. Do đó, để làm cho công việc thay thế đoạn, getItemPosition()cần phải được ghi đè trong bộ điều hợp của bạn và phải quay lại POSITION_NONEkhi được gọi với một đối số cũ, bị ẩn, đoạn làm đối số.

Điều này cũng có nghĩa là bộ điều hợp của bạn luôn cần phải biết đoạn nào sẽ được hiển thị ở vị trí 0 FirstPageFragmenthoặc NextFragment. Một cách để làm điều này là cung cấp một trình nghe khi tạo FirstPageFragment, nó sẽ được gọi khi đến lúc chuyển các đoạn. Tôi nghĩ rằng đây là một điều tốt, mặc dù, để cho bộ điều hợp phân đoạn của bạn xử lý tất cả các chuyển mạch phân đoạn và các cuộc gọi đến ViewPagerFragmentManager.

Thứ ba, FragmentPagerAdapterlưu trữ các đoạn được sử dụng theo tên xuất phát từ vị trí, vì vậy nếu có một đoạn ở vị trí 0, nó sẽ không được thay thế mặc dù lớp này là mới. Có hai giải pháp, nhưng đơn giản nhất là sử dụng remove()chức năng của FragmentTransaction, nó cũng sẽ xóa thẻ của nó.

Đó là rất nhiều văn bản, đây là mã nên hoạt động trong trường hợp của bạn:

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

Hy vọng điều này sẽ giúp bất cứ ai!


24
làm việc cho tôi quá, nhưng làm thế nào để thực hiện nút quay lại để hiển thị đoạn đầu tiên?
dùng1159819

6
Giải thích tuyệt vời nhưng bạn có thể đăng mã nguồn đầy đủ về điều này, cụ thể là các lớp FirstPageFragment và SecondPageFragment.
georgiecasey

6
Làm thế nào để bạn thêm FirstPageFragmentListener vào Gói trong newInstance ()?
miguel

4
Bạn có thể thêm mã nơi bạn xử lý với FirstPageFragment.newInstance()paramener listener không?
MiguelHincapieC

6
Điều này chỉ hoạt động với tôi nếu tôi gọi commitNow () thay vì commit () khi xóa đoạn. Không chắc tại sao trường hợp sử dụng của tôi lại khác, nhưng hy vọng điều này sẽ giúp ai đó tiết kiệm thời gian.
Ian Lovejoy

37

Kể từ ngày 13 tháng 11 năm 2012, việc lặp lại các đoạn trong ViewPager dường như đã trở nên dễ dàng hơn rất nhiều. Google đã phát hành Android 4.2 với sự hỗ trợ cho các đoạn được lồng vào nhau và nó cũng được hỗ trợ trong Thư viện hỗ trợ Android mới v11, do đó, điều này sẽ hoạt động trở lại 1.6

Nó rất giống với cách thay thế một đoạn thông thường ngoại trừ bạn sử dụng getChildFragmentManager. Nó dường như hoạt động ngoại trừ backstack mảnh lồng nhau không bật khi người dùng nhấp vào nút quay lại. Theo giải pháp trong câu hỏi được liên kết đó, bạn cần gọi thủ công popBackStackImmediate () trên trình quản lý con của đoạn. Vì vậy, bạn cần ghi đè onBackPression () của hoạt động ViewPager nơi bạn sẽ nhận được đoạn hiện tại của ViewPager và gọi getChildFragmentManager (). PopBackStackImmediate () trên đó.

Việc sử dụng Fragment hiện đang được hiển thị cũng hơi khó khăn, tôi đã sử dụng giải pháp "android: switcher: VIEWPAGER_ID: INDEX" bẩn thỉu này nhưng bạn cũng có thể tự mình theo dõi tất cả các đoạn của ViewPager như được giải thích trong giải pháp thứ hai trên trang này .

Vì vậy, đây là mã của tôi cho ViewPager với 4 ListView với chế độ xem chi tiết được hiển thị trong ViewPager khi người dùng nhấp vào một hàng và với nút quay lại hoạt động. Tôi đã cố gắng chỉ bao gồm các mã có liên quan vì lý do ngắn gọn, vì vậy hãy để lại nhận xét nếu bạn muốn ứng dụng đầy đủ được tải lên 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;
    }

 }
}

List ProductsFragment.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
Bạn có thể tải mã của mình lên github không? cảm ơn.
Gautham

4
@georgiecasey: Tôi đã tải xuống API 17, Android 4.2 cho phép tôi bắt đầu sử dụng getChildFragmentManager (). Nhưng tôi phải thiếu một cái gì đó từ giải pháp của bạn, vì bây giờ tôi đang chồng chéo các mảnh cũ + mới trên màn hình. Lưu ý: Tôi đang cố gắng sử dụng giải pháp của bạn trong buổi hòa nhạc với mã TabAd CHƯƠNG của Google từ developer.android.com/reference/android/support/v4/view/ ,. Cảm ơn cho bất kỳ đề xuất và / hoặc mã tham chiếu.
gcl1

27
R.id.products_list_linear đề cập đến điều gì? Vui lòng liên kết đến mã của bạn như đã được cung cấp.
howettl

1
@howettl các tham chiếu ID đến thành viên phân đoạn bên ngoài. Chìa khóa để giải quyết vấn đề này là sử dụng các đoạn lồng nhau (theo cách đó bạn không phải thao tác với bộ điều hợp viewpager).
Indrek Kõue

2
Điều này đã bao giờ làm cho nó để github?
Baruch

32

Dựa trên câu trả lời của @wize, điều mà tôi thấy hữu ích và thanh lịch, tôi có thể đạt được một phần những gì tôi muốn, vì tôi muốn khả năng quay trở lại Mảnh vỡ đầu tiên sau khi được thay thế. Tôi đã đạt được nó một chút sửa đổi một chút mã của mình.

Đây sẽ là FragmentPagerAd CHƯƠNG:

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

Để thực hiện thay thế, chỉ cần xác định một trường tĩnh, thuộc loại CalendarPageFragmentListenervà được khởi tạo thông qua các newInstancephương thức của các đoạn tương ứng và gọi FirstFragment.pageListener.onSwitchToNextFragment()hoặc NextFragment.pageListener.onSwitchToNextFragment()trả lời.


Làm thế nào để bạn thực hiện thay thế? không thể khởi tạo Trình nghe mà không ghi đè phương thức onSwitchToNext.
Leandros

1
Bạn không bị mất trạng thái được lưu trong mFragmentAtPos0 nếu bạn xoay thiết bị?
seb

@seb, tôi thực sự đã cải thiện giải pháp này để lưu mFragmentAtPos0tham chiếu khi lưu trạng thái hoạt động. Đây không phải là giải pháp tao nhã nhất, nhưng hiệu quả.
mdelolmo

Bạn có thể thấy câu trả lời của tôi . Nó thay thế các mảnh vỡ và trở lại đầu tiên.
Lyd

@mdelolmo Bạn có thể xem câu hỏi này về ViewPager không? Cảm ơn bạn stackoverflow.com/questions/27937250/ khỏe
Hoa Vũ

21

Tôi đã thực hiện một giải pháp cho:

  • Thay thế đoạn động bên trong tab
  • Bảo trì lịch sử trên mỗi tab
  • Làm việc với những thay đổi định hướng

Các thủ thuật để đạt được điều này là như sau:

  • Sử dụng phương thức notifyDataSetChanged () để áp dụng thay thế đoạn
  • Chỉ sử dụng trình quản lý phân đoạn cho giai đoạn lùi và không thay thế cho mùi
  • Duy trì lịch sử bằng cách sử dụng mẫu memento (stack)

Mã bộ điều hợp như sau:

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

Lần đầu tiên bạn thêm tất cả các tab, chúng ta cần gọi phương thức createdHistory (), để tạo lịch sử ban đầu

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

Mỗi khi bạn muốn thay thế một đoạn thành một tab cụ thể mà bạn gọi: thay thế (vị trí int cuối cùng, phân đoạn Class cuối cùng, Bundle args cuối cùng)

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

Khi nhấn lại, bạn cần gọi phương thức 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();
}

Giải pháp hoạt động với thanh hành động sherlock và với cử chỉ vuốt.


Thật tuyệt vời! bạn có thể đặt nó trên github?
Nicramus

Việc triển khai này là sạch nhất trong tất cả những gì được cung cấp ở đây và giải quyết mọi vấn đề. Nó nên là câu trả lời được chấp nhận.
dazedviper

Có ai có vấn đề với việc thực hiện này? Tôi có 2 người trong số họ. 1. lần đầu tiên không đăng ký - Tôi phải nhấp lại hai lần để đăng ký. 2. Sau khi tôi nhấp lại (hai lần) trên tab a và tôi chuyển đến tab b, khi tôi quay lại tab a, nó chỉ hiển thị nội dung của tab b
yaronbic

7

tl; dr: Sử dụng một đoạn máy chủ chịu trách nhiệm thay thế nội dung được lưu trữ của nó và theo dõi lịch sử điều hướng trở lại (như trong trình duyệt).

Vì trường hợp sử dụng của bạn bao gồm một số lượng tab cố định, giải pháp của tôi hoạt động tốt: Ý tưởng là lấp đầy ViewPager bằng các phiên bản của lớp tùy chỉnh HostFragment, có thể thay thế nội dung được lưu trữ của nó và giữ lịch sử điều hướng quay lại của chính nó. Để thay thế đoạn được lưu trữ, bạn thực hiện cuộc gọi đến phương thức 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();
    }
}

Tất cả phương pháp đó làm là thay thế bố cục khung bằng id R.id.hosted_fragmentbằng đoạn được cung cấp cho phương thức.

Kiểm tra hướng dẫn của tôi về chủ đề này để biết thêm chi tiết và một ví dụ hoạt động hoàn chỉnh trên GitHub!


Mặc dù điều này có vẻ vụng về hơn, nhưng tôi nghĩ thực tế là cách tốt nhất (về cấu trúc và dễ bảo trì) để làm điều này.
Sira Lam

Tôi kết thúc với một NPE. : / Không thể tìm thấy Chế độ xem cần thay thế
Fariz Fakkel

6

Một số giải pháp được trình bày đã giúp tôi giải quyết được một phần vấn đề nhưng vẫn còn một điều quan trọng trong các giải pháp đã tạo ra ngoại lệ bất ngờ và nội dung trang đen thay vì nội dung phân đoạn trong một số trường hợp.

Vấn đề là lớp FragmentPagerAd CHƯƠNG đang sử dụng ID vật phẩm để lưu trữ các đoạn được lưu trong bộ nhớ cache vào FragmentManager . Vì lý do này, bạn cũng cần ghi đè phương thức getItemId (int location) để nó trả về ví dụ vị trí cho các trang cấp cao nhất và vị trí 100+ cho các trang chi tiết. Nếu không, đoạn cấp cao nhất được tạo trước đó sẽ được trả về từ bộ đệm thay vì đoạn ở mức chi tiết.

Hơn nữa, tôi đang chia sẻ ở đây một ví dụ hoàn chỉnh về cách triển khai hoạt động giống như tab với các trang Fragment bằng ViewPager và các nút tab bằng Radiogroup cho phép thay thế các trang cấp cao nhất bằng các trang chi tiết và cũng hỗ trợ nút quay lại. Việc triển khai này chỉ hỗ trợ một cấp độ xếp chồng ngược (danh sách vật phẩm - chi tiết vật phẩm) nhưng việc thực hiện xếp chồng ngược nhiều cấp độ lại đơn giản. Ví dụ này hoạt động khá tốt trong các trường hợp thông thường ngoại trừ việc nó đang ném NullPulumException trong trường hợp khi bạn chuyển sang trang thứ hai, thay đổi đoạn của trang đầu tiên (trong khi không hiển thị) và quay lại trang đầu tiên. Tôi sẽ đăng một giải pháp cho vấn đề này một khi tôi sẽ tìm ra nó:

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

Hmm, có một vấn đề khác với mã này. Khi tôi muốn bắt đầu một hoạt động từ đoạn bị xóa và thêm aggain, tôi gặp lỗi: "Trạng thái lưu không thành công: NewFirstFragment {405478a0} đã xóa chỉ mục: -1" (theo dõi ngăn xếp đầy đủ: nopaste.info/547a23dfea.html ). Nhìn vào các nguồn Android, tôi phát hiện ra rằng nó có liên quan đến backstack nhưng tôi không biết cách khắc phục cho đến nay. Có ai có gợi ý gì không?
Blackhex

2
Thật tuyệt, sử dụng <code> mFragmentManager.beginTransaction (). Detach (old_fragment) .attach (đoạn) .commit (); </ code> thay vì <pre> mFragmentManager.beginTransaction (). SetTransition (). old_fragment) .add (mContainer.getId (), đoạn) .commit (); </ pre> trong ví dụ trên dường như khắc phục cả hai vấn đề :-).
Blackhex

1
Nếu bạn sử dụng phương pháp này, hãy đảm bảo bạn không cho phép Android tạo lại Hoạt động của mình khi xoay thiết bị. Nếu không, PagerAd CHƯƠNG sẽ được tạo lại và bạn sẽ mất mBackFragments. Điều này cũng sẽ gây nhầm lẫn nghiêm trọng cho FragmentManager. Sử dụng BackStack để tránh vấn đề này, với chi phí triển khai PagerAd CHƯƠNG phức tạp hơn (nghĩa là bạn không thể thừa hưởng từ FragmentPagerAd CHƯƠNG.)
Norman

6

Tôi đã tạo ViewPager với 3 phần tử và 2 phần tử phụ cho chỉ mục 2 và 3 và đây là những gì tôi muốn làm ..

nhập mô tả hình ảnh ở đây

Tôi đã thực hiện điều này với sự trợ giúp từ các câu hỏi và câu trả lời trước đây từ StackOverFlow và đây là liên kết.

ViewPagerChildFragments


Dự án của bạn thất bại, nếu bạn xoay thiết bị.
Dory

6

Để thay thế một đoạn bên trong một ViewPagerbạn có thể chuyển mã nguồn ViewPager, PagerAdapterFragmentStatePagerAdaptercác lớp học vào dự án của bạn và thêm đoạn mã sau.

vào ViewPager:

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

vào FragmentStatePagerAd CHƯƠNG:

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()đảm bảo rằng sau cuộc gọi tiếp theo của getItem()getFragmentPosition()sẽ trả về vị trí trả về newFragment của đoạn trong bộ điều hợp của bạn.

Bây giờ, để thay thế các cuộc gọi mảnh

mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);

Nếu bạn quan tâm đến một dự án ví dụ, hãy hỏi tôi các nguồn.


Các phương thức getFragmentP vị trí () và handleGetItemInbalidated () nên như thế nào? Tôi không thể cập nhật getItem () để trả về NewFragment .. Xin hãy giúp đỡ ..

Chào! Vui lòng tìm dự án thử nghiệm trên các tệp
AndroidTeam tại Mail.Ru

1
Dự án thử nghiệm của bạn thất bại, nếu bạn xoay thiết bị.
seb

1
@ AndroidTeamAtMail.Ru liên kết dường như đã hết hạn. Bạn có thể chia sẻ mã
Sunny

Bạn có thể thấy câu trả lời của tôi . Nó thay thế các mảnh vỡ và trở lại đầu tiên.
Lyd

4

Hoạt động tuyệt vời với giải pháp của AndroidTeam, tuy nhiên tôi thấy rằng tôi cần khả năng quay trở lại giống như FrgmentTransaction.addToBackStack(null) Nhưng chỉ thêm điều này sẽ chỉ khiến Fragment bị thay thế mà không thông báo cho ViewPager. Kết hợp giải pháp được cung cấp với cải tiến nhỏ này sẽ cho phép bạn quay lại trạng thái trước đó bằng cách chỉ ghi đè onBackPressed()phương thức của hoạt động . Hạn chế lớn nhất là nó sẽ chỉ quay lại một lần mà có thể dẫn đến nhiều lần nhấp trở lại

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

Hy vọng điều này sẽ giúp được ai đó.

Ngoài ra getFragmentPosition(), nó đi ngược lại khá nhiều getItem(). Bạn biết mảnh nào đi đâu, chỉ cần đảm bảo bạn trả lại đúng vị trí của nó. Đây là một ví dụ:

    @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

Trong onCreateViewphương pháp của bạn , containerthực sự là một ViewPagerví dụ.

Vì vậy, chỉ cần gọi

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

sẽ thay đổi đoạn hiện tại trong của bạn ViewPager.


1
Từ nhiều giờ tìm kiếm, đây là một dòng mã mà tôi cần để làm cho tất cả các tab của tôi hoạt động chính xác. vpViewPager.setCurrentItem(1);. Tôi đã đi qua ví dụ của từng người, không có gì xảy ra mỗi lần cho đến khi cuối cùng tôi cũng đến với bạn. Cảm ơn bạn.
Brandon

1
tâm trí bị thổi bay ... điều này thật tuyệt vời !! Tôi đã không nhận ra rằng biến container là chính viewpager. Câu trả lời này phải được khám phá trước khi tất cả những câu trả lời dài khác. Thêm vào đó, không có các biến người nghe chuyển đến hàm tạo phân đoạn, đơn giản hóa tất cả việc thực hiện khi Android tự động tạo lại các đoạn bằng cách sử dụng hàm tạo no-arg mặc định khi cấu hình thay đổi.
Kevin Lee

3
không phải điều này chỉ thay đổi thành một tab hiện có sao? Làm thế nào điều này có liên quan đến việc thay thế các mảnh vỡ?
xử

2
Đồng ý với @behelit, điều này không trả lời câu hỏi, nó sẽ chuyển ViewPagersang trang khác, nó sẽ không thay thế bất kỳ đoạn nào.
Yoann Hercouet

2

Đây là giải pháp tương đối đơn giản của tôi cho vấn đề này. Các chìa khóa cho giải pháp này là sử dụng FragmentStatePagerAdapterthay vì FragmentPagerAdapternhư trước đây sẽ loại bỏ các mảnh không sử dụng cho bạn trong khi sau này vẫn giữ lại các phiên bản của chúng. Thứ hai là việc sử dụng POSITION_NONEtrong getItem (). Tôi đã sử dụng một Danh sách đơn giản để theo dõi các mảnh vỡ của mình. Yêu cầu của tôi là thay thế toàn bộ danh sách các mảnh cùng một lúc bằng một danh sách mới, nhưng dưới đây có thể dễ dàng sửa đổi để thay thế các mảnh riêng lẻ:

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

Tôi cũng đã thực hiện một giải pháp, đó là làm việc với Stacks . Đó là một cách tiếp cận mang tính mô đun hơn nên bạn không cần phải chỉ định từng phân đoạn và chi tiết trong đó FragmentPagerAdapter. Nó được xây dựng dựa trên ví dụ từ ActionbarSherlock, xuất phát nếu tôi ngay từ Ứng dụng 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) {
    }

}

Thêm phần này cho chức năng nút quay lại trong MainActivity của bạn:

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

Nếu bạn muốn lưu Trạng thái Mảnh vỡ khi nó bị xóa. Hãy để Fragment của bạn thực hiện giao diện SaveStateBundletrả về trong hàm một gói với trạng thái lưu của bạn. Nhận gói sau khi khởi tạo bằng cách this.getArguments().

Bạn có thể khởi tạo một tab như thế này:

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

hoạt động tương tự nếu bạn muốn thêm một mảnh trên đầu ngăn xếp Tab. Quan trọng : Tôi nghĩ, nó sẽ không hoạt động nếu bạn muốn có 2 thể hiện của cùng một lớp trên đầu hai Tab. Tôi đã thực hiện giải pháp này nhanh chóng cùng nhau, vì vậy tôi chỉ có thể chia sẻ nó mà không cung cấp bất kỳ kinh nghiệm nào với nó.


Tôi nhận được một ngoại lệ nullpulum khi nhấn nút quay lại và đoạn bị thay thế của tôi cũng không hiển thị. Vì vậy, có lẽ tôi đang làm điều gì đó sai: nên gọi đúng để thay đổi các đoạn đúng cách là gì?
Jeroen

Cập nhật: Tôi đã sửa hầu hết mọi thứ, tuy nhiên, nếu tôi nhấn nút quay lại sau khi thêm một đoạn mới và sau đó chuyển sang một tab khác và quay lại, tôi nhận được một ngoại lệ nullpulum. Vì vậy, tôi có 2 câu hỏi: 1: làm cách nào tôi có thể xóa lịch sử của tab và đảm bảo chỉ có đoạn mới nhất (đỉnh của ngăn xếp te) vẫn còn hiện diện? 2: làm thế nào tôi có thể thoát khỏi ngoại lệ nullpulum?
Jeroen

@J 1 u chắc chắn đó không phải là một thất bại trong mã của bạn? Điều này cũng xảy ra nếu bạn chuyển sang một tab (và sau đó quay lại) trong vùng lân cận trực tiếp (phải hoặc trái)?
seb

Ok, tôi gặp phải một vấn đề mới: nếu tôi xoay màn hình của mình, thì ngăn xếp dường như lại trống rỗng? Nó dường như mất theo dõi các mục trước đó ...
Jeroen

1
@seb cảm ơn. Tôi đã sử dụng mã này nhưng các đoạn lồng nhau bây giờ đã có thể nên không còn là vấn đề nữa. :)
Garcon

2

Việc thay thế các đoạn trong một khung nhìn khá liên quan nhưng rất có thể và có thể trông cực kỳ lắt léo. Trước tiên, bạn cần để cho trình xem chính tự xử lý việc loại bỏ và thêm các đoạn. Điều đang xảy ra là khi bạn thay thế đoạn bên trong SearchFragment, trình xem của bạn vẫn giữ các lượt xem đoạn của nó. Vì vậy, bạn kết thúc với một trang trống vì SearchFragment bị xóa khi bạn cố gắng thay thế nó.

Giải pháp là tạo một trình lắng nghe bên trong trình xem của bạn, nó sẽ xử lý các thay đổi được thực hiện bên ngoài nó, trước tiên hãy thêm mã này vào dưới cùng của bộ điều hợp.

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

Sau đó, bạn cần tạo một lớp riêng trong trình xem của bạn để trở thành người nghe khi bạn muốn thay đổi đoạn của mình. Ví dụ, bạn có thể thêm một cái gì đó như thế này. Lưu ý rằng nó thực hiện giao diện vừa được tạo. Vì vậy, bất cứ khi nào bạn gọi phương thức này, nó sẽ chạy mã bên trong lớp bên dưới.

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

Có hai điều chính cần chỉ ra ở đây:

  1. FragAt0 là một đoạn "linh hoạt". Nó có thể đảm nhận bất cứ loại mảnh nào bạn cung cấp cho nó. Điều này cho phép nó trở thành người bạn tốt nhất của bạn trong việc thay đổi đoạn ở vị trí 0 thành đoạn bạn mong muốn.
  2. Lưu ý các trình nghe được đặt trong hàm tạo 'newInstance (listener). Đây là cách bạn sẽ gọifragment0Changed (Chuỗi newFragmentIdentification) `. Đoạn mã sau cho thấy cách bạn tạo trình nghe bên trong đoạn của bạn.

    static nextFragmentListener listenerSearch;

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

Bạn có thể gọi sự thay đổi bên trong của bạn 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");
    }

}

Điều này sẽ kích hoạt mã bên trong trình xem của bạn để chuyển đoạn của bạn ở vị trí zero FragAt0 để trở thành một tìm kiếm mớiResultFragment. Có hai phần nhỏ hơn bạn sẽ cần thêm vào trình xem trước khi nó hoạt động.

Người ta sẽ ở trong phương thức ghi đè getItem của trình xem.

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

    }

Bây giờ không có phần cuối cùng này, bạn vẫn sẽ có được một trang trống. Kiểu khập khiễng, nhưng nó là một phần thiết yếu của viewPager. Bạn phải ghi đè phương thức getItemP vị trí của trình xem. Thông thường, phương thức này sẽ trả về POSITION_UNCHANGED, cho biết người xem giữ mọi thứ giống nhau và vì vậy getItem sẽ không bao giờ được gọi để đặt đoạn mới trên trang. Đây là một ví dụ về những điều bạn có thể làm

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
}

Giống như tôi đã nói, mã được tham gia rất nhiều, nhưng về cơ bản bạn phải tạo một bộ điều hợp tùy chỉnh cho tình huống của bạn. Những điều tôi đã đề cập sẽ làm cho nó có thể thay đổi các mảnh. Có thể sẽ mất nhiều thời gian để ngâm mọi thứ vào nên tôi sẽ kiên nhẫn, nhưng tất cả sẽ có ý nghĩa. Nó hoàn toàn đáng để dành thời gian bởi vì nó có thể tạo ra một ứng dụng thực sự hấp dẫn.

Đây là nugget để xử lý nút quay lại. Bạn đặt cái này vào trong MainActivity của bạn

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

Bạn sẽ cần tạo một phương thức gọi là backPression () bên trong FragmentSearchResults gọi Fragment0changed. Điều này song song với mã tôi đã hiển thị trước đó sẽ xử lý nhấn nút quay lại. Chúc may mắn với mã của bạn để thay đổi người xem. Phải mất rất nhiều công sức, và theo như tôi đã tìm thấy, không có bất kỳ sự thích ứng nhanh nào. Giống như tôi đã nói, về cơ bản, bạn đang tạo một bộ điều hợp chế độ xem tùy chỉnh và để nó xử lý tất cả các thay đổi cần thiết bằng cách sử dụng trình nghe


Cảm ơn bạn cho giải pháp này. Nhưng bạn đã có bất kỳ vấn đề sau khi xoay màn hình. Ứng dụng của tôi gặp sự cố khi tôi xoay màn hình và cố tải lại một đoạn khác trong cùng một tab.
AnteGemini

Đã được một thời gian kể từ khi tôi thực hiện Android dev, nhưng tôi đoán là có một phần dữ liệu không còn ở đó ứng dụng của bạn đang đếm. Khi bạn xoay, toàn bộ hoạt động sẽ khởi động lại và có thể gây ra sự cố nếu bạn cố tải lại trạng thái trước đó mà không có tất cả dữ liệu bạn cần (nói từ gói trong onPause, onResume)
user3331142

2

Đây là cách của tôi để đạt được điều đó.

Trước hết, thêm tab Root_fragmentbên viewPagertrong mà bạn muốn thực hiện fragmentsự kiện nhấn nút . Thí dụ;

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

Trước hết, RootTabFragmentnên được bao gồm FragmentLayoutđể thay đổi mảnh.

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

Sau đó, bên trong RootTabFragment onCreateView, thực hiện fragmentChangecho bạnFirstPagerFragment

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

Sau đó, thực hiện onClicksự kiện cho nút của bạn bên trong FirstPagerFragmentvà thực hiện thay đổi đoạn như thế một lần nữa.

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

Hy vọng điều này sẽ giúp bạn


1

Tôi tìm thấy giải pháp đơn giản, hoạt động tốt ngay cả khi bạn muốn thêm các đoạn mới ở giữa hoặc thay thế đoạn hiện tại. Trong giải pháp của tôi, bạn nên ghi đè lên getItemId()id sẽ trả về id duy nhất cho mỗi phân đoạn. Không phải vị trí như mặc định.

Có nó:

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

Lưu ý: Trong ví dụ này FirstFragmentSecondFragmentmở rộng lớp trừu tượng PageFragment, có phương thức getPage().


1

Tôi làm một cái gì đó tương tự như wize nhưng trong câu trả lời của tôi yo có thể thay đổi giữa hai mảnh bất cứ khi nào bạn muốn. Và với câu trả lời wize, tôi có một số vấn đề khi thay đổi hướng của màn hình một thứ như thế. Đây là PagerAd CHƯƠNG trông giống như:

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

Trình nghe tôi đã triển khai trong hoạt động của bộ chứa bộ điều hợp để đặt nó vào đoạn khi gắn nó, đây là hoạt động:

    public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {

//...

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


    }

//...
}

Sau đó, trong đoạn đặt người nghe khi đính kèm một cuộc gọi nó:

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

Và cuối cùng là người nghe:

public interface changeFragmentListener {

    void onChange(boolean isNextFragment);

}

1

Tôi đã làm theo câu trả lời của @wize@mdelolmo và tôi đã có giải pháp. Cảm ơn tấn. Nhưng, tôi đã điều chỉnh các giải pháp này một chút để cải thiện mức tiêu thụ bộ nhớ.

Các vấn đề tôi quan sát thấy:

Họ lưu ví dụ Fragmentđược thay thế. Trong trường hợp của tôi, nó là một mảnh vỡ MapViewvà tôi nghĩ rằng nó rất tốn kém. Vì vậy, tôi đang duy trì FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)thay vì Fragmentchính nó.

Đây là thực hiện của tôi.

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

Liên kết demo tại đây .. https://youtu.be/l_62uhKkLyM

Đối với mục đích demo, sử dụng 2 mảnh TabReplaceFragmentDemoTab2Fragmentở vị trí hai. Trong tất cả các trường hợp khác, tôi đang sử dụng các DemoTabFragmenttrường hợp.

Giải trình:

Tôi đang chuyển Switchtừ Hoạt động sang DemoCollectionPagerAdapter. Dựa trên trạng thái của công tắc này, chúng tôi sẽ hiển thị đoạn chính xác. Khi kiểm tra công tắc được thay đổi, tôi gọi SwitchFragListener's onSwitchToNextFragmentphương pháp, nơi tôi đang thay đổi giá trị của pagerAdapterPosChangedbiến POSITION_NONE. Kiểm tra thêm về POSITION_NONE . Điều này sẽ làm mất hiệu lực getItem và tôi có logic để khởi tạo đoạn đúng ở đó. Xin lỗi, nếu lời giải thích là một chút lộn xộn.

Một lần nữa xin cảm ơn @wize và @mdelolmo cho ý tưởng ban đầu.

Hy vọng điều này là hữu ích. :)

Hãy cho tôi biết nếu thực hiện này có bất kỳ sai sót. Điều đó sẽ rất hữu ích cho dự án của tôi.


0

Sau khi nghiên cứu tôi tìm thấy giải pháp với mã ngắn. trước hết hãy tạo một cá thể công khai trên đoạn và chỉ xóa đoạn của bạn trên onSaveInstanceState nếu đoạn không được tạo lại khi thay đổi hướng.

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.