Search code examples
androidandroid-viewpagerandroid-banner

I can only use the ViewPager to page backward and can not page forward


I can only use the ViewPager to page backward and can not page forward.

running image

I'm writint an banner,but I can only use the ViewPager to page backward and can not page forward.

This is the adapter I wrote.

class MyPagerAdapter extends PagerAdapter {

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        int p = position % mImgRes.length;
        container.addView(mListView.get(p));
        return mListView.get(p);
    } 

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
    }

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

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

}

This is an error message.

   07-29 20:45:02.098 20791-20791/com.example.myfirstpro E/AndroidRuntime: 
   FATAL EXCEPTION: main
   Process: com.example.myfirstpro, PID: 20791
   java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
       at android.view.ViewGroup.addViewInner(ViewGroup.java:4465)
       at android.view.ViewGroup.addView(ViewGroup.java:4301)
       at android.support.v4.view.ViewPager.addView(ViewPager.java:1505)
       at android.view.ViewGroup.addView(ViewGroup.java:4242)
       at android.view.ViewGroup.addView(ViewGroup.java:4215)
       at com.example.myfirstpro.MainFragment$MyPagerAdapter.instantiateItem(MainFragment.java:62)
       at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:1034)
       at android.support.v4.view.ViewPager.populate(ViewPager.java:1216)
       at android.support.v4.view.ViewPager.populate(ViewPager.java:1116)
       at android.support.v4.view.ViewPager$3.run(ViewPager.java:273)
       at android.view.Choreographer$CallbackRecord.run(Choreographer.java:892)
       at android.view.Choreographer.doCallbacks(Choreographer.java:704)
       at android.view.Choreographer.doFrame(Choreographer.java:637)
       at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:878)
       at android.os.Handler.handleCallback(Handler.java:739)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:148)
       at android.app.ActivityThread.main(ActivityThread.java:5628)
       at java.lang.reflect.Method.invoke(Native Method)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:853)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:737)

The program will crash when I roll forward.

This is in addition to the code other than the adapter.@Hong Duan

    private ViewPager mVpScroll;
private int mImgRes[] = new int[] {
        R.drawable.banner01,
        R.drawable.banner02,
        R.drawable.banner03
};
private List<View> mListView = new ArrayList<>();
private LayoutInflater mInfalte;

public MainFragment() {
    // Required empty public constructor
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    for (int i = 0; i < mImgRes.length; i++) {
        View inflate = inflater.inflate(R.layout.fragment_main_scroll_item, null);
        ImageView ivBanner = (ImageView) inflate.findViewById(R.id.iv_scroll);
        ivBanner.setImageResource(mImgRes[i]);
        mListView.add(inflate);
    }
    mInfalte = inflater;
    return inflater.inflate(R.layout.fragment_main, container, false);
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    mVpScroll = (ViewPager) view.findViewById(R.id.vp_scroll);
    mVpScroll.setAdapter(new MyPagerAdapter());
}

Solution

  • As the error message shows, the root cause is a child View is added twice. But why this only happens when you swipe backward? I've made a test project and I think I find the reason :)

    When ViewPager scrolls, it will recycle items using its PagerAdapter's methods: instantiateItem and destroyItem, when ViewPager wants to show a new item, instantiateItem is called, when ViewPager scrolls away, the view which off the screen will be destroyed by method destroyItem.

    By default, the ViewPager will keep 1 more item on each side for smooth scrolling, which means there are at most 3 items keep by the ViewPager.

    The problem is, you are using:

    int p = position % mImgRes.length;
    

    to implement a "loop" ViewPager, that means if mImgRes.length == 3, the position 3 will show the same View at position 0.

    Now think about your case:

    • when ViewPager inited, the item 0 and 1(right side item) are added by instantiateItem;

    • then scroll right a page, the item 1 is shown, and the item 2 is added, now the item 0(left side item), 1, 2(right side item) are kept;

    • scroll right a page again, the item 2 is shown, the item 3 is added, the item 0 is destroyed by method destroyItem, now the item 1(left side item), 2, 3(right side item, the same item to 0) are kept;

    Everything works fine if instantiateItem and destroyItem work as expected, and it does work when you scroll to the right("forward"), but it does not work when you scroll to the left("backward") because the order of when instantiateItem and destroyItem are executed is different! See the log:

    07-30 22:00:04.385 26064-26064/com.example.test D/xxxxx: instantiateItem: 0
    07-30 22:00:04.388 26064-26064/com.example.test D/xxxxx: instantiateItem: 1
    07-30 22:00:17.693 26064-26064/com.example.test D/xxxxx: instantiateItem: 2
    07-30 22:00:18.743 26064-26064/com.example.test D/xxxxx: destroyItem: 0
    07-30 22:00:18.744 26064-26064/com.example.test D/xxxxx: instantiateItem: 3
    07-30 22:00:26.477 26064-26064/com.example.test D/xxxxx: instantiateItem: 0  < -- crash!!!
    07-30 22:00:18.743 26064-26064/com.example.test D/xxxxx: destroyItem: 3
    

    when you scroll to the right, destroyItem executed first, then instantiateItem, so it works fine, but when you scroll to the left, instantiateItem executed first, so when the last line of log printed, the item 0 is added the second time, because the item 3(which share the same View with item 0) has not been destroyed yet, then it crashes :)

    The interesting thing is that if mImgRes.length > 3, it will work fine, because when there are 4 items, it's not too late when destroyItem execute.

    So, if you want to implement ViewPager with the "loop" feature, you have to think about how to work around this issue.