0. Preface:

Animation and edge estimates a little cold, a lot of people will make do, today I will go into depth about it edge plan is a kind of Internet spread, personal feeling is also the best, and slightly improved a little

Figure town building
Town building 1 Town building 2
This series is divided into three parts:
  • RecyclerView Zero-point Breakthrough (Basic Usage)
  • RecyclerView Zero Breakthrough (animation + sidebar)
  • RecyclerView Zero-point Breakthrough (Detailed Analysis)


1. Animation — Built-in parsingDefaultItemAnimatorWith the custom

This is less than 700 lines of code, which should hold. For the sake of research, I will make a copy of DefaultItemAnimator into the project

Get the whole picture:

DefaultItemAnimator – > SimpleItemAnimator – > RecyclerView. ItemAnimator several core callback function is as follows:


1.1. When adding:

The default effect is for the following items to slide down as a whole, and the following inserted items to fade in (transparency 0~1)


1.1.1: View function execution at add time

AnimateMove, endAnimationy pair called animateAdd 10 times, endAnimation pair called runPendingAnimations 1. The largest item in an animateMove is position: 11, the maximum Position on the current page, has been tested several times: all items on the current page after the insertion Position respond to the animateMove method, and in random order, the inserted items respond to the animateAdd methodCopy the code

1.1.2: animateAdd analysis
-->[DefaultItemAnimator#animateAdd]----------------------------------------------------- @Override public boolean animateAdd(final ViewHolder holder) { resetAnimation(holder); // Reset the animation holder.itemView.setalpha (0); // Set the transparency of the item to 0, which is the blank area mpendingExtractor.add (holder); // Add this transparent item to the list of mPendingAdditionsreturn true;
}

-->[DefaultItemAnimator#animateAdd]
-----------------------------------------------------
private void resetAnimation(ViewHolder holder) {
    if(sDefaultInterpolator == null) { sDefaultInterpolator = new ValueAnimator().getInterpolator(); } holder.itemView.animate().setInterpolator(sDefaultInterpolator); endAnimation(holder); } - > [to add ViewHolder list] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- private ArrayList < ViewHolder > mPendingAdditions = new ArrayList<>();Copy the code

1.1.3: endAnimation analysis of mPendingAdditions
 @Override
    public void endAnimation(ViewHolder item) {
        Log.e(TAG, "endAnimation: "); final View view = item.itemView; // Entry view view.animate().cancel(); // Unanimate the items view // skip n lines.... // List of item layouts added: mPendingAdditionsif(mpendingaddition.remove (item)) {// Remove the item view.setalpha (1); // Set this item transparency to 1 dispatchAddFinished(item); } // omit n lines.... dispatchFinishedWhenDone(); }Copy the code

1.1.4: mPendingAdditions in runPendingAnimations
- > [ArrayList < ViewHolder > list] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ArrayList < ArrayList < ViewHolder > > mAdditionsList = new ArrayList<>(); -->[DefaultItemAnimator#runPendingAnimations]
-----------------------------------------------------
@Override
public void runPendingAnimations() {//mPendingAdditions not empty, can add Boolean additionsPending =! mPendingAdditions.isEmpty(); / / additionsPending forfalseCan result in a direct return without animationif(! removalsPending && ! movesPending && ! additionsPending && ! changesPending) {return; } // omit n lines....if(additionsPending) { final ArrayList<ViewHolder> additions = new ArrayList<>(); additions.addAll(mPendingAdditions); // Add the view of mPendingAdditions to additions mAdditionsList (additions); //mAdditionsList box of additions mpendingextraction.clear (); //mPendingAdditions = newRunnable() {// Runnable... Remember this guy's name [adder] @override public voidrun() {
                for(ViewHolder holder: additions) {// Additions: additions animateAddImpl(holder); //---- animation core ----} addition.clear (); // Delete additions mAdditionsList. Remove (additions); // Remove additions}};if(removalsPending | | movesPending | | changesPending) {/ / if there are other animation to perform long removeDuration = removalsPending? getRemoveDuration() : 0; long moveDuration = movesPending ? getMoveDuration() : 0; long changeDuration = changesPending ? getChangeDuration() : 0; long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); View view = additions.get(0).itemView; ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); }else{ adder.run(); / / start [adder]}}} - > [to add animation ViewHolder] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ArrayList < ViewHolder > mAddAnimations = new ArrayList<>(); -->[DefaultItemAnimator#animateAddImpl]----------------------------------------------------- void animateAddImpl(final ViewHolder holder) { final View view = holder.itemView; Final ViewPropertyAnimator animation = view.animate(); // Get animate Maddanimations.add (holder); Animation.alpha (1).setDuration(getAddDuration()))//tag1: default duration 120ms-- Execute the transparency animation.setListener (new)AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { dispatchAddStarting(holder); } public void onAnimationCancel(Animator Animator) {view.setalpha (1); } public void onAnimationEnd(Animator Animator) {animation.setListener(null); dispatchAddFinished(holder); mAddAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); } -->[android.support.v7.widget.RecyclerView.ItemAnimator#getAddDuration]
------------------------tag1-----------------------------
private long mAddDuration = 120;
public long getAddDuration() {
    return mAddDuration;
}
Copy the code

1.2: Custom add animation
1.2.1: Fixed point rotation

Notice that the animation in the animateAddImpl is called after the move has finished

-->[RItemAnimator#animateAddImpl]
-----------------------------------------------------
animation.rotation(360).setDuration(1000)
        .setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animator) {
                view.setAlpha(1);

Copy the code

1.2.2 jitter
Zoom jitter Mobile jitter

ViewPropertyAnimator: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet: AnimatorSet Always implemented. If you can’t use ObjectAnimator, see ObjectAnimator

void animateAddImpl(final ViewHolder holder) { final View view = holder.itemView; mAddAnimations.add(holder); // create instance //(View, attribute name, initialization value, end value).offloat (View,"translationX", 0, 20, -20, 0, 20, -20, 0, 20, -20, 0) .setDuration(300); ObjectAnimator scaleX = ObjectAnimator// Create instance //(View, attribute name, initial value, end value).offloat (View,"scaleX",1, 0.95f, 1.05f,1, 0.95f, 1.05f,1, 0.95f, 1.05f,1).setduration (300); // Set the duration AnimatorSetset= new AnimatorSet(); set.playTogether(scaleX,translationX); // Two effects together //set. PlaySequentially (translationX); // Add animation //set.playSequentially(scaleX); // Add the animation set.addListener(new)AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { view.setAlpha(1); } @Override public void onAnimationEnd(Animator animation) { dispatchAddFinished(holder); mAddAnimations.remove(holder); dispatchFinishedWhenDone(); } @Override public void onAnimationStart(Animator animation) { view.setAlpha(1); dispatchAddStarting(holder); }}); set.start(); }Copy the code

1.2.3: Fixed axis rotation
rotationX rotationY
RotationY = ObjectAnimator rotationY = ObjectAnimator rotationY = ObjectAnimator rotationY = ObjectAnimator rotationY = ObjectAnimator rotationY"rotationY", 0360). SetDuration (1000); ObjectAnimator rotationX = ObjectAnimator rotationX = ObjectAnimator rotationX = ObjectAnimator rotationX = ObjectAnimator rotationX"rotationX", 0360). SetDuration (1000); // Set the durationCopy the code

1.3: Insert the next item animation:
Results 1 Results 2
1.3.1.
Analysis with add: motion core in DefaultItemAnimator#animateMoveImpl;
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();   
ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();  
ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
Copy the code
-->[The following entry executes: animateMove()] ----------------------------------------------------- @Override public boolean animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; / / get the item View View fromX + = (int) holder. ItemView. GetTranslationX (); fromY += (int) holder.itemView.getTranslationY(); resetAnimation(holder); int deltaX = toX - fromX; int deltaY = toY - fromY;if (deltaX == 0 && deltaY == 0) {
        dispatchMoveFinished(holder);
        return false; }// Size calculationif(deltaX ! SetTranslationX (-deltax); }if(deltaY ! = 0) { view.setTranslationY(-deltaY); } //mPendingMoves adds MoveInfo-- Information about moves is encapsulated in MoveInfo, Add (new MoveInfo(holder, fromX, fromY, toX, toY));return true; } - > [mPendingMoves runPendingAnimations () the performance of] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- a Boolean movesPending = ! mPendingMoves.isEmpty();if(! removalsPending && ! movesPending && ! additionsPending && ! changesPending) { // nothing to animatereturn; } // And add is a routine -- the core motion method in: animateMoveImplif (movesPending) {
    final ArrayList<MoveInfo> moves = new ArrayList<>();
    moves.addAll(mPendingMoves);
    mMovesList.add(moves);
    mPendingMoves.clear();
    Runnable mover = new Runnable() {
        @Override
        public void run() {
            for(MoveInfo moveInfo : moves) { animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, moveInfo.toX, moveInfo.toY); } moves.clear(); mMovesList.remove(moves); }}; - > [mPendingMoves runPendingAnimations () the performance of] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; final int deltaX = toX - fromX; final int deltaY = toY - fromY;if(deltaX ! = 0) { view.animate().translationX(0); }if(deltaY ! = 0) { view.animate().translationY(0); } final ViewPropertyAnimator animation = view.animate(); mMoveAnimations.add(holder); Animation.setduration (getMoveDuration()).setListener(newAnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animator) {
            dispatchMoveStarting(holder);
        }
        @Override
        public void onAnimationCancel(Animator animator) {
            if(deltaX ! = 0) { view.setTranslationX(0); }if(deltaY ! = 0) { view.setTranslationY(0); } } @Override public void onAnimationEnd(Animator animator) { animation.setListener(null); dispatchMoveFinished(holder); mMoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); }Copy the code

1.3.2: Effect 1:
] - > [animateMoveImpl () -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / fixed axis rotation ObjectAnimator / / instance is created. The ofFloat (view,"rotationX", 0, 360) .setDuration(1000).start(); // Set the durationCopy the code

1.3.3: Effect 2:
] - > [animateMoveImpl () -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ObjectAnimator / / instance is created. The ofFloat (view,"ScaleX", 1, 0.5 f, f, 1.2 0.8 f, 1). SetDuration (1000). The start (); ObjectAnimator// create instance. OfFloat (view,"ScaleY", 1, 0.5 f, f, 1.2 0.8 f, 1). SetDuration (1000). The start (); // Set the durationCopy the code

1.4: summary

AnimateChangeImpl () : animateChangeImpl() : animateMoveImpl () : animateChangeImpl() : animateMoveImpl (

DefaultItemAnimator doesn't have to be an animation library. I don't think it's necessary to use DefaultItemAnimator to change a few sentences. After all, the requirements are constantly changing, and one library can't cover all the requirements, The only thing that can handle change is the change itself. Remember where to modify effects: Update data: animateChangeImpl() Add data: animateAddImpl() Move: animateMoveImpl()Copy the code

2. Drawing of edge lines:

Defects: Poor handling of grid and waterfall flow endings (although both layouts generally do not use edges)

2.1: Overview of effects
2.1.1: Three constructors:

2.1.2: Three styles:


2.2: Code implementation
2.2.1: the use of
//mIdRvGoods.addItemDecoration(new RVItemDivider(this, RVItemDivider.Type.HORIZONTAL,10,Color.BLACK)); //mIdRvGoods.addItemDecoration(new RVItemDivider(this, RVItemDivider.Type.VERTICAL)); / / horizontal and VERTICAL mIdRvGoods. AddItemDecoration (new RVItemDivider (this, RVItemDivider. Type. VERTICAL, 10, Color. BLACK)); mIdRvGoods.addItemDecoration(new RVItemDivider(this, RVItemDivider.Type.HORIZONTAL, 10, Color.BLACK));Copy the code
2.2.2: Code implementation
/** * Author: Zhang Feng Jietele <br/> * Time: 2018/12/30003:10:36 <br/> * Email: [email protected]<br/> RecyclerView line * / public class RVItemDivider extends RecyclerView. ItemDecoration {public enum Type {VERTICAL, / / VERTICAL line HORIZONTAL,// HORIZONTAL} private Paint mPaint; // private Drawable mDivider; //Drawable private int dividerHeight = 1; // The default is 1px private Type mOrientation; In the direction / / private static final int [] ATTRS = new int [] {android. State Richard armitage TTR event. ListDivider}; public RVItemDivider(Context context, Type orientation) { mOrientation = orientation; final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } public RVItemDivider(Context context, Type orientation, int drawableId) { this(context, orientation); mDivider = ContextCompat.getDrawable(context, drawableId); mDividerHeight = mDivider.getIntrinsicHeight(); } /** * custom divider ** @param context context * @param orientation list orientation * @param dividerHeight dividerHeight * @param dividerColor dividerColor  */ public RVItemDivider(Context context, Type orientation, int dividerHeight, int dividerColor) { this(context, orientation); mDividerHeight = dividerHeight; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(dividerColor); mPaint.setStyle(Paint.Style.FILL); } /** * get the size of the partition line ** @param outRect line * @param view line * @param parent RecyclerView * @param state state */ @override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); switch (mOrientation) {caseHORIZONTAL: outRect.set(0, 0, 0, mDividerHeight); // Cross the rectanglebreak;
            caseVERTICAL: outRect.set(0, 0, mDividerHeight, 0); @param canvas @param parent RecyclerView @param state */ @override public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { super.onDraw(canvas, parent, state); switch (mOrientation) {caseVERTICAL: drawVertical(canvas, parent); // Vertical rectanglebreak;
            caseHORIZONTAL: drawHorizontal(canvas, parent); // Cross the rectanglebreak; Private void drawHorizontal(canvas canvas, private void drawHorizontal(canvas canvas, private void drawHorizontal)) RecyclerView parent) { final int left = parent.getPaddingLeft(); final int right = parent.getMeasuredWidth() - parent.getPaddingRight(); final int childNum = parent.getChildCount();for(int i = 0; i < childNum; I ++) {final View child = parent.getChildat (I); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); At the bottom of the line / / in the upper left corner coordinates (itemView + margin, itemView + + line high margin) at the bottom of the final int top = child. GetBottom () + layoutParams. BottomMargin; final int bottom = top + mDividerHeight;if(mDivider ! = null) {// Draw the mDivider mDivider. SetBounds (left, top, right, bottom); mDivider.draw(canvas); }if(mPaint ! DrawRect (left, top, right, bottom, mPaint); }} /** * drawVertical line -------- ** @param canvas * @param parent RecyclerView */ private void drawVertical(canvas) canvas, RecyclerView parent) { final int top = parent.getPaddingTop(); final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom(); final int childSize = parent.getChildCount();for (int i = 0; i < childSize; i++) {
            final View child = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int left = child.getRight() + layoutParams.rightMargin;
            final int right = left + mDividerHeight;
            if(mDivider ! = null) { mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); }if(mPaint ! = null) { canvas.drawRect(left, top, right, bottom, mPaint); }}}}Copy the code

Postscript: Jiwen specification

1. Growth records and errata of this paper
Program source code The date of note
V0.1 – making The 2018-12-5 RecyclerView Zero Breakthrough (animation + sidebar)
2. More about me
Pen name QQ WeChat hobby
Zhang Fengjie special lie 1981462002 zdl1994328 language
My lot My Jane books I’m the nuggets Personal website
3. The statement

3—- personal ability is limited, if there is something wrong welcome everyone to criticize and point out, must be humbly correct 4—- see here, I am here to thank you for your love and support