24

Vì vậy, tôi đang gặp sự cố với việc hủy (xóa) một trang khỏi ViewPager sau khi hướng màn hình thay đổi. Tôi sẽ cố gắng mô tả vấn đề trong các dòng sau.Hủy mục khỏi bộ điều hợp của ViewPager sau khi hướng màn hình thay đổi

Tôi đang sử dụng FragmentStatePagerAdapter cho bộ điều hợp của ViewPager và giao diện nhỏ mô tả cách máy nhắn tin chế độ xem vô tận hoạt động. Ý tưởng đằng sau nó là bạn có thể cuộn sang phải cho đến khi bạn đạt đến cuối của ViewPager. Nếu bạn có thể tải thêm kết quả từ một cuộc gọi API, một trang tiến trình được hiển thị cho đến khi kết quả đến.

Mọi thứ đều tốt đẹp cho đến đây, bây giờ vấn đề xảy ra. Nếu trong quá trình tải này, tôi xoay màn hình (điều này sẽ không ảnh hưởng đến cuộc gọi API mà về cơ bản là một AsyncTask), khi trở về cuộc gọi, các tai nạn ứng dụng đem lại cho tôi ngoại lệ này:

E/AndroidRuntime(13471): java.lang.IllegalStateException: Fragment ProgressFragment{42b08548} is not currently in the FragmentManager 
E/AndroidRuntime(13471): at android.support.v4.app.FragmentManagerImpl.saveFragmentInstanceState(FragmentManager.java:573) 
E/AndroidRuntime(13471): at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:136) 
E/AndroidRuntime(13471): at mypackage.OutterFragment$PagedSingleDataAdapter.destroyItem(OutterFragment.java:609) 

Sau khi đào một chút trong mã của thư viện có vẻ như trường dữ liệu mIndex của đoạn nhỏ hơn 0 trong trường hợp này và điều này làm tăng ngoại lệ đó.

Đây là mã của adapter pager:

static class PagedSingleDataAdapter extends FragmentStatePagerAdapter implements 
     IEndlessPagerAdapter { 

    private WeakReference<OutterFragment> fragment; 
    private List<DataItem> data; 
    private SparseArray<WeakReference<Fragment>> currentFragments = new SparseArray<WeakReference<Fragment>>(); 

    private ProgressFragment progressElement; 

    private boolean isLoadingData; 

    public PagedSingleDataAdapter(SherlockFragment fragment, List<DataItem> data) { 
     super(fragment.getChildFragmentManager()); 
     this.fragment = new WeakReference<OutterFragment>(
       (OutterFragment) fragment); 
     this.data = data; 
    } 

    @Override 
    public Object instantiateItem(ViewGroup container, int position) { 
     Object item = super.instantiateItem(container, position); 
     currentFragments.append(position, new WeakReference<Fragment>(
       (Fragment) item)); 
     return item; 
    } 

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

    @Override 
    public Fragment getItem(int position) { 
     if (isPositionOfProgressElement(position)) { 
      return getProgessElement(); 
     } 

     WeakReference<Fragment> fragmentRef = currentFragments.get(position); 
     if (fragmentRef == null) { 
      return PageFragment.newInstance(args); // here I'm putting some info 
               // in the args, just deleted 
               // them now, not important 
     } 

     return fragmentRef.get(); 
    } 

    @Override 
    public int getCount() { 
     int size = data.size(); 
     return isLoadingData ? ++size : size; 
    } 

    @Override 
    public int getItemPosition(Object item) { 
     if (item.equals(progressElement) && !isLoadingData) { 
      return PagerAdapter.POSITION_NONE; 
     } 
     return PagerAdapter.POSITION_UNCHANGED; 
    } 

    public void setData(List<DataItem> data) { 
     this.data = data; 
     notifyDataSetChanged(); 
    } 

    @Override 
    public boolean isPositionOfProgressElement(int position) { 
     return isLoadingData && position == data.size(); 
    } 

    @Override 
    public void setLoadingData(boolean isLoadingData) { 
     this.isLoadingData = isLoadingData; 
    } 

    @Override 
    public boolean isLoadingData() { 
     return isLoadingData; 
    } 

    @Override 
    public Fragment getProgessElement() { 
     if (progressElement == null) { 
      progressElement = new ProgressFragment(); 
     } 
     return progressElement; 
    } 

    public static class ProgressFragment extends SherlockFragment { 

     public ProgressFragment() { 
     } 

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

      TextView progressView = new TextView(container.getContext()); 
      progressView.setGravity(Gravity.CENTER_HORIZONTAL 
        | Gravity.CENTER_VERTICAL); 
      progressView.setText(R.string.loading_more_data); 
      LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, 
        LayoutParams.FILL_PARENT); 
      progressView.setLayoutParams(params); 

      return progressView; 
     } 
    } 
} 

Các onPageSelected() callback dưới đây, về cơ bản bắt đầu cuộc gọi api nếu cần thiết:

@Override 
    public void onPageSelected(int currentPosition) { 
     updatePagerIndicator(currentPosition); 
     activity.invalidateOptionsMenu(); 
     if (requestNextApiPage(currentPosition)) { 
      pagerAdapter.setLoadingData(true); 
      requestNextPageController.requestNextPageOfData(this); 
     } 

Bây giờ, nó cũng đáng phải nói gì cuộc gọi API sau khi phân phối kết quả. Dưới đây là callback:

@Override 
public boolean onTaskSuccess(Context arg0, List<DataItem> result) { 
    data = result; 
    pagerAdapter.setLoadingData(false); 
    pagerAdapter.setData(result); 
    activity.invalidateOptionsMenu(); 

    return true; 
} 

Ok, bây giờ bởi vì phương pháp setData() gọi các notifiyDataSetChanged(), điều này sẽ gọi getItemPosition() cho các mảnh vỡ hiện đang trong mảng currentFragments. Tất nhiên là đối với phần tử tiến trình, nó trả về POSITION_NONE vì tôi muốn xóa trang này, do đó về cơ bản, hãy gọi lại số gọi destroyItem() từ số PagedSingleDataAdapter. Nếu tôi không xoay màn hình, mọi thứ đều hoạt động OK, nhưng như tôi đã nói nếu tôi xoay nó khi phần tử tiến trình được hiển thị và cuộc gọi API chưa kết thúc, cuộc gọi lại destroyItem() sẽ được gọi sau khi khởi động lại hoạt động .

Có lẽ tôi cũng nên nói rằng tôi đang lưu trữ ViewPager trong một Phân đoạn khác chứ không phải trong một hoạt động, do đó, OutterFragment lưu trữ số ViewPager. Tôi đang instantiating pagerAdapter trong onActivityCreated() callback của OutterFragment và sử dụng setRetainInstance(true) để khi màn hình xoay pagerAdapter vẫn giữ nguyên (không có gì cần phải thay đổi, phải không?), Mã ở đây:

if (pagerAdapter == null) { 
    pagerAdapter = new PagedSingleDataAdapter(this, data); 
} 
pager.setAdapter(pagerAdapter); 

if (savedInstanceState == null) { 
    pager.setOnPageChangeListener(this); 
    pager.setCurrentItem(currentPosition); 
} 

Tổng kết tại , các VẤN ĐỀ là:

Nếu tôi cố gắng để loại bỏ các yếu tố tiến bộ từ ViewPager sau khi nó được khởi tạo và hoạt động đã bị phá hủy và tái tạo (hướng màn hình thay đổi) tôi nhận được ngoại lệ trên (pagerAdapter vẫn giữ nguyên, để mọi thứ bên trong nó lso vẫn giữ nguyên, tài liệu tham khảo, vv… kể từ khi OutterFragment lưu trữ pagerAdapter không bị hủy chỉ được tách ra khỏi hoạt động và sau đó được đính kèm lại). Có lẽ nó xảy ra một cái gì đó với người quản lý mảnh, nhưng tôi thực sự không biết những gì.

Những gì tôi đã cố gắng:

  1. Đang cố gắng để loại bỏ đoạn tiến bộ của tôi sử dụng một kỹ thuật tức là trên onTaskSuccess() callback Tôi đã cố gắng để loại bỏ các mảnh vỡ từ người quản lý đoạn, không làm việc .

  2. Tôi cũng đã cố gắng ẩn phần tử tiến trình thay vì xóa phần tử tiến trình hoàn toàn khỏi trình quản lý phân đoạn. Điều này đã làm việc 50%, vì tầm nhìn không còn ở đó nữa, nhưng tôi đã có một trang trống, vì vậy đó không thực sự là những gì tôi đang tìm kiếm.

  3. Tôi cũng đã cố gắng (lại) đính kèm progressFragment vào trình quản lý đoạn sau khi thay đổi hướng màn hình, điều này cũng không hoạt động.

  4. Tôi cũng đã cố xóa và sau đó thêm lại đoạn tiến trình vào trình quản lý phân đoạn sau khi hoạt động được tạo lại, không hoạt động.

  5. Cố gắng gọi destroyItem() theo cách thủ công từ cuộc gọi lại onTaskSuccess() (thực sự rất xấu) nhưng không hoạt động.

Xin lỗi các bạn đã đăng bài dài nhưng tôi đã cố gắng giải thích vấn đề tốt nhất để các bạn có thể hiểu được.

Bất kỳ giải pháp, đề xuất nào được đánh giá cao.

Cảm ơn!

CẬP NHẬT: FOUND SOLUTION OK, vì vậy quá trình này mất một thời gian. Vấn đề là gọi hàm destroyItem() được gọi hai lần trên đoạn tiến trình, một lần khi hướng màn hình thay đổi và sau đó một lần nữa sau khi cuộc gọi api kết thúc. Đó là lý do tại sao ngoại lệ. Giải pháp mà tôi tìm thấy là: Tiếp tục theo dõi nếu cuộc gọi api kết thúc hay không và phá hủy đoạn tiến trình chỉ trong trường hợp này, mã bên dưới.

@Override 
     public void destroyItem(ViewGroup container, int position, Object object) { 
      if (object.equals(progressElement) && apiCallFinished == true) { 
       apiCallFinished = false; 
       currentFragments.put(position, currentFragments.get(position + 1)); 
       super.destroyItem(container, position, object); 
      } else if (!(object.equals(progressElement))) { 
       currentFragments.put(position, null); 
       super.destroyItem(container, position, object); 
      } 
     } 

và sau đó apiCallFinished này được thiết lập là false trong constructor của adapter và là true trong onTaskSuccess() gọi lại. Và nó thực sự hoạt động!

+0

Có thể là một câu hỏi ngu ngốc, nhưng bạn có thực sự thêm lại các đoạn của mình vào viewPager không, sau khi xoay màn hình của bạn? Hoạt động của bạn bị phá hủy khi thay đổi hướng. –

+0

ah, tốt nhất, vâng tôi đang thêm chúng nhưng quên thêm mã cho nó :). Cảm ơn bạn đã chỉ ra điều này, tôi sẽ chỉnh sửa một chút bài đăng của mình. Chúc mừng! –

+0

Thử thực sự thêm lại các tab của bạn bằng .addTab() trên onCreate. Vấn đề của bạn chỉ là progressFragment, hay là gì? –

Trả lời

4

CẬP NHẬT: GIẢI PHÁP ÂM THANH OK, vì vậy việc này mất một lúc. Vấn đề là gọi hàm destroyItem() được gọi hai lần trên đoạn tiến trình, một lần khi hướng màn hình thay đổi và sau đó một lần nữa sau khi cuộc gọi api kết thúc. Đó là lý do tại sao ngoại lệ. Các giải pháp mà tôi tìm thấy là sau đây: Tiếp tục theo dõi nếu cuộc gọi api hoàn thành hay không và phá hủy các mảnh tiến bộ chỉ trong trường hợp này, mã dưới đây.

@Override 
     public void destroyItem(ViewGroup container, int position, Object object) { 
      if (object.equals(progressElement) && apiCallFinished == true) { 
       apiCallFinished = false; 
       currentFragments.put(position, currentFragments.get(position + 1)); 
       super.destroyItem(container, position, object); 
      } else if (!(object.equals(progressElement))) { 
       currentFragments.put(position, null); 
       super.destroyItem(container, position, object); 
      } 
     } 

và sau đó apiCallFinished này được đặt thành false trong hàm tạo của bộ điều hợp và đúng trong lời gọi onTaskSuccess(). Và nó thực sự hoạt động!

Các vấn đề liên quan