Cập nhật dữ liệu trong ListFragment như một phần của ViewPager


142

Tôi đang sử dụng ViewPager tương thích v4 trong Android. FragmentActivity của tôi có một loạt dữ liệu sẽ được hiển thị theo các cách khác nhau trên các trang khác nhau trong ViewPager của tôi. Cho đến nay tôi chỉ có 3 phiên bản của ListFragment giống nhau, nhưng trong tương lai tôi sẽ có 3 phiên bản ListFragments khác nhau. ViewPager nằm trên màn hình điện thoại dọc, các danh sách không nằm cạnh nhau.

Bây giờ, một nút trên ListFragment bắt đầu một hoạt động toàn trang riêng biệt (thông qua FragmentActivity), trả về và FragmentActivity sửa đổi dữ liệu, lưu nó, sau đó cố gắng cập nhật tất cả các chế độ xem trong ViewPager của nó. Nó ở đây, nơi tôi đang bị mắc kẹt.

public class ProgressMainActivity extends FragmentActivity
{
    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    ...
        mAdapter = new MyAdapter(getSupportFragmentManager());

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

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        ...
        updateFragments();
        ...
    }
    public void updateFragments()
    {
        //Attempt 1:
        //mAdapter.notifyDataSetChanged();
        //mPager.setAdapter(mAdapter);

        //Attempt 2:
        //HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentById(mAdapter.fragId[0]);
        //fragment.updateDisplay();
    }

    public static class MyAdapter extends FragmentPagerAdapter implements
         TitleProvider
    {
      int[] fragId = {0,0,0,0,0};
      public MyAdapter(FragmentManager fm)
      {
         super(fm);
      }
      @Override
      public String getTitle(int position){
         return titles[position];
      }
      @Override
      public int getCount(){
         return titles.length;
      }

      @Override
      public Fragment getItem(int position)
      {

         Fragment frag = HomeListFragment.newInstance(position);
         //Attempt 2:
         //fragId[position] = frag.getId();
         return frag;
      }

      @Override
      public int getItemPosition(Object object) {
         return POSITION_NONE; //To make notifyDataSetChanged() do something
     }
   }

    public class HomeListFragment extends ListFragment
    {
    ...
        public static HomeListFragment newInstance(int num)
        {
            HomeListFragment f = new HomeListFragment();
            ...
            return f;
        }
   ...

Bây giờ bạn có thể thấy, nỗ lực đầu tiên của tôi là thông báo choDataSetChanged trên toàn bộ FragmentPagerAd CHƯƠNG, và điều này đôi khi cho thấy cập nhật dữ liệu, nhưng những lần khác tôi đã nhận được IllegalStateException: Không thể thực hiện hành động này sau onSaveInstanceState.

Lần thử thứ hai của tôi liên quan đến việc cố gắng gọi một hàm cập nhật trong ListFragment của tôi, nhưng getId trong getItem đã trả về 0. Theo các tài liệu tôi đã thử

có được một tham chiếu đến Fragment từ FragmentManager, sử dụng findFragmentById () hoặc findFragmentByTag ()

nhưng tôi không biết thẻ hoặc id của Fragment của tôi! Tôi có một android: id = "@ + id / viewpager" cho ViewPager và android: id = "@ android: id / list" cho ListView của tôi trong bố cục ListFragment, nhưng tôi không nghĩ chúng hữu ích.

Vì vậy, làm cách nào tôi có thể: a) cập nhật toàn bộ ViewPager một cách an toàn trong một lần (lý tưởng là đưa người dùng trở lại trang mà anh ta đã truy cập trước đó) - người dùng thấy chế độ xem thay đổi. Hoặc tốt hơn là, b) gọi một chức năng trong mỗi ListFragment bị ảnh hưởng để cập nhật ListView theo cách thủ công.

Bất kỳ trợ giúp sẽ được chấp nhận một cách biết ơn!

Câu trả lời:


58

Cố gắng ghi lại thẻ mỗi khi một mảnh vỡ được khởi tạo.

public class MPagerAdapter extends FragmentPagerAdapter {
    private Map<Integer, String> mFragmentTags;
    private FragmentManager mFragmentManager;

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

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

    @Override
    public Fragment getItem(int position) {
        return Fragment.instantiate(mContext, AFragment.class.getName(), null);
    }

    @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 Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}

Cái này nhìn ổn nhưng nó không hoạt động trong lớp của tôi. Nó có làm việc với bạn không? Tôi vẫn nhận được nullcho mảnh.
Sandfish

1
Nó làm việc cho tôi, tôi đã sử dụng nó trong một số dự án. Bạn có thể thử đọc mã nguồn của FragmentPagerAd CHƯƠNG và in một số nhật ký để gỡ lỗi.
faylon

Cảm ơn hoạt động như một sự quyến rũ ngay cả sau khi xoay màn hình. Ngay cả khi tôi đã có thể thay đổi nội dung của FragmentA bằng MyActivity từ FragmentB, sử dụng giải pháp này.
Mehdi Fanai

5
Nó hoạt động FragmentPagerAdapernhưng không thành công FragmentStatePagerAdaper( Fragment.getTag()luôn quay trở lại null.
Engine Bai

1
Tôi đã thử giải pháp này, tạo ra một phương pháp để cập nhật dữ liệu của đoạn. Nhưng bây giờ tôi không thể nhìn thấy quan điểm của mảnh vỡ. Tôi có thể thấy dữ liệu sẽ bị phân mảnh nhưng lượt xem không hiển thị. Có ai giúp đỡ không?
Arun Badole

256

Câu trả lời của Barkside hoạt động với FragmentPagerAdapternhưng không hoạt động FragmentStatePagerAdapter, bởi vì nó không đặt thẻ trên các đoạn mà nó chuyển đến FragmentManager.

Với FragmentStatePagerAdapterdường như chúng ta có thể nhận được bằng cách sử dụng instantiateItem(ViewGroup container, int position)cuộc gọi của nó . Nó trả về tham chiếu cho đoạn ở vị trí position. Nếu FragmentStatePagerAdapterđã có tham chiếu đến đoạn được đề cập, instantiateItemchỉ cần trả về tham chiếu đến đoạn đó và không gọi getItem()lại để khởi tạo lại đoạn đó.

Vì vậy, giả sử, tôi hiện đang xem đoạn # 50 và muốn truy cập đoạn # 49. Vì họ đã ở gần, nên rất có thể # 49 sẽ được khởi tạo. Vì thế,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)

5
Tôi sẽ đơn giản hóa điều đó bằng cách chỉ tạo "a" một PagerAd CHƯƠNG (PagerAd CHƯƠNG a = pager.getAd CHƯƠNG ();). Hoặc thậm chí MyFragment f49 = (MyFragment) pager.getAd CHƯƠNG (). InstantiateItem (pager, 49); instantiateItem () là từ PagerAdapter, vì vậy bạn không cần phải định kiểu nó để gọi instantiateItem ()
Dandre Allison

12
... và chỉ trong trường hợp có ai thắc mắc, ViewPager sẽ theo dõi chỉ số của đoạn được xem (ViewPager.getCienItem ()), là 49 trong ví dụ trên ... nhưng tôi phải nói rằng tôi 'TUYỆT VỜI rằng API ViewPager sẽ không trả lại tham chiếu đến Fragment thực tế.
mblackwell8

6
Với FragmentStatePagerAdapter, sử dụng mã ở trên với a.instantiateItem(viewPager, viewPager.getCurrentItem());(để thoát khỏi 49) như đề xuất của @ mblackwell8 hoạt động hoàn hảo.
Cedric Simon

1
Đợi đã, vậy tôi có phải chuyển ViewPager cho mọi phân đoạn mà nó chứa chỉ để có thể thực hiện mọi thứ cho các khung nhìn trong phân đoạn không? Hiện tại mọi thứ tôi làm trong onCreate trên trang hiện tại xảy ra ở trang tiếp theo. Điều này nghe có vẻ cực kỳ mờ ám.
G_V

điều quan trọng cần đề cập là bất kỳ cuộc gọi nào instantiateItemsẽ được bao quanh bởi startUpdate/ finishUpdatecuộc gọi. chi tiết có trong câu trả lời của tôi cho một câu hỏi tương tự: stackoverflow.com/questions/14035090/ trên
morgwai

154

OK, tôi nghĩ rằng tôi đã tìm ra cách để thực hiện yêu cầu b) trong câu hỏi của riêng tôi vì vậy tôi sẽ chia sẻ vì lợi ích của người khác. Thẻ của các đoạn bên trong ViewPager có dạng "android:switcher:VIEWPAGER_ID:INDEX", trong đó VIEWPAGER_IDR.id.viewpagerbố cục XML và INDEX là vị trí trong trình xem. Vì vậy, nếu vị trí được biết (ví dụ 0), tôi có thể thực hiện trong updateFragments():

      HomeListFragment fragment = 
          (HomeListFragment) getSupportFragmentManager().findFragmentByTag(
                       "android:switcher:"+R.id.viewpager+":0");
      if(fragment != null)  // could be null if not instantiated yet
      {
         if(fragment.getView() != null) 
         {
            // no need to call if fragment's onDestroyView() 
            //has since been called.
            fragment.updateDisplay(); // do what updates are required
         }
      }

Tôi không biết đây có phải là một cách làm hợp lệ hay không, nhưng nó sẽ làm cho đến khi một cái gì đó tốt hơn được đề xuất.


4
Tôi đăng nhập chỉ để +1 câu trả lời và câu hỏi của bạn.
SuitUp

5
vì điều này không có trong tài liệu chính thức, tôi sẽ không sử dụng nó. Điều gì nếu họ thay đổi thẻ trong một bản phát hành trong tương lai?
Thierry-Dimitri Roy

4
FragmentPagerAdOG đi kèm với một phương thức tĩnh makeFragmentNameđược sử dụng để tạo Thẻ mà bạn có thể sử dụng thay thế cho cách tiếp cận ít bị hack hơn: HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
dgmltn

8
@dgmltn, makeFragmentNameprivate staticphương thức, vì vậy bạn không thể truy cập được.
StenaviN

1
Mẹ ngọt ngào của Chúa Giêsu làm việc này! Tôi sẽ chỉ giữ ngón tay của tôi vượt qua và sử dụng nó.
Bogdan Alexandru

35

Nếu bạn hỏi tôi, giải pháp thứ hai ở trang bên dưới, theo dõi tất cả các trang phân đoạn "hoạt động", sẽ tốt hơn: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

Câu trả lời từ barkside là quá hack đối với tôi.

bạn theo dõi tất cả các trang phân đoạn "hoạt động". Trong trường hợp này, bạn theo dõi các trang phân đoạn trong FragmentStatePagerAd CHƯƠNG, được ViewPager sử dụng.

private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();

public Fragment getItem(int index) {
    Fragment myFragment = MyFragment.newInstance();
    mPageReferences.put(index, myFragment);
    return myFragment;
}

Để tránh việc giữ tham chiếu đến các trang phân đoạn "không hoạt động", chúng ta cần triển khai phương thức hủy bỏ của FragmentStatePagerAd Module (...):

public void destroyItem(View container, int position, Object object) {
    super.destroyItem(container, position, object);
    mPageReferences.remove(position);
}

... và khi bạn cần truy cập trang hiện đang hiển thị, sau đó bạn gọi:

int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);

... Trong đó phương thức getFragment (int) của MyAd CHƯƠNG trông như thế này:

public MyFragment getFragment(int key) {
    return mPageReferences.get(key);
}

"


@Frank giải pháp thứ hai trong liên kết rất đơn giản ...! Cảm ơn .. :)
TheFlash

3
Tốt hơn nếu bạn sử dụng SpzzyArray thay vì bản đồ
GaRRaPeTa


đã cập nhật câu trả lời của tôi để nó sử dụng SpzzyArray, nếu bạn nhắm mục tiêu <11, thay vào đó hãy sử dụng SpzzyArrayCompat vì lớp đã cập nhật trong phiên bản 11.
Frank

2
Điều này sẽ phá vỡ các sự kiện vòng đời. Xem stackoverflow.com/a/24662049/236743
Dori

13

Được rồi, sau khi thử nghiệm phương pháp của @barkside ở trên, tôi không thể làm cho nó hoạt động với ứng dụng của mình. Sau đó, tôi nhớ rằng ứng dụng IOSched2012 cũng sử dụng một ứng dụng viewpagervà đó là nơi tôi tìm thấy giải pháp của mình. Nó không sử dụng bất kỳ ID hoặc Thẻ phân đoạn nào vì chúng không được lưu trữ bởiviewpager dễ dàng truy cập.

Đây là những phần quan trọng từ ứng dụng IOSched HomeActivity. Đặc biệt chú ý đến bình luận , vì trong đó có chìa khóa:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // Since the pager fragments don't have known tags or IDs, the only way to persist the
    // reference is to use putFragment/getFragment. Remember, we're not persisting the exact
    // Fragment instance. This mechanism simply gives us a way to persist access to the
    // 'current' fragment instance for the given fragment (which changes across orientation
    // changes).
    //
    // The outcome of all this is that the "Refresh" menu button refreshes the stream across
    // orientation changes.
    if (mSocialStreamFragment != null) {
        getSupportFragmentManager().putFragment(outState, "stream_fragment",
                mSocialStreamFragment);
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    if (mSocialStreamFragment == null) {
        mSocialStreamFragment = (SocialStreamFragment) getSupportFragmentManager()
                .getFragment(savedInstanceState, "stream_fragment");
    }
}

Và lưu trữ các trường hợp của bạn Fragmentstrong FragmentPagerAdapternhư vậy:

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

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return (mMyScheduleFragment = new MyScheduleFragment());

            case 1:
                return (mExploreFragment = new ExploreFragment());

            case 2:
                return (mSocialStreamFragment = new SocialStreamFragment());
        }
        return null;
    }

Ngoài ra, hãy nhớ bảo vệ các Fragmentcuộc gọi của bạn như vậy:

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }

Ryan, đây có phải là để nói rằng bạn đang giám sát các chức năng mà ViewPager sử dụng để 'giữ' các trường hợp phân đoạn của bạn và đặt chúng thành một biến công khai có thể truy cập được không? Tại sao trên ví dụ khôi phục bạn có if (mSocialStreamFragment == null) {?
Thomas Clowes

1
đây là một câu trả lời hay và +1 cho giới thiệu ứng dụng io12 - không chắc chắn liệu điều này có hoạt động trong các thay đổi trong vòng đời hay không do getItem()không được gọi sau khi cha mẹ được tạo lại do thay đổi vòng đời. Xem stackoverflow.com/a/24662049/236743 . Trong ví dụ này, một phần được bảo vệ chống lại một mảnh nhưng không phải là hai mảnh kia
Dori

2

Bạn có thể sao chép FragmentPagerAdaptervà sửa đổi một số mã nguồn, thêm getTag()phương thức

ví dụ

public abstract class AppFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;

private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;

public AppFragmentPagerAdapter(FragmentManager fm) {
    mFragmentManager = fm;
}


public abstract Fragment getItem(int position);

@Override
public void startUpdate(ViewGroup container) {
}

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

    final long itemId = getItemId(position);


    String name = getTag(position);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);

        mCurTransaction.add(container.getId(), fragment,
                getTag(position));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment) object).getView());
    mCurTransaction.detach((Fragment) object);
}

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

@Override
public void finishUpdate(ViewGroup container) {
    if (mCurTransaction != null) {
        mCurTransaction.commitAllowingStateLoss();
        mCurTransaction = null;
        mFragmentManager.executePendingTransactions();
    }
}

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

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

@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}


public long getItemId(int position) {
    return position;
}

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

protected abstract String getTag(int position);
}

sau đó mở rộng nó, ghi đè lên phương thức trừu tượng này, không cần phải sợ thay đổi Nhóm Android

FragmentPageAdapter mã nguồn trong tương lai

 class TimeLinePagerAdapter extends AppFragmentPagerAdapter {


    List<Fragment> list = new ArrayList<Fragment>();


    public TimeLinePagerAdapter(FragmentManager fm) {
        super(fm);
        list.add(new FriendsTimeLineFragment());
        list.add(new MentionsTimeLineFragment());
        list.add(new CommentsTimeLineFragment());
    }


    public Fragment getItem(int position) {
        return list.get(position);
    }

    @Override
    protected String getTag(int position) {
        List<String> tagList = new ArrayList<String>();
        tagList.add(FriendsTimeLineFragment.class.getName());
        tagList.add(MentionsTimeLineFragment.class.getName());
        tagList.add(CommentsTimeLineFragment.class.getName());
        return tagList.get(position);
    }


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


}

1

Cũng hoạt động mà không có vấn đề:

ở đâu đó trong bố cục của đoạn:

<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="@+id/fragment_reference">
     <View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>

trong đoạn onCreateView ():

...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;

và phương thức trả về Fragment từ ViewPager, nếu tồn tại:

public Fragment getFragment(int pageIndex) {        
        View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
        if (w == null) return null;
        View r = (View) w.getParent();
        return (Fragment) r.getTag();
}

1

Ngoài ra, bạn có thể ghi đè setPrimaryItemphương thức từ FragmentPagerAdapternhư vậy:

public void setPrimaryItem(ViewGroup container, int position, Object object) { 
    if (mCurrentFragment != object) {
        mCurrentFragment = (Fragment) object; //Keep reference to object
        ((MyInterface)mCurrentFragment).viewDidAppear();//Or call a method on the fragment
    }

    super.setPrimaryItem(container, position, object);
}

public Fragment getCurrentFragment(){
    return mCurrentFragment;
}

0

Tôi muốn đưa ra cách tiếp cận của mình trong trường hợp nó có thể giúp đỡ bất kỳ ai khác:

Đây là bộ chuyển đổi máy nhắn tin của tôi:

 public class CustomPagerAdapter extends FragmentStatePagerAdapter{
    private Fragment[] fragments;

    public CustomPagerAdapter(FragmentManager fm) {
        super(fm);
        fragments = new Fragment[]{
                new FragmentA(),
                new FragmentB()
        };
    }

    @Override
    public Fragment getItem(int arg0) {
        return fragments[arg0];
    }

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

}

Trong hoạt động của tôi, tôi có:

public class MainActivity {
        private ViewPager view_pager;
        private CustomPagerAdapter adapter;


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

        adapter = new CustomPagerAdapter(getSupportFragmentManager());
        view_pager = (ViewPager) findViewById(R.id.pager);
        view_pager.setAdapter(adapter);
        view_pager.setOnPageChangeListener(this);
          ...
         }

}

Sau đó, để có được đoạn hiện tại, những gì tôi làm là:

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);

1
điều này sẽ bị hỏng sau các phương pháp vòng đời hoạt động / phân đoạn
Dori

0

Đây là giải pháp của tôi vì tôi không cần phải theo dõi các tab của mình và dù sao cũng cần phải làm mới chúng.

    Bundle b = new Bundle();
    b.putInt(Constants.SharedPreferenceKeys.NUM_QUERY_DAYS,numQueryDays);
    for(android.support.v4.app.Fragment f:getSupportFragmentManager().getFragments()){
        if(f instanceof HomeTermFragment){
            ((HomeTermFragment) f).restartLoader(b);
        }
    }
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.