hỗ trợ FragmentPagerAdapter giữ tham chiếu đến các phân đoạn cũ


109

THÔNG TIN MỚI NHẤT:

tôi đã thu hẹp vấn đề của mình xuống thành vấn đề với việc lưu giữ các bản sao của các đoạn cũ và máy xem khung của tôi không đồng bộ với FragmentManager của tôi. Xem sự cố này ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Tôi vẫn không có manh mối làm thế nào để giải quyết điều này. Bất kỳ đề xuất...

Tôi đã cố gắng gỡ lỗi này trong một thời gian dài và bất kỳ trợ giúp nào sẽ được đánh giá rất cao. Tôi đang sử dụng FragmentPagerAdapter chấp nhận danh sách các đoạn như vậy:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

Việc thực hiện là tiêu chuẩn. Tôi đang sử dụng ActionBarSherlock và thư viện khả năng tính toán v4 cho Fragment.

Vấn đề của tôi là sau khi rời khỏi ứng dụng và mở một số ứng dụng khác và quay lại, các phân đoạn sẽ mất tham chiếu trở lại FragmentActivity (tức là. getActivity() == null). Tôi không thể hiểu tại sao điều này lại xảy ra. Tôi đã cố gắng thiết lập thủ công setRetainInstance(true);nhưng điều này không giúp được gì. Tôi nhận ra rằng điều này xảy ra khi FragmentActivity của tôi bị phá hủy, tuy nhiên điều này vẫn xảy ra nếu tôi mở ứng dụng trước khi nhận được thông báo nhật ký. Có bất kỳ ý tưởng?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

Bộ chuyển đổi:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

Một trong những mảnh vỡ của tôi đã bị tước bỏ nhưng tôi đã hoàn thiện mọi thứ đã bị tước bỏ và vẫn không hoạt động ...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

@Override
public void onActivityCreated(Bundle savedState) {
    super.onActivityCreated(savedState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


@Override
public void onResume(){
    super.onResume();
}

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

Phương thức cập nhật được gọi khi người dùng tương tác với một phân đoạn khác và getActivity trả về null. Đây là phương thức mà đoạn kia đang gọi ...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

Tôi tin rằng khi ứng dụng bị phá hủy, sau đó được tạo lại thay vì chỉ khởi động ứng dụng đến phân đoạn mặc định, ứng dụng sẽ khởi động và sau đó người xem điều hướng đến trang cuối cùng đã biết. Điều này có vẻ lạ, không phải ứng dụng chỉ tải đến phân đoạn mặc định?


27
Phân mảnh của Android thật tệ!
Hamidreza Sadegh

13
Chúa ơi, tôi thích tin nhắn nhật ký của bạn: D
oli.G

Câu trả lời:


120

Bạn đang gặp sự cố vì bạn đang khởi tạo và giữ các tham chiếu đến các phân đoạn của mình bên ngoài PagerAdapter.getItemvà đang cố gắng sử dụng các tham chiếu đó một cách độc lập với ViewPager. Như Seraph nói, bạn có đảm bảo rằng một phân đoạn đã được khởi tạo / thêm vào ViewPager tại một thời điểm cụ thể - đây nên được coi là một chi tiết triển khai. ViewPager không tải chậm các trang của nó; theo mặc định, nó chỉ tải trang hiện tại và trang ở bên trái và bên phải.

Nếu bạn đặt ứng dụng của mình ở chế độ nền, các đoạn đã được thêm vào trình quản lý phân đoạn sẽ được lưu tự động. Ngay cả khi ứng dụng của bạn bị hủy, thông tin này sẽ được khôi phục khi bạn khởi chạy lại ứng dụng của mình.

Bây giờ hãy coi rằng bạn đã xem một vài trang, Các phân đoạn A, B và C. Bạn biết rằng những trang này đã được thêm vào trình quản lý phân mảnh. Bởi vì bạn đang sử dụng FragmentPagerAdaptervà không sử dụng FragmentStatePagerAdapter, những đoạn này sẽ vẫn được thêm vào (nhưng có khả năng bị tách ra) khi bạn cuộn sang các trang khác.

Hãy xem xét rằng bạn sau đó chạy nền cho ứng dụng của mình và sau đó nó sẽ bị giết. Khi bạn quay lại, Android sẽ nhớ rằng bạn đã từng có các Phân đoạn A, B và C trong trình quản lý phân mảnh và vì vậy nó sẽ tạo lại chúng cho bạn và sau đó thêm chúng vào. Tuy nhiên, những cái được thêm vào trình quản lý phân mảnh bây giờ KHÔNG phải là những cái bạn có trong danh sách phân mảnh trong Hoạt động của bạn.

FragmentPagerAdapter sẽ không gọi getPositionnếu đã có một phân đoạn được thêm vào cho vị trí trang cụ thể đó. Trên thực tế, vì phân đoạn do Android tạo lại sẽ không bao giờ bị xóa nên bạn không có hy vọng thay thế nó bằng một cuộc gọi tới getPosition. Việc nắm bắt nó cũng khá khó khăn để có được một tham chiếu đến nó vì nó đã được thêm một thẻ mà bạn không biết. Đây là do thiết kế; bạn không được khuyến khích làm rối với các phân đoạn mà máy xem trang đang quản lý. Bạn phải thực hiện tất cả các hành động của mình trong một phân đoạn, giao tiếp với hoạt động và yêu cầu chuyển sang một trang cụ thể, nếu cần.

Bây giờ, trở lại vấn đề của bạn với hoạt động bị thiếu. Việc gọi pagerAdapter.getItem(1)).update(id, name)sau khi tất cả những điều này đã xảy ra trả về cho bạn phân đoạn trong danh sách của bạn, phân đoạn này vẫn chưa được thêm vào trình quản lý phân mảnh và do đó, nó sẽ không có tham chiếu Hoạt động. Tôi đề xuất rằng phương pháp cập nhật của bạn nên sửa đổi một số cấu trúc dữ liệu được chia sẻ (có thể được quản lý bởi hoạt động), và sau đó khi bạn chuyển đến một trang cụ thể, nó có thể tự vẽ dựa trên dữ liệu cập nhật này.


1
Tôi thích giải pháp của bạn vì nó rất thanh lịch và có khả năng sẽ cấu trúc lại mã của tôi tuy nhiên như bạn đã nói, tôi muốn giữ 100% logic và dữ liệu trong phân đoạn. Giải pháp của bạn sẽ yêu cầu khá nhiều Giữ ​​tất cả dữ liệu trong FragmentActivity và sau đó sử dụng từng Fragment đơn giản để xử lý Logic hiển thị. Như tôi đã nói, tôi thích điều này hơn nhưng sự tương tác giữa các mảnh là siêu nặng và có thể gây khó chịu khi quản lý. Dù sao Cảm ơn bạn đã giải thích chi tiết của bạn. Bạn đã giải thích điều này tốt hơn nhiều so với tôi có thể.
Maurycy

28
Tóm lại: không bao giờ giữ một tham chiếu đến một bên ngoài Fragment của Adaptor
passsy

2
Vậy làm thế nào để một cá thể Activity truy cập vào cá thể FragmentPagerAdapter nếu nó không khởi tạo FragmentPagerAdapter? Các phiên bản trong tương lai sẽ không chỉ phục hồi FragmentPagerAdapter và tất cả các phiên bản phân mảnh của nó sao? FragmentPagerAdapter có nên triển khai tất cả các giao diện phân mảnh để quản lý giao tiếp giữa các phân mảnh không?
Eric H.

tuyên bố "Xử lý nó cũng khá khó khăn để có được tham chiếu đến nó vì nó đã được thêm vào một thẻ mà bạn không biết. Điều này là do thiết kế; bạn không nên làm rối với các phân đoạn mà máy nhắn tin xem đang quản lý . " là sai. rất dễ dàng để có được một tham chiếu bằng cách gọi điện instantiateItemvà bạn thực sự nên làm như vậy trong onCreatehoạt động của mình. xem chi tiết tại đây: stackoverflow.com/questions/14035090/…
morgwai

nói chung đó là tinh ranh để mảnh thuyết minh cho mình mà không cần tải bởi vì trong một "tiêu diệt và tái tạo" kịch bản mà bạn có thể kết thúc xử lý một đoạn riêng biệt để người thực sự tái tạo và hiển thị
HMAC

108

Tôi đã tìm thấy giải pháp đơn giản phù hợp với tôi.

Làm cho bộ điều hợp phân mảnh của bạn mở rộng FragmentStatePagerAdapter thay vì FragmentPagerAdapter và ghi đè phương thức onSave để trả về null

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

Điều này ngăn android tạo lại phân mảnh


Một ngày sau, tôi tìm thấy một giải pháp khác tốt hơn.

Gọi setRetainInstance(true)cho tất cả các phân đoạn của bạn và lưu các tham chiếu đến chúng ở đâu đó. Tôi đã làm điều đó trong biến static trong hoạt động của mình, bởi vì nó được khai báo là singleTask và các đoạn mảnh có thể giữ nguyên mọi lúc.

Bằng cách này, android không tạo lại các phân đoạn mà sử dụng các trường hợp giống nhau.


4
Cảm ơn, cảm ơn, cảm ơn rất nhiều, tôi thực sự không có lời cảm ơn bạn Mik, tôi đã đuổi vấn đề này từ 10 ngày qua và cố gắng rất nhiều methods.But này bốn dây chuyền ma thuật đã cứu sống tôi :)

7
có một tham chiếu tĩnh đến các mảnh và / hoặc các hoạt động là một việc rất rủi ro, vì nó có thể gây rò rỉ bộ nhớ rất dễ dàng. Tất nhiên, nếu cẩn thận, bạn có thể xử lý khá dễ dàng bằng cách đặt chúng thành null khi không cần thiết nữa.
nhà phát triển android

Điều này đã làm việc cho tôi. Các phân đoạn và chế độ xem tương ứng của chúng giữ liên kết của chúng sau khi ứng dụng gặp sự cố và khởi động lại. Cảm ơn!
ngọt ngào

Tôi đang sử dụng setRetainInstance(true)với FragmentPagerAdapter. Tất cả đều hoạt động tốt. Nhưng khi tôi xoay thiết bị, bộ chuyển đổi vẫn có các mảnh nhưng các mảnh không hiển thị. Các phương thức vòng đời của các mảnh cũng không được gọi. Có ai giúp được không?
Jonas

1
bạn đã cứu ngày của tôi !!! Đã tìm kiếm lỗi này trong 3 ngày cho đến nay. Tôi có một Viewpager có 2 phân đoạn bên trong Hoạt động SingleTask và với cờ "Không giữ Hoạt động" được bật. Cảm ơn rất nhiều!!!!
ma trận

29

Tôi đã giải quyết vấn đề này bằng cách truy cập trực tiếp vào các phân đoạn của mình thông qua FragmentManager thay vì qua FragmentPagerAdapter như vậy. Trước tiên, tôi cần tìm ra thẻ của đoạn được tạo tự động bởi FragmentPagerAdapter ...

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

Sau đó, tôi chỉ đơn giản là nhận được một tham chiếu đến đoạn đó và làm những gì tôi cần như vậy ...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

Bên trong các phân đoạn của mình, tôi đặt setRetainInstance(false);giá trị để tôi có thể thêm các giá trị vào gói đã lưu theo cách thủ công.

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

và sau đó trong OnCreate, tôi lấy khóa đó và khôi phục trạng thái của phân mảnh nếu cần. Một giải pháp dễ dàng mà thật khó (đối với tôi ít nhất) để tìm ra.


Tôi có một vấn đề tương tự như bạn, nhưng tôi không hiểu rõ lời giải thích của bạn, bạn có thể cung cấp thêm chi tiết? Tôi có và bộ điều hợp lưu trữ các phân đoạn trong danh sách, nhưng khi tôi tiếp tục ứng dụng của mình từ các ứng dụng ứng dụng gần đây, các ứng dụng gặp sự cố do có một số phân đoạn tách rời. Vấn đề là ở đây stackoverflow.com/questions/11631408/...
Georgy Gobozov

3
'Của tôi' của bạn trong tham chiếu phân mảnh là gì?
Josh

Tất cả chúng ta đều biết giải pháp này không phải là cách tiếp cận tốt nhưng nó là cách dễ nhất để sử dụng FragmentByTagtrong ViewPager.
Youngjae

đây là một cách làm thế nào để truy cập các mảnh vỡ mà không dựa vào khả năng tương thích với cách nội bộ gán thẻ: stackoverflow.com/questions/14035090/...
morgwai

26

Giải pháp thử nghiệm làm việc toàn cầu.

getSupportFragmentManager()giữ tham chiếu null một số lần và View pager không tạo mới vì nó tìm thấy tham chiếu đến cùng một phân đoạn. Vì vậy, việc sử dụng này getChildFragmentManager()giải quyết vấn đề một cách đơn giản.

Đừng làm điều này:

new PagerAdapter(getSupportFragmentManager(), fragments);

Làm cái này:

new PagerAdapter(getChildFragmentManager() , fragments);


6
điều này sẽ chỉ có thể thực hiện được nếu bạn đang lưu trữ (và khởi tạo) pagerAdapter bên trong Fragment chứ không phải bên trong hoạt động của bạn. Trong trường hợp này, bạn đã đúng, bạn nên sử dụng childFragmentManager theo mặc định. Việc sử dụng SupportFragmentManager sẽ bị sai theo mặc định
Klitos G.

Rất chính xác. Đã giải quyết vấn đề của tôi mà không sử dụng hack. Cảm ơn! Phiên bản Kotlin của tôi chuyển từ FragmentStatePagerAdapter(activity!!.supportFragmentManager)sang dễ nhìn hơn FragmentStatePagerAdapter(childFragmentManager):)
ecth

7

Đừng cố gắng tương tác giữa các phân đoạn trong ViewPager. Bạn không thể đảm bảo rằng phân đoạn khác được đính kèm hoặc thậm chí tồn tại. Thay vì thay đổi tiêu đề thanh hành động từ phân đoạn, bạn có thể làm điều đó từ hoạt động của mình. Sử dụng mẫu giao diện độc lập cho việc này:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}

Chắc chắn sẽ bao gồm mã ngay bây giờ ... Tôi đã ghi nhật ký các phương pháp đó. OnDetach được gọi khi onStop được gọi tronggmentActivity. onAttach được gọi ngay trước onCreate của FragmentActivity.
Maurycy

Hmm cảm ơn bạn nhưng sự cố vẫn còn. Thay vào đó, tôi đã đặt tên gọi lại. Tôi có thể không đặt logic vào Fragment? Tôi có các phân đoạn của tôi kích hoạt các dịch vụ và các phần tử khác yêu cầu tham chiếu đến hoạt động. Tôi sẽ phải chuyển tất cả logic ứng dụng của mình sang hoạt động chính để tránh vấn đề tôi đang gặp phải. Điều này có vẻ hơi không cần thiết.
Maurycy

Ok vì vậy tôi vừa kiểm tra điều này đầy đủ và nhận xét tất cả mã của tôi ... ngoại trừ lệnh gọi lại để thay đổi tiêu đề. Ứng dụng WHen lần đầu tiên được khởi chạy và tôi chuyển đến màn hình đó, tên tiêu đề được thay đổi np. Sau khi tải một số ứng dụng sau đó quay trở lại tiêu đề không thay đổi nữa. Có vẻ như cuộc gọi lại đang được nhận trên một phiên bản hoạt động cũ. Tôi không thể nghĩ ra cách giải thích khác
Maurycy

tôi đã khắc phục sự cố này thành sự cố vớigmentManager và sự cố này ... code.google.com/p/android/issues/detail?id=19211 . Tôi vẫn không có đầu mối làm thế nào để giải quyết này
Maurycy

5

Bạn có thể xóa các mảnh khi phá hủy máy ngắm, trong trường hợp của tôi, tôi đã xóa chúng trên onDestroyView()phân mảnh của mình:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}

Cảm ơn, giải pháp của bạn hoạt động. Nó được yêu cầu ViewPagercũng dựa trên childFragmentManagerbộ điều hợp (không fragmentManager). Cũng có một biến thể khác hoạt động: không sử dụng onDestroyView, nhưng loại bỏ các đoạn con trước khi ViewPagertạo bộ điều hợp.
CoolMind

4

Sau vài giờ tìm kiếm một vấn đề tương tự, tôi nghĩ có một giải pháp khác. Điều này ít nhất nó đã làm việc cho tôi và tôi chỉ phải thay đổi một vài dòng.

Đây là sự cố mà tôi gặp phải, tôi có một hoạt động với một máy nhắn tin xem sử dụng FragmentStatePagerAdapter với hai Fragment. Mọi thứ hoạt động tốt cho đến khi tôi buộc hoạt động bị phá hủy (tùy chọn của nhà phát triển) hoặc tôi xoay màn hình. Tôi giữ một tham chiếu đến hai đoạn sau khi chúng được tạo bên trong phương thức getItem.

Tại thời điểm đó, hoạt động sẽ được tạo lại và mọi thứ hoạt động tốt tại thời điểm này nhưng tôi đã mất tham chiếu đến fragmetns của mình vì getItem không được gọi lại.

Đây là cách tôi khắc phục sự cố đó, bên trong FragmentStatePagerAdapter:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object aux = super.instantiateItem(container, position);

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

Bạn sẽ không nhận được cuộc gọi trên getItem một lần nữa nếu bộ điều hợp đã có tham chiếu đến nó trong nội bộ và bạn không nên thay đổi điều đó. Thay vào đó, bạn có thể lấy đoạn mà nó đang được sử dụng bằng cách xem xét phương thức khác này ngay lập tức (), phương thức này sẽ được gọi cho mỗi đoạn của bạn.

Hy vọng rằng sẽ giúp được bất cứ ai.


Nếu bạn có nhiều mảnh vỡ thì sao? bạn có thực sự muốn lưu một tài liệu tham khảo cho mỗi người trong số họ không? Nó không phải là xấu cho trí nhớ?
nhà phát triển android

Tất cả phụ thuộc vào chính xác những gì bạn đang cố gắng đạt được. Thông thường với các máy nhắn tin dạng xem này, bạn không chứa nhiều hơn 3 mảnh cùng một lúc. Cái ở giữa và cái ở mỗi bên. Đối với những gì tôi muốn, tôi thực sự cần tham chiếu đến các mảnh vỡ, nhưng tôi biết tôi chỉ cần xử lý hai.
Lancelot

0

Vì FragmentManager sẽ chăm sóc khôi phục các Fragment của bạn cho bạn ngay sau khi phương thức onResume () được gọi, nên tôi yêu cầu phân đoạn gọi ra hoạt động và thêm chính nó vào danh sách. Trong trường hợp của tôi, tôi đang lưu trữ tất cả những thứ này trong triển khai PagerAdapter của mình. Mỗi phân mảnh biết vị trí của nó vì nó được thêm vào các đối số phân mảnh khi tạo. Bây giờ bất cứ khi nào tôi cần thao tác một phân đoạn tại một chỉ mục cụ thể, tất cả những gì tôi phải làm là sử dụng danh sách từ bộ điều hợp của mình.

Sau đây là ví dụ về Bộ điều hợp cho ViewPager tùy chỉnh sẽ phát triển phân đoạn khi nó di chuyển vào tiêu điểm và thu nhỏ khi nó di chuyển ra khỏi tiêu điểm. Ngoài các lớp Bộ điều hợp và Phân đoạn, tôi có ở đây, tất cả những gì bạn cần là hoạt động mẹ có thể tham chiếu đến biến bộ điều hợp và bạn đã được thiết lập.

Bộ chuyển đổi

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

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

@Override
public Fragment getItem(int position) {
    return SummaryTabletFragment.newInstance(position);
}

@Override
public void onPageScrollStateChanged(int state) {}

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

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

Miếng

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

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

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

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

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}

0

Giải pháp của tôi: Tôi đặt hầu hết mọi Chế độ xem là static. Bây giờ ứng dụng của tôi tương tác hoàn hảo. Có thể gọi các phương thức tĩnh từ khắp mọi nơi có lẽ không phải là một phong cách tốt, nhưng tại sao phải thử với mã không hoạt động? Tôi đã đọc rất nhiều câu hỏi và câu trả lời của họ ở đây trên SO và không có giải pháp nào mang lại thành công (đối với tôi).

Tôi biết nó có thể làm rò rỉ bộ nhớ và lãng phí đống, và mã của tôi sẽ không phù hợp với các dự án khác, nhưng tôi không cảm thấy lo sợ về điều này - tôi đã thử nghiệm ứng dụng trên các thiết bị và điều kiện khác nhau, không có vấn đề gì cả, Android Nền tảng dường như có thể xử lý điều này. Giao diện người dùng được làm mới mỗi giây và ngay cả trên thiết bị S2 ICS (4.0.3), ứng dụng có thể xử lý hàng nghìn điểm đánh dấu địa lý.


0

Tôi gặp phải vấn đề tương tự nhưng ViewPager của tôi nằm bên trong TopFragment đã tạo và đặt bộ điều hợp bằng cách sử dụng setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

Tôi đã khắc phục sự cố này bằng cách ghi đè onAttachFragment(Fragment childFragment)trong TopFragment như sau:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

Như đã biết (xem câu trả lời ở trên), khi childFragmentManager tự tạo lại, nó cũng tạo ra các đoạn bên trong viewPager.
Phần quan trọng là sau đó, anh ấy gọi onAttachFragment và bây giờ chúng ta có một tham chiếu đến phân đoạn mới được tạo lại!

Hy vọng điều này sẽ giúp ích cho ai nhận được Q già này như tôi :)


0

Tôi đã giải quyết vấn đề bằng cách lưu các phân đoạn trong SparceArray:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}

0

Mong bạn biết ...

Thêm vào những rắc rối với các lớp này, có một lỗi khá thú vị đáng được chia sẻ.

Tôi đang sử dụng ViewPager để điều hướng một cây các mục (chọn một mục và chế độ xem pager hoạt động cuộn sang bên phải và nhánh tiếp theo xuất hiện, điều hướng trở lại và ViewPager cuộn theo hướng ngược lại để quay lại nút trước đó) .

Sự cố phát sinh khi tôi đẩy và bật các đoạn ra khỏi phần cuối của FragmentStatePagerAdapter. Nó đủ thông minh để nhận thấy rằng các mục thay đổi và đủ thông minh để tạo và thay thế một mảnh khi mục đó đã thay đổi. Nhưng không đủ thông minh để loại bỏ trạng thái phân mảnh hoặc đủ thông minh để cắt các trạng thái phân mảnh được lưu bên trong khi kích thước bộ điều hợp thay đổi. Vì vậy, khi bạn mở một mục và đẩy một mục mới vào cuối, phân đoạn cho mục mới sẽ nhận được trạng thái lưu của phân đoạn cho mục cũ, điều này gây ra sự tàn phá tuyệt đối trong mã của tôi. Các phân đoạn của tôi chứa dữ liệu có thể đòi hỏi nhiều thao tác để tìm nạp lại từ internet, vì vậy không lưu trạng thái thực sự không phải là một lựa chọn.

Tôi không có giải pháp thay thế rõ ràng. Tôi đã sử dụng một cái gì đó như thế này:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

Một giải pháp không hoàn hảo vì cá thể phân mảnh mới vẫn nhận được Gói State đã lưu, nhưng ít nhất nó không mang dữ liệu cũ.


0

Vì mọi người không có xu hướng đọc bình luận, đây là một câu trả lời hầu như trùng lặp với những gì tôi đã viết ở đây :

nguyên nhân gốc rễ của vấn đề là thực tế là hệ thống Android không gọi getItemđể lấy các phân đoạn thực sự được hiển thị, nhưng instantiateItem. Phương pháp này đầu tiên cố gắng tra cứu và sử dụng lại một cá thể phân mảnh cho một tab nhất định trong FragmentManager. Chỉ khi tra cứu này không thành công (chỉ xảy ra lần đầu tiên khi FragmentManagermới được tạo) thì mới getItemđược gọi. Vì những lý do rõ ràng là không tạo lại các đoạn (có thể nặng), chẳng hạn như mỗi khi người dùng xoay thiết bị của mình.
Để giải quyết vấn đề này, thay vì tạo các phân đoạn với Fragment.instantiatetrong hoạt động của mình, bạn nên thực hiện với pagerAdapter.instantiateItemvà tất cả các lệnh gọi này phải được bao quanh bởi startUpdate/finishUpdatecác lệnh gọi phương thức bắt đầu / cam kết giao dịch phân đoạn tương ứng.getItem phải là nơi mà các mảnh thực sự được tạo ra bằng cách sử dụng các hàm tạo tương ứng của chúng.

List<Fragment> fragments = new Vector<Fragment>();

@Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        adapter.finishUpdate(viewPager);
}

class MyPagerAdapter extends FragmentPagerAdapter {

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

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

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
}
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.