Continuing the previous article , in this one I’ll talk about ItemDecoration
and ItemAnimator
and try to explain how they work in RecyclerView
using the example of a simple application that is available on Github .
ItemDecoration
used to decorate list items in a RecyclerView
.
With the help of ItemDecoration
you can add dividers between view
components, align them, or split them in equal intervals. To add a simple separator between view
components, use the DividerItemDecoration
class, which can be found in the support library version 25.1.0 and higher. The following code snippet demonstrates its implementation:
mDividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), mLayoutManager.getOrientation()); recyclerView.addItemDecoration(mDividerItemDecoration);
The best way to create your own separator is to extend the RecyclerView.ItemDecoration
class. In the sample application, I used the GridLayoutManager
and applied CharacterItemDecoration
to the RecyclerView
:
recyclerView.addItemDecoration(new CharacterItemDecoration(50));
Here CharacterItemDecoration
sets the offset ( born offset) to 50 pixels in its constructor and overrides getItemOffsets(...)
. Inside the getItemOffsets()
method, each outRects
field determines the number of pixels that must be set for each view
component, like internal and external indents. Since I used GridLayoutManager
and wanted to set equal distances between grid elements, I set the right padding to 25 pixels (i.e. offset / 2) for each even element and the left padding to 25 pixels for each odd element, while keeping the top padding the same for all items.
ItemAnimator
used to animate items or view
components within RecyclerView
.
Let's make our application Instagram-like by extending DefaultItemAnimator
and redefining several methods.
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) { return true; }
The canReuseUpdatedViewHolder(...)
method determines whether the same ViewHolder
be used for animation if the data of this element changes. If it returns false
, then both ViewHolders
— old and updated — are passed to the animateChange(...)
method.
public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state, @NonNull RecyclerView.ViewHolder viewHolder, int changeFlags, @NonNull List<Object> payloads) { if (changeFlags == FLAG_CHANGED) { for (Object payload : payloads) { if (payload instanceof String) { return new CharacterItemHolderInfo((String) payload); } } } return super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads); } public static class CharacterItemHolderInfo extends ItemHolderInfo { public String updateAction; public CharacterItemHolderInfo(String updateAction) { this.updateAction = updateAction; } }
RecyclerView
calls the recordPreLayoutInformation(...)
method before drawing a layout
. ItemAnimator
must record the necessary information about the view
component before it is overwritten, moved or deleted. The data returned by this method will be transferred to the corresponding animation method (in our case, this is animateChange(...)
).
@Override public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { if (preInfo instanceof CharacterItemHolderInfo) { CharacterItemHolderInfo recipesItemHolderInfo = (CharacterItemHolderInfo) preInfo; CharacterRVAdapter.CharacterViewHolder holder = (CharacterRVAdapter.CharacterViewHolder) newHolder; if (CharacterRVAdapter.ACTION_LIKE_IMAGE_DOUBLE_CLICKED.equals(recipesItemHolderInfo.updateAction)) { animatePhotoLike(holder); } } return false; } private void animatePhotoLike(final CharacterRVAdapter.CharacterViewHolder holder) { holder.likeIV.setVisibility(View.VISIBLE); holder.likeIV.setScaleY(0.0f); holder.likeIV.setScaleX(0.0f); AnimatorSet animatorSet = new AnimatorSet(); ObjectAnimator scaleLikeIcon = ObjectAnimator.ofPropertyValuesHolder (holder.likeIV, PropertyValuesHolder.ofFloat("scaleX", 0.0f, 2.0f), PropertyValuesHolder.ofFloat("scaleY", 0.0f, 2.0f), PropertyValuesHolder.ofFloat("alpha", 0.0f, 1.0f, 0.0f)); scaleLikeIcon.setInterpolator(DECELERATE_INTERPOLATOR); scaleLikeIcon.setDuration(1000); ObjectAnimator scaleLikeBackground = ObjectAnimator.ofPropertyValuesHolder (holder.characterCV, PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.95f, 1.0f), PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.95f, 1.0f)); scaleLikeBackground.setInterpolator(DECELERATE_INTERPOLATOR); scaleLikeBackground.setDuration(600); animatorSet.playTogether(scaleLikeIcon, scaleLikeBackground); animatorSet.start(); }
RecyclerView
calls the animateChange(...)
method when the adapter element is present both before and after drawing after calling the notifyItemChanged(int)
method. This method can also be used when calling notifyDataSetChanged()
, if the adapter uses stable identifiers. This is necessary so that RecyclerView
can reuse view
components in the same ViewHolders
. Note that this method takes as arguments: (ViewHolder oldHolder, ViewHolder newHolder, ItemHolderInfo preInfo, ItemHolderInfo postInfo) . Because we reuse the ViewHolder
, both oldHolder and newHolder are the same.
Whenever the user double-clicks on any item, the following method is called:
notifyItemChanged(position, ACTION_LIKE_IMAGE_DOUBLE_CLICKED);
This runs the entire call chain: canReuseUpdatedViewHolder(...)
, recordPreLayoutInformation(...)
and, ultimately, animateChange(...)
in ItemAnimator
, which in turn animates the list item and the heart icon in this element ( example on the gifs above).
This is the second part of a series of articles about RecyclerView
. If you missed the first part, then read it here .
A few more good articles on RecyclerView
:
Source: https://habr.com/ru/post/426773/
All Articles