12

I have a recyclerView, that contains a list of custom views. When I click a recyclerView item, it is expanded, providing more information. Another click upon this view (or upon another one) closes current one. The default animation, that provides SimpleItemAnimator, doesn't appear to be suitable. So, I tried to implement animateChange on my own like this:

public class CustomItemAnimator extends CustomDefaultItemAnimator {

    private static final int FADE_IN = 1;
    private static final int FADE_OUT = 2;

    private ItemHolderInfo getItemHolderInfo(RecyclerView.ViewHolder viewHolder, MoveInfo moveInfo) {
        moveInfo.stuff.clear();
        ViewGroup content_container = (ViewGroup) viewHolder.itemView.findViewById(R.id.item_content);
        for (int i = 0; i < content_container.getChildCount(); i++) {
            View view = content_container.getChildAt(i);
            if (view.getVisibility() == View.VISIBLE) {
                moveInfo.stuff.put(view.getId(), new Pair<>(view.getTop(), view.getBottom()));
            }
        }
        moveInfo.stuff.put(viewHolder.itemView.getId(), new Pair<>(viewHolder.itemView.getTop(), viewHolder.itemView.getBottom()));
        moveInfo.stuff.put(content_container.getId(), new Pair<>(content_container.getTop(), content_container.getBottom()));
        return moveInfo;
    }

    @NonNull
    @Override
    public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state, @NonNull RecyclerView.ViewHolder viewHolder, int changeFlags, @NonNull List<Object> payloads) {
        ItemHolderInfo info = super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads);
        return getItemHolderInfo(viewHolder, (MoveInfo) info);
    }


    @NonNull
    @Override
    public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state, @NonNull RecyclerView.ViewHolder viewHolder) {
        MoveInfo info = (MoveInfo) super.recordPostLayoutInformation(state, viewHolder);
        return getItemHolderInfo(viewHolder, info);
    }

    @Override
    public boolean animateChange(@NonNull final RecyclerView.ViewHolder oldHolder, @NonNull final RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        Log.d("ANIMATOR", "animate change called 2");
        MoveInfo pre = (MoveInfo) preInfo;
        MoveInfo post = (MoveInfo) postInfo;
        int fade_style;
        final HashMap<Integer, Pair<Integer, Integer>> minMap;
        HashMap<Integer, Pair<Integer, Integer>> maxMap;
        if (pre.stuff.keySet().size() <= post.stuff.keySet().size()) {
            minMap = pre.stuff;
            maxMap = post.stuff;
            fade_style = FADE_IN;
        } else {
            minMap = post.stuff;
            maxMap = pre.stuff;
            fade_style = FADE_OUT;
        }

        /*Getting the GoneViews*/
        HashMap<Integer, Pair<Integer, Integer>> goneViews = new HashMap<>();

        for (Integer i : maxMap.keySet()) {
            if (minMap.get(i) == null) {
                goneViews.put(i, maxMap.get(i));
            }
        }
        List<Animator> animators = new ArrayList<>();

        for (Integer i : minMap.keySet()) {
            ViewPropertyAnimatorCompat animView = ViewCompat.animate(newHolder.itemView.findViewById(i));
            ObjectAnimator viewDeltaTop = ObjectAnimator.ofInt(newHolder.itemView.findViewById(i), "top", pre.stuff.get(i).first, post.stuff.get(i).first);
            ObjectAnimator viewDeltaBot = ObjectAnimator.ofInt(newHolder.itemView.findViewById(i), "bottom", pre.stuff.get(i).second, post.stuff.get(i).second);
            animators.add(viewDeltaTop);
            animators.add(viewDeltaBot);
        }

        for (Integer i : goneViews.keySet()) {
            float from = fade_style == FADE_IN ? 0.0f : 1.0f;
            float to = fade_style == FADE_IN ? 1.0f : 0.0f;
            Animator viewAlpha = ObjectAnimator.ofFloat(newHolder.itemView.findViewById(i), "alpha", from, to);
            animators.add(viewAlpha);
        }
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(animators);
        animatorSet.setDuration(getChangeDuration());
        animatorSet.start();
        return true;
    }

    @Override
    public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull List<Object> payloads) {
        //to make viewholders reusable
        return true;
    }

    //we store our infos here
    private class MoveInfo extends ItemHolderInfo {
        HashMap<Integer, Pair<Integer, Integer>> stuff = new HashMap<>();
    }

    //not implementing this cause cast error in my case
    @Override
    public ItemHolderInfo obtainHolderInfo() {
        return new MoveInfo();
    }
}

This works like a charm, except one thing. If I try to scroll, while changes are animated, the animated view is not displayed properly. It looks like as if the animated bottom/top values don't receive any input by the scroll. I rly like the way, the itemAnimator works, and I would like to make things work at this way. Any tips are appreciated. Probably, I have to examine LinearLayoutManager?

NST
  • 15
  • 10
Fattum
  • 996
  • 1
  • 9
  • 23
  • What problem do you face when using the SimpleItemAnimator? Why is it not suitable? – Ashish Pathak Oct 20 '16 at 09:54
  • SimpleItemAnimator is somewhat unpropriate for the kind of animation I want to execute. If you look at the default implementation of animateChange and animateMove, you will notice, that it instanlty resizes the view to the new size and then slides in this view changing the Translation parameter. This is not the behaviour I want. If you look at my code, I want the smooth Collapse/Expansion of my views, meaning, that all other viewHolders move as long, as the animated view changes it's size – Fattum Oct 21 '16 at 14:34
  • Did you look at this SO post? https://stackoverflow.com/questions/4946295/android-expand-collapse-animation – Yuxal Mar 15 '18 at 14:04

0 Answers0