Remove Item + Scroll to Next in Android ViewPager
Asked Answered
C

3

9

I have an Activity containing a ViewPager that displays N fragments. Each fragment is showing the properties of an object from an ArrayList in my ViewPager's custom adapter (extends FragmentStatePagerAdapter).

The fragment has (among other things) a button that should remove the currently displayed fragment and scroll to the next one with setCurrentItem(position, true) so that if the user scrolls back, the previous item is gone. I do so by using something like this (simplified):

deleteButton.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {

              MyActivity parentActivity = (MyActivity)getActivity();

              // First, scroll to next item (smoothly)
              parentActivity.pager.setCurrentItem(parentActivity.pager.getCurrentItem()+1, true); 

              // Database stuff...
              doSomeDBOperations();

              // Method in Activity that removes the current object (I believe this method is working fine and yes, it calls notifyDataSetChanged())
              parent.removeObject(currentObject);

    }
});

This has the desired behavior as the object represented by the fragment whose delete button was pressed gets removed and the viewpager goes to the next page.

My problem is that the ViewPager doesn't scroll smoothly but rather "jumps instantly" to the next fragment. If I comment the removeObject() call, the smooth scroll works (but the item isn't removed). I believe it's has something to do with the removeObject() being called before the setCurrentItem() has finished the smooth scrolling animation?

Any ideas on how to fix this and achieve item removal + smooth scroll? If my assumption is correct, how can I make sure I get the smooth scroll to finish before removing the object?

EDIT 1:

My assumption seems correct. If I put the parent.removeObject(currentObject) inside

// ...inside the previously shown public void onClick(View v)...
confirm.postDelayed(new Runnable() {

    @Override
    public void run() {
              // Method in Activity that removes the current object (I believe this method is working fine and yes, it calls notifyDataSetChanged())
              parent.removeObject(currentObject);
    }
}, 1000);

so that the removeObject() call waits for a second, it works as expected: scroll to the next item, remove the previous. But this is a very ugly workaround so I'd still like a better approach.

EDIT 2:

I figured out a possible solution (see below).

Cullan answered 28/10, 2013 at 19:6 Comment(0)
C
13

I ended up overriding the

public void onPageScrollStateChanged(int state) 

method:

Whenever the user presses the delete button in the fragment, the listener sets a bool in the current item (flagging it for deletion) and scrolls to the next one.

When the onPageScrollStateChanged detects that the scroll state changed to ViewPager.SCROLL_STATE_IDLE (which happens when the smooth scroll ends) it checks if the previous item was marked for deletion and, if so, removes it from the ArrayList and calls notifyDataSetChanged().

By doing so, I've managed to get the ViewPager to smoothly scroll to the next position and delete the previous item when the "delete" button is pressed.

EDIT: Code snippet.

@Override
public void onPageScrollStateChanged(int state)
{   

    switch(state)
    {
    case ViewPager.SCROLL_STATE_DRAGGING:

        break;

    case ViewPager.SCROLL_STATE_IDLE:

        int previousPosition = currentPosition - 1;
        if(previousPosition < 0){
            previousPosition = 0;
        }
        MyItem previousItem = itemList.get(previousPosition);
        if(previousItem.isDeleted())
        {
            deleteItem(previousItem); 
            // deleteItem() Does some DB operations, then calls itemList.remove(position) and notifyDataSetChanged()
        }

        break;

    case ViewPager.SCROLL_STATE_SETTLING:
        break;
    }

}
Cullan answered 29/10, 2013 at 11:18 Comment(4)
Can you add a code snippet for this implementation? I am trying to implement something like this but failed.Ethnogeny
I tried the above and it works. However when I do notifyDataSetChanged() it sets the 2nd page from the deleted one as my current page. Could you please help?Manganate
This was a long time ago, but perhaps you need to override/modify getItemPosition(Object object), getCount() and getItemId(int position) on your ViewPager adapter.Cullan
Same problem as @AtulOHolicTugman
I
1

Have you tried ViewPager.OnPageChangeListener? I would call removeObject(n) method in OnPageChangeListener.onPageSelected(n+1) method.

Immaterialize answered 28/10, 2013 at 21:27 Comment(2)
I've just tried that. I put a bool in the adapter to flag if the previous item should be deleted onPageSelected() and change it to true when the user presses thee button. Sadly, that doesn't work because the onPageSelected() method seems to fire before the smooth scroll has finished. I've updated my question.Cullan
I ended up doing it in a different way but I'm up-voting for pointing me to the right direction.Cullan
B
1

I did something different that works smoothly. The idea is to to remove the current item with animation (setting its alpha to 0), then translating horizontally the left or right item (with animation) to the now invisible item position. After the animation is complete, I do the actual data removal and notfyDataSetChanged() call.

This remove() method I put inside a subclass of ViewPager

public void remove(int position, OnViewRemovedListener onViewRemovedListener) {

    final int childCount = getChildCount();
    if (childCount > 0) {
        View toRemove = getChildAt(position);
        int to = toRemove.getLeft();

        final PagerAdapter adapter = getAdapter();
        toRemove.animate()
                .alpha(0)
                .setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime))
                .setListener(new SimpleAnimatorListener() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        if (childCount == 1) {
                            if (onViewRemovedListener != null) onViewRemovedListener.onRemoved(position, -1);
                            if (adapter!= null) adapter.notifyDataSetChanged();
                        }
                    }
                })
                .start();

        if (childCount > 1) {
            int newPosition = position + 1 <= childCount - 1 ? position + 1 : position - 1;
            View replacement = getChildAt(newPosition);
            int from = replacement.getLeft();
            replacement.animate()
                    .setInterpolator(new DecelerateInterpolator())
                    .setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime))
                    .translationX(to - from)
                    .setListener(new SimpleAnimatorListener() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            if (onViewRemovedListener != null) onViewRemovedListener.onRemoved(position, newPosition);
                            if (adapter!= null) adapter.notifyDataSetChanged();
                        }
                    })
                    .start();
        }
    }
}

public interface OnViewRemovedListener {

    void onRemoved(int position, int newPosition);
}
Birkle answered 5/8, 2018 at 4:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.