com.google.android.flexbox.FlexboxLayoutManager.java Source code

Java tutorial

Introduction

Here is the source code for com.google.android.flexbox.FlexboxLayoutManager.java

Source

/*
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.flexbox;

import static android.support.v7.widget.LinearLayoutManager.INVALID_OFFSET;
import static android.support.v7.widget.RecyclerView.NO_POSITION;

import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * LayoutManager for the {@link RecyclerView}. This class is intended to be used within a
 * {@link RecyclerView} and offers the same capabilities of measure/layout its children
 * as the {@link FlexboxLayout}.
 */
public class FlexboxLayoutManager extends RecyclerView.LayoutManager
        implements FlexContainer, RecyclerView.SmoothScroller.ScrollVectorProvider {

    private static final String TAG = "FlexboxLayoutManager";

    /**
     * Temporary Rect instance to be passed to
     * {@link RecyclerView.LayoutManager#calculateItemDecorationsForChild}
     * to avoid creating a Rect instance every time.
     */
    private static final Rect TEMP_RECT = new Rect();

    private static final boolean DEBUG = false;

    /**
     * The current value of the {@link FlexDirection}, the default value is {@link
     * FlexDirection#ROW}.
     *
     * @see FlexContainer#getFlexDirection()
     */
    private int mFlexDirection;

    /**
     * The current value of the {@link FlexWrap}, the default value is {@link FlexWrap#WRAP}.
     *
     * @see FlexContainer#getFlexWrap()
     */
    private int mFlexWrap;

    /**
     * The current value of the {@link JustifyContent}, the default value is
     * {@link JustifyContent#FLEX_START}.
     *
     * @see FlexContainer#getJustifyContent()
     */
    private int mJustifyContent;

    /**
     * The current value of the {@link AlignItems}, the default value is
     * {@link AlignItems#STRETCH}.
     *
     * @see FlexContainer#getAlignItems()
     */
    private int mAlignItems;

    private int mMaxLine = NOT_SET;

    /**
     * True if the layout direction is right to left, false otherwise.
     */
    private boolean mIsRtl;

    /**
     * True if the layout direction is bottom to top, false otherwise.
     */
    private boolean mFromBottomToTop;

    private List<FlexLine> mFlexLines = new ArrayList<>();

    private final FlexboxHelper mFlexboxHelper = new FlexboxHelper(this);

    /**
     * A snapshot of the {@link RecyclerView.Recycler} instance at a given moment.
     * It's not guaranteed that this instance has a reference to the latest Recycler.
     * When you want to use the latest Recycler, use the one passed as an method argument
     * (such as the one in {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)})
     */
    private RecyclerView.Recycler mRecycler;

    /**
     * A snapshot of the {@link RecyclerView.State} instance at a given moment.
     * It's not guaranteed that this instance has a reference to the latest State.
     * When you want to use the latest State, use the one passed as an method argument
     * (such as the one in {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)})
     */
    private RecyclerView.State mState;

    private LayoutState mLayoutState;

    private AnchorInfo mAnchorInfo = new AnchorInfo();

    /**
     * {@link OrientationHelper} along cross axis, which will be the primary scrolling direction.
     * e.g. If the flex direction is set to {@link FlexDirection#ROW} and flex wrap is set to
     * {@link FlexWrap#WRAP}, the RecyclerView scrolls vertically (along the cross axis).
     */
    private OrientationHelper mOrientationHelper;

    /**
     * {@link OrientationHelper} along the main axis, which will be the secondary scrolling
     * direction if the size of the main size is larger than the parent of the RecyclerView.
     */
    private OrientationHelper mSubOrientationHelper;

    private SavedState mPendingSavedState;

    /**
     * The position to which the next layout should start from this adapter position.
     * This value is set either from the {@link #mPendingSavedState} when a configuration change
     * happens or programmatically such as when the {@link #scrollToPosition(int)} is called.
     */
    private int mPendingScrollPosition = NO_POSITION;

    /**
     * The offset by which the next layout should be offset.
     */
    private int mPendingScrollPositionOffset = INVALID_OFFSET;

    /**
     * The width value used in the last {@link #onLayoutChildren} method.
     */
    private int mLastWidth = Integer.MIN_VALUE;

    /**
     * The height value used in the last {@link #onLayoutChildren} method.
     */
    private int mLastHeight = Integer.MIN_VALUE;

    /**
     * If set to {@code true}, this LayoutManager tries to recycle the children when detached from
     * the RecyclerView so that recycled views can be reused using RecycledViewPool.
     */
    private boolean mRecycleChildrenOnDetach;

    /**
     * View cache within this LayoutManager. This is used to avoid the same ViewHolder is created
     * multiple times in the same layout pass (onLayoutChildren or scrollHorizontally or
     * scrollVertically).
     * The keys and values in this cache needs to be cleared at the end of each layout pass.
     */
    private SparseArray<View> mViewCache = new SparseArray<>();

    private final Context mContext;

    /** The reference to the parent of the RecyclerView */
    private View mParent;

    /**
     * Indicates the position that the view position that the flex line which has the view having
     * this position needs to be recomputed before the next layout.
     * For example, this is updated when a new View is inserted into the position before the
     * first visible position.
     */
    private int mDirtyPosition = NO_POSITION;

    /**
     * Used for storing the results of calculation of flex lines to avoid creating a new instance
     * every time the calculation happens.
     */
    private FlexboxHelper.FlexLinesResult mFlexLinesResult = new FlexboxHelper.FlexLinesResult();

    /**
     * Creates a default FlexboxLayoutManager.
     */
    public FlexboxLayoutManager(Context context) {
        this(context, FlexDirection.ROW, FlexWrap.WRAP);
    }

    /**
     * Creates a FlexboxLayoutManager with the flexDirection specified.
     *
     * @param flexDirection the flex direction attribute
     */
    public FlexboxLayoutManager(Context context, @FlexDirection int flexDirection) {
        this(context, flexDirection, FlexWrap.WRAP);
    }

    /**
     * Creates a FlexboxLayoutManager with the flexDirection and flexWrap attributes specified.
     *
     * @param flexDirection the flex direction attribute
     * @param flexWrap      the flex wrap attribute
     */
    public FlexboxLayoutManager(Context context, @FlexDirection int flexDirection, @FlexWrap int flexWrap) {
        setFlexDirection(flexDirection);
        setFlexWrap(flexWrap);
        setAlignItems(AlignItems.STRETCH);
        setAutoMeasureEnabled(true);
        mContext = context;
    }

    /**
     * Constructor used when layout manager is set in XML by RecyclerView attribute
     * "layoutManager". No corresponding attributes for the {@code orientation},
     * {@code reverseLayout} and {@code stackFromEnd} exist in Flexbox, thus map the similar
     * attributes from Flexbox that behave similarly for each of them.
     *
     * {@code android:orientation} maps to the {@link FlexDirection},
     * HORIZONTAL -> {@link FlexDirection#ROW}, VERTICAL -> {@link FlexDirection#COLUMN}.
     *
     * {@code android.support.v7.recyclerview:reverseLayout} reverses the direction of the
     * {@link FlexDirection}, i.e. if reverseLayout is {@code true}, {@link FlexDirection#ROW} is
     * changed to {@link FlexDirection#ROW_REVERSE}. Similarly {@link FlexDirection#COLUMN} is
     * changed to {@link FlexDirection#COLUMN_REVERSE}.
     */
    public FlexboxLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
        switch (properties.orientation) {
        case LinearLayoutManager.HORIZONTAL:
            if (properties.reverseLayout) {
                setFlexDirection(FlexDirection.ROW_REVERSE);
            } else {
                setFlexDirection(FlexDirection.ROW);
            }
            break;
        case LinearLayoutManager.VERTICAL:
            if (properties.reverseLayout) {
                setFlexDirection(FlexDirection.COLUMN_REVERSE);
            } else {
                setFlexDirection(FlexDirection.COLUMN);
            }
            break;
        }
        setFlexWrap(FlexWrap.WRAP);
        setAlignItems(AlignItems.STRETCH);
        setAutoMeasureEnabled(true);
        mContext = context;
    }

    // From here, methods from FlexContainer
    @FlexDirection
    @Override
    public int getFlexDirection() {
        return mFlexDirection;
    }

    @Override
    public void setFlexDirection(@FlexDirection int flexDirection) {
        if (mFlexDirection != flexDirection) {
            // Remove the existing views even if the direction changes from
            // row -> row_reverse or column -> column_reverse to make the item decorations dirty
            // state
            removeAllViews();
            mFlexDirection = flexDirection;
            mOrientationHelper = null;
            mSubOrientationHelper = null;
            clearFlexLines();
            requestLayout();
        }
    }

    @Override
    @FlexWrap
    public int getFlexWrap() {
        return mFlexWrap;
    }

    @Override
    public void setFlexWrap(@FlexWrap int flexWrap) {
        if (flexWrap == FlexWrap.WRAP_REVERSE) {
            throw new UnsupportedOperationException("wrap_reverse is not supported in " + "FlexboxLayoutManager");
        }
        if (mFlexWrap != flexWrap) {
            if (mFlexWrap == FlexWrap.NOWRAP || flexWrap == FlexWrap.NOWRAP) {
                removeAllViews();
                clearFlexLines();
            }
            mFlexWrap = flexWrap;
            mOrientationHelper = null;
            mSubOrientationHelper = null;
            requestLayout();
        }
    }

    @JustifyContent
    @Override
    public int getJustifyContent() {
        return mJustifyContent;
    }

    @Override
    public void setJustifyContent(@JustifyContent int justifyContent) {
        if (mJustifyContent != justifyContent) {
            mJustifyContent = justifyContent;
            requestLayout();
        }
    }

    @AlignItems
    @Override
    public int getAlignItems() {
        return mAlignItems;
    }

    @Override
    public void setAlignItems(@AlignItems int alignItems) {
        if (mAlignItems != alignItems) {
            if (mAlignItems == AlignItems.STRETCH || alignItems == AlignItems.STRETCH) {
                removeAllViews();
                clearFlexLines();
            }
            mAlignItems = alignItems;
            requestLayout();
        }
    }

    @AlignContent
    @Override
    public int getAlignContent() {
        return AlignContent.STRETCH;
    }

    @Override
    public void setAlignContent(@AlignContent int alignContent) {
        throw new UnsupportedOperationException(
                "Setting the alignContent in the " + "FlexboxLayoutManager is not supported. Use FlexboxLayout "
                        + "if you need to use this attribute.");
    }

    @Override
    public int getMaxLine() {
        return mMaxLine;
    }

    @Override
    public void setMaxLine(int maxLine) {
        if (mMaxLine != maxLine) {
            mMaxLine = maxLine;
            requestLayout();
        }
    }

    @Override
    public List<FlexLine> getFlexLines() {
        List<FlexLine> result = new ArrayList<>(mFlexLines.size());
        for (int i = 0, size = mFlexLines.size(); i < size; i++) {
            FlexLine flexLine = mFlexLines.get(i);
            if (flexLine.getItemCount() == 0) {
                continue;
            }
            result.add(flexLine);
        }
        return result;
    }

    @Override
    public int getDecorationLengthMainAxis(View view, int index, int indexInFlexLine) {
        if (isMainAxisDirectionHorizontal()) {
            return getLeftDecorationWidth(view) + getRightDecorationWidth(view);
        } else {
            return getTopDecorationHeight(view) + getBottomDecorationHeight(view);
        }
    }

    @Override
    public int getDecorationLengthCrossAxis(View view) {
        if (isMainAxisDirectionHorizontal()) {
            return getTopDecorationHeight(view) + getBottomDecorationHeight(view);
        } else {
            return getLeftDecorationWidth(view) + getRightDecorationWidth(view);
        }
    }

    @Override
    public void onNewFlexItemAdded(View view, int index, int indexInFlexLine, FlexLine flexLine) {
        // To avoid creating a new Rect instance every time, passing the same Rect instance
        // since calculated decorations are assigned to view's LayoutParams inside the
        // calculateItemDecorationsForChild method anyway.
        calculateItemDecorationsForChild(view, TEMP_RECT);
        if (isMainAxisDirectionHorizontal()) {
            int decorationWidth = getLeftDecorationWidth(view) + getRightDecorationWidth(view);
            flexLine.mMainSize += decorationWidth;
            flexLine.mDividerLengthInMainSize += decorationWidth;
        } else {
            int decorationHeight = getTopDecorationHeight(view) + getBottomDecorationHeight(view);
            flexLine.mMainSize += decorationHeight;
            flexLine.mDividerLengthInMainSize += decorationHeight;
        }
    }

    /**
     * @return the number of flex items contained in the flex container.
     * This method doesn't always reflect the latest state of the adapter.
     * If you want to access the latest state of the adapter, use the {@link RecyclerView.State}
     * instance passed as an argument for some methods (such as
     * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)})
     *
     * This method is used to avoid the implementation of the similar method.
     * i.e. {@link FlexboxLayoutManager#getChildCount()} returns the child count, but it doesn't
     * include the children that are detached or scrapped.
     */
    @Override
    public int getFlexItemCount() {
        return mState.getItemCount();
    }

    /**
     * @return the flex item as a view specified as the index.
     * This method doesn't always return the latest state of the view in the adapter.
     * If you want to access the latest state, use the {@link RecyclerView.Recycler}
     * instance passed as an argument for some methods (such as
     * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)})
     *
     * This method is used to avoid the implementation of the similar method.
     * i.e. {@link FlexboxLayoutManager#getChildAt(int)} returns a view for the given index,
     * but the index is based on the layout position, not based on the adapter position, which
     * isn't desired given the usage of this method.
     */
    @Override
    public View getFlexItemAt(int index) {
        // Look up the cache within the LayoutManager first, since it's the most light operation.
        View cachedView = mViewCache.get(index);
        if (cachedView != null) {
            return cachedView;
        }
        return mRecycler.getViewForPosition(index);
    }

    /**
     * Returns a View for the given index.
     * The order attribute ({@link FlexItem#getOrder()}) is not supported by this class since
     * otherwise all view holders need to be inflated at least once even though only the visible
     * part of the layout is needed.
     * Implementing this method just to make this class conform to the
     * {@link FlexContainer} interface.
     *
     * @param index the index of the view
     * @return the view for the given index.
     * If the index is negative or out of bounds of the number of contained views,
     * returns {@code null}.
     */
    @Override
    public View getReorderedFlexItemAt(int index) {
        return getFlexItemAt(index);
    }

    @Override
    public void onNewFlexLineAdded(FlexLine flexLine) {
        // No op
    }

    @Override
    public int getChildWidthMeasureSpec(int widthSpec, int padding, int childDimension) {
        return getChildMeasureSpec(getWidth(), getWidthMode(), padding, childDimension, canScrollHorizontally());
    }

    @Override
    public int getChildHeightMeasureSpec(int heightSpec, int padding, int childDimension) {
        return getChildMeasureSpec(getHeight(), getHeightMode(), padding, childDimension, canScrollVertically());
    }

    @Override
    public int getLargestMainSize() {
        if (mFlexLines.size() == 0) {
            return 0;
        }
        int largestSize = Integer.MIN_VALUE;
        for (int i = 0, size = mFlexLines.size(); i < size; i++) {
            FlexLine flexLine = mFlexLines.get(i);
            largestSize = Math.max(largestSize, flexLine.mMainSize);
        }
        return largestSize;
    }

    @Override
    public int getSumOfCrossSize() {
        int sum = 0;
        for (int i = 0, size = mFlexLines.size(); i < size; i++) {
            FlexLine flexLine = mFlexLines.get(i);
            // TODO: Consider adding decorator between flex lines.
            sum += flexLine.mCrossSize;
        }
        return sum;
    }

    @Override
    public void setFlexLines(List<FlexLine> flexLines) {
        mFlexLines = flexLines;
    }

    @Override
    public List<FlexLine> getFlexLinesInternal() {
        return mFlexLines;
    }

    @Override
    public void updateViewCache(int position, View view) {
        mViewCache.put(position, view);
    }
    // The end of methods from FlexContainer

    // ScrollVectorProvider method
    @Override
    public PointF computeScrollVectorForPosition(int targetPosition) {
        if (getChildCount() == 0) {
            return null;
        }
        int firstChildPos = getPosition(getChildAt(0));
        int direction = targetPosition < firstChildPos ? -1 : 1;
        if (isMainAxisDirectionHorizontal()) {
            return new PointF(0, direction);
        } else {
            return new PointF(direction, 0);
        }
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
        return new LayoutParams(c, attrs);
    }

    @Override
    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
        return lp instanceof LayoutParams;
    }

    @Override
    public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
        removeAllViews();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        if (mPendingSavedState != null) {
            return new SavedState(mPendingSavedState);
        }
        SavedState savedState = new SavedState();
        if (getChildCount() > 0) {
            // TODO: Find the child from end if mFlexWrap == FlexWrap.WRAP_REVERSE
            View firstView = getChildClosestToStart();
            savedState.mAnchorPosition = getPosition(firstView);
            savedState.mAnchorOffset = mOrientationHelper.getDecoratedStart(firstView)
                    - mOrientationHelper.getStartAfterPadding();
        } else {
            savedState.invalidateAnchor();
        }
        return savedState;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof SavedState) {
            mPendingSavedState = (SavedState) state;
            requestLayout();
            if (DEBUG) {
                Log.d(TAG, "Loaded saved state. " + mPendingSavedState);
            }
        } else {
            if (DEBUG) {
                Log.w(TAG, "Invalid state was trying to be restored. " + state);
            }
        }
    }

    @Override
    public void onItemsAdded(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {
        super.onItemsAdded(recyclerView, positionStart, itemCount);
        updateDirtyPosition(positionStart);
    }

    @Override
    public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, int itemCount,
            Object payload) {
        super.onItemsUpdated(recyclerView, positionStart, itemCount, payload);
        updateDirtyPosition(positionStart);
    }

    @Override
    public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {
        super.onItemsUpdated(recyclerView, positionStart, itemCount);
        updateDirtyPosition(positionStart);
    }

    @Override
    public void onItemsRemoved(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {
        super.onItemsRemoved(recyclerView, positionStart, itemCount);
        updateDirtyPosition(positionStart);
    }

    @Override
    public void onItemsMoved(@NonNull RecyclerView recyclerView, int from, int to, int itemCount) {
        super.onItemsMoved(recyclerView, from, to, itemCount);
        updateDirtyPosition(Math.min(from, to));
    }

    private void updateDirtyPosition(int positionStart) {
        int lastVisiblePosition = findLastVisibleItemPosition();
        if (positionStart >= lastVisiblePosition) {
            return;
        }
        int childCount = getChildCount();
        mFlexboxHelper.ensureMeasureSpecCache(childCount);
        mFlexboxHelper.ensureMeasuredSizeCache(childCount);
        mFlexboxHelper.ensureIndexToFlexLine(childCount);
        assert mFlexboxHelper.mIndexToFlexLine != null;

        if (positionStart >= mFlexboxHelper.mIndexToFlexLine.length) {
            return;
        }

        mDirtyPosition = positionStart;

        View firstView = getChildClosestToStart();
        if (firstView == null) {
            return;
        }

        // Assign the pending scroll position and offset so that the first visible position is
        // restored in the next layout.
        mPendingScrollPosition = getPosition(firstView);

        if (!isMainAxisDirectionHorizontal() && mIsRtl) {
            mPendingScrollPositionOffset = mOrientationHelper.getDecoratedEnd(firstView)
                    + mOrientationHelper.getEndPadding();
        } else {
            mPendingScrollPositionOffset = mOrientationHelper.getDecoratedStart(firstView)
                    - mOrientationHelper.getStartAfterPadding();
        }
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // Layout algorithm:
        // 1) Find an anchor coordinate and anchor flex line position. If not found, the coordinate
        //    starts from zero.
        // 2) From the anchor position to the visible area, calculate the flex lines that needs to
        //    be filled.
        // 3) Fill toward end from the anchor position
        // 4) Fill toward start from the anchor position
        if (DEBUG) {
            Log.d(TAG, "onLayoutChildren started");
            Log.d(TAG, "getChildCount: " + getChildCount());
            Log.d(TAG, "State: " + state);
            Log.d(TAG, "PendingSavedState: " + mPendingSavedState);
            Log.d(TAG, "PendingScrollPosition: " + mPendingScrollPosition);
            Log.d(TAG, "PendingScrollOffset: " + mPendingScrollPositionOffset);
        }

        // Assign the Recycler and the State as the member variables so that
        // the method from FlexContainer (such as getFlexItemCount()) returns the number of
        // flex items from the adapter not the child count in the LayoutManager because
        // LayoutManager#getChildCount doesn't include the views that are detached or scrapped.
        mRecycler = recycler;
        mState = state;
        int childCount = state.getItemCount();
        if (childCount == 0 && state.isPreLayout()) {
            return;
        }
        resolveLayoutDirection();
        ensureOrientationHelper();
        ensureLayoutState();
        mFlexboxHelper.ensureMeasureSpecCache(childCount);
        mFlexboxHelper.ensureMeasuredSizeCache(childCount);

        mFlexboxHelper.ensureIndexToFlexLine(childCount);

        mLayoutState.mShouldRecycle = false;

        if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor(childCount)) {
            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
        }

        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || mPendingSavedState != null) {
            mAnchorInfo.reset();
            updateAnchorInfoForLayout(state, mAnchorInfo);
            mAnchorInfo.mValid = true;
        }
        detachAndScrapAttachedViews(recycler);

        if (mAnchorInfo.mLayoutFromEnd) {
            updateLayoutStateToFillStart(mAnchorInfo, false, true);
        } else {
            updateLayoutStateToFillEnd(mAnchorInfo, false, true);
        }
        if (DEBUG) {
            Log.d(TAG, String.format("onLayoutChildren. recycler.getScrapList.size(): %s, state: %s",
                    recycler.getScrapList().size(), state));
        }

        updateFlexLines(childCount);
        if (DEBUG) {
            for (int i = 0, size = mFlexLines.size(); i < size; i++) {
                FlexLine flexLine = mFlexLines.get(i);
                Log.d(TAG, String.format("%d flex line. MainSize: %d, CrossSize: %d, itemCount: %d", i,
                        flexLine.getMainSize(), flexLine.getCrossSize(), flexLine.getItemCount()));
            }
        }

        int startOffset;
        int endOffset;
        if (mAnchorInfo.mLayoutFromEnd) {
            int filledToEnd = fill(recycler, state, mLayoutState);
            if (DEBUG) {
                Log.d(TAG, String.format("filled: %d toward start", filledToEnd));
            }
            startOffset = mLayoutState.mOffset;
            updateLayoutStateToFillEnd(mAnchorInfo, true, false);
            int filledToStart = fill(recycler, state, mLayoutState);
            if (DEBUG) {
                Log.d(TAG, String.format("filled: %d toward end", filledToStart));
            }
            endOffset = mLayoutState.mOffset;
        } else {
            int filledToEnd = fill(recycler, state, mLayoutState);
            if (DEBUG) {
                Log.d(TAG, String.format("filled: %d toward end", filledToEnd));
            }
            endOffset = mLayoutState.mOffset;
            updateLayoutStateToFillStart(mAnchorInfo, true, false);
            int filledToStart = fill(recycler, state, mLayoutState);
            if (DEBUG) {
                Log.d(TAG, String.format("filled: %d toward start", filledToStart));
            }
            startOffset = mLayoutState.mOffset;
        }

        if (getChildCount() > 0) {
            if (mAnchorInfo.mLayoutFromEnd) {
                int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
                startOffset += fixOffset;
                fixLayoutStartGap(startOffset, recycler, state, false);
            } else {
                int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
                endOffset += fixOffset;
                fixLayoutEndGap(endOffset, recycler, state, false);
            }
        }
    }

    /**
     * Fill the gap the toward the start position if the gap to be filled is made.
     * Large part is copied from LinearLayoutManager#fixLayoutStartGap.
     */
    private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler, RecyclerView.State state,
            boolean canOffsetChildren) {
        int gap;
        int fixOffset;
        if (!isMainAxisDirectionHorizontal() && mIsRtl) {
            gap = mOrientationHelper.getEndAfterPadding() - startOffset;
            if (gap > 0) {
                // check if we should fix this gap.
                fixOffset = handleScrollingCrossAxis(-gap, recycler, state);
            } else {
                return 0; // nothing to fix
            }
        } else {
            gap = startOffset - mOrientationHelper.getStartAfterPadding();
            if (gap > 0) {
                // check if we should fix this gap.
                fixOffset = -handleScrollingCrossAxis(gap, recycler, state);
            } else {
                return 0; // nothing to fix
            }
        }
        startOffset += fixOffset;
        if (canOffsetChildren) {
            // re-calculate gap, see if we could fix it
            gap = startOffset - mOrientationHelper.getStartAfterPadding();
            if (gap > 0) {
                mOrientationHelper.offsetChildren(-gap);
                return fixOffset - gap;
            }
        }
        return fixOffset;
    }

    /**
     * Fill the gap the toward the end position if the gap to be filled is made.
     * This process is necessary in a case like {@link #scrollToPosition(int)} is called
     * for the last item, otherwise the last item is placed as the first line.
     * Large part is copied from LinearLayoutManager#fixLayoutEndGap.
     */
    private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler, RecyclerView.State state,
            boolean canOffsetChildren) {
        int gap;
        boolean columnAndRtl = !isMainAxisDirectionHorizontal() && mIsRtl;
        int fixOffset;
        if (columnAndRtl) {
            gap = endOffset - mOrientationHelper.getStartAfterPadding();
            if (gap > 0) {
                fixOffset = handleScrollingCrossAxis(gap, recycler, state);
            } else {
                return 0; // nothing to fix
            }
        } else {
            gap = mOrientationHelper.getEndAfterPadding() - endOffset;
            if (gap > 0) {
                fixOffset = -handleScrollingCrossAxis(-gap, recycler, state);
            } else {
                return 0; // nothing to fix
            }
        }

        // move offset according to scroll amount
        endOffset += fixOffset;
        if (canOffsetChildren) {
            // re-calculate gap, see if we could fix it
            gap = mOrientationHelper.getEndAfterPadding() - endOffset;
            if (gap > 0) {
                mOrientationHelper.offsetChildren(gap);
                return gap + fixOffset;
            }
        }
        return fixOffset;
    }

    private void updateFlexLines(int childCount) {
        //noinspection ResourceType
        int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), getWidthMode());
        //noinspection ResourceType
        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(getHeight(), getHeightMode());
        int width = getWidth();
        int height = getHeight();
        boolean isMainSizeChanged;
        int needsToFill;
        // Clear the flex lines if the main size has changed from the last measurement.
        // For example this happens when the developer handles the configuration changes manually
        // or the user change the width boundary in the multi window mode.
        if (isMainAxisDirectionHorizontal()) {
            isMainSizeChanged = mLastWidth != Integer.MIN_VALUE && mLastWidth != width;

            // If the mInfinite flag is set to true (that usually happens when RecyclerViews are
            // nested and inner RecyclerView's layout_height is set to wrap_content, thus height is
            // passed as 0 from the RecyclerView)
            // Set the upper limit as the height of the device in order to prevent computing all
            // items in the adapter
            needsToFill = mLayoutState.mInfinite ? mContext.getResources().getDisplayMetrics().heightPixels
                    : mLayoutState.mAvailable;
        } else {
            isMainSizeChanged = mLastHeight != Integer.MIN_VALUE && mLastHeight != height;

            // If the mInfinite flag is set to true (that usually happens when RecyclerViews are
            // nested and inner RecyclerView's layout_width is set to wrap_content, thus width is
            // passed as 0 from the RecyclerView)
            // Set the upper limit as the width of the device in order to prevent computing all
            // items in the adapter
            needsToFill = mLayoutState.mInfinite ? mContext.getResources().getDisplayMetrics().widthPixels
                    : mLayoutState.mAvailable;
        }

        mLastWidth = width;
        mLastHeight = height;

        if (mDirtyPosition == NO_POSITION && (mPendingScrollPosition != NO_POSITION || isMainSizeChanged)) {
            if (mAnchorInfo.mLayoutFromEnd) {
                // Prior flex lines should be already calculated, don't have to be updated
                return;
            }
            // TODO: This path may need another consideration to not calculate the entire flex
            // lines prior to the anchor position since it may cause noticeable amount of
            // skipped frames.
            // Another note: deciding anchor position for the anchor view assumes that prior
            // flex lines are calculated otherwise the position of the view can't be decided.
            // It may be possible that assumes the anchor view is always at the start of a flex
            // line and calculate the rest of flex lines as user scrolls to the top (toward the
            // start) incrementally, but that approach may lead to inconsistent anchor view
            // position
            mFlexLines.clear();
            assert mFlexboxHelper.mIndexToFlexLine != null;
            mFlexLinesResult.reset();
            if (isMainAxisDirectionHorizontal()) {
                mFlexboxHelper.calculateHorizontalFlexLinesToIndex(mFlexLinesResult, widthMeasureSpec,
                        heightMeasureSpec, needsToFill, mAnchorInfo.mPosition, mFlexLines);
            } else {
                mFlexboxHelper.calculateVerticalFlexLinesToIndex(mFlexLinesResult, widthMeasureSpec,
                        heightMeasureSpec, needsToFill, mAnchorInfo.mPosition, mFlexLines);
            }
            mFlexLines = mFlexLinesResult.mFlexLines;
            mFlexboxHelper.determineMainSize(widthMeasureSpec, heightMeasureSpec);
            mFlexboxHelper.stretchViews();
            mAnchorInfo.mFlexLinePosition = mFlexboxHelper.mIndexToFlexLine[mAnchorInfo.mPosition];
            mLayoutState.mFlexLinePosition = mAnchorInfo.mFlexLinePosition;
        } else {
            // Calculate the flex lines until the calculated cross size reaches the
            // LayoutState#mAvailable (or until the end of the flex container)
            // calculation can be done incrementally because the flex lines prior to the anchor
            // position haven't changed
            int fromIndex = mDirtyPosition != NO_POSITION ? Math.min(mDirtyPosition, mAnchorInfo.mPosition)
                    : mAnchorInfo.mPosition;

            mFlexLinesResult.reset();
            if (isMainAxisDirectionHorizontal()) {
                if (mFlexLines.size() > 0) {
                    // Remove the already calculated flex lines from the fromIndex (either of
                    // anchor position or the position marked as dirty (last time the item was
                    // changed) and calculate beyond the available amount
                    // (visible area that needs to be filled)
                    mFlexboxHelper.clearFlexLines(mFlexLines, fromIndex);
                    mFlexboxHelper.calculateFlexLines(mFlexLinesResult, widthMeasureSpec, heightMeasureSpec,
                            needsToFill, fromIndex, mAnchorInfo.mPosition, mFlexLines);
                } else {
                    mFlexboxHelper.ensureIndexToFlexLine(childCount);
                    mFlexboxHelper.calculateHorizontalFlexLines(mFlexLinesResult, widthMeasureSpec,
                            heightMeasureSpec, needsToFill, 0, mFlexLines);
                }
            } else {
                if (mFlexLines.size() > 0) {
                    // Remove the already calculated flex lines from the fromIndex (either of
                    // anchor position or the position marked as dirty (last time the item was
                    // changed) and calculate beyond the available amount
                    // (visible area that needs to be filled)
                    mFlexboxHelper.clearFlexLines(mFlexLines, fromIndex);
                    mFlexboxHelper.calculateFlexLines(mFlexLinesResult, heightMeasureSpec, widthMeasureSpec,
                            needsToFill, fromIndex, mAnchorInfo.mPosition, mFlexLines);
                } else {
                    mFlexboxHelper.ensureIndexToFlexLine(childCount);
                    mFlexboxHelper.calculateVerticalFlexLines(mFlexLinesResult, widthMeasureSpec, heightMeasureSpec,
                            needsToFill, 0, mFlexLines);
                }
            }
            mFlexLines = mFlexLinesResult.mFlexLines;
            mFlexboxHelper.determineMainSize(widthMeasureSpec, heightMeasureSpec, fromIndex);
            // Unlike the FlexboxLayout not calling FlexboxHelper#determineCrossSize because
            // the align content attribute (which is used to determine the cross size) is only
            // effective
            // when the size of flex line is equal or more than 2 and the parent height
            // (length along the cross size) is fixed. But in RecyclerView, these two conditions
            // can't
            // be true at the same time. Because it's scrollable along the cross axis
            // or even if not (when flex wrap is "nowrap") the size of the flex lines should be 1.
            mFlexboxHelper.stretchViews(fromIndex);
        }
    }

    @Override
    public void onLayoutCompleted(RecyclerView.State state) {
        super.onLayoutCompleted(state);
        if (DEBUG) {
            Log.d(TAG, "onLayoutCompleted. " + state);
        }
        mPendingSavedState = null;
        mPendingScrollPosition = NO_POSITION;
        mPendingScrollPositionOffset = INVALID_OFFSET;
        mDirtyPosition = NO_POSITION;
        mAnchorInfo.reset();
        mViewCache.clear();
    }

    boolean isLayoutRtl() {
        return mIsRtl;
    }

    private void resolveLayoutDirection() {
        int layoutDirection = getLayoutDirection();
        switch (mFlexDirection) {
        case FlexDirection.ROW:
            mIsRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
            mFromBottomToTop = mFlexWrap == FlexWrap.WRAP_REVERSE;
            break;
        case FlexDirection.ROW_REVERSE:
            mIsRtl = layoutDirection != ViewCompat.LAYOUT_DIRECTION_RTL;
            mFromBottomToTop = mFlexWrap == FlexWrap.WRAP_REVERSE;
            break;
        case FlexDirection.COLUMN:
            mIsRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
            if (mFlexWrap == FlexWrap.WRAP_REVERSE) {
                mIsRtl = !mIsRtl;
            }
            mFromBottomToTop = false;
            break;
        case FlexDirection.COLUMN_REVERSE:
            mIsRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
            if (mFlexWrap == FlexWrap.WRAP_REVERSE) {
                mIsRtl = !mIsRtl;
            }
            mFromBottomToTop = true;
            break;
        default:
            mIsRtl = false;
            mFromBottomToTop = false;
        }
    }

    private void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) {
        if (updateAnchorFromPendingState(state, anchorInfo, mPendingSavedState)) {
            if (DEBUG) {
                Log.d(TAG, "updated anchor from the pending state");
            }
            return;
        }
        if (updateAnchorFromChildren(state, anchorInfo)) {
            if (DEBUG) {
                Log.d(TAG, String.format("updated anchor info from existing children. AnchorInfo: %s", anchorInfo));
            }
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "deciding anchor info for fresh state");
        }
        anchorInfo.assignCoordinateFromPadding();
        anchorInfo.mPosition = 0;
        anchorInfo.mFlexLinePosition = 0;
    }

    private boolean updateAnchorFromPendingState(RecyclerView.State state, AnchorInfo anchorInfo,
            SavedState savedState) {
        assert mFlexboxHelper.mIndexToFlexLine != null;
        if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
            return false;
        }
        if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
            mPendingScrollPosition = NO_POSITION;
            mPendingScrollPositionOffset = INVALID_OFFSET;
            if (DEBUG) {
                Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition);
            }
            return false;
        }

        anchorInfo.mPosition = mPendingScrollPosition;
        anchorInfo.mFlexLinePosition = mFlexboxHelper.mIndexToFlexLine[anchorInfo.mPosition];
        if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor(state.getItemCount())) {
            anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() + savedState.mAnchorOffset;
            anchorInfo.mAssignedFromSavedState = true;
            anchorInfo.mFlexLinePosition = NO_POSITION;
            return true;
        }

        if (mPendingScrollPositionOffset == INVALID_OFFSET) {
            View anchorView = findViewByPosition(mPendingScrollPosition);
            if (anchorView != null) {
                if (mOrientationHelper.getDecoratedMeasurement(anchorView) > mOrientationHelper.getTotalSpace()) {
                    anchorInfo.assignCoordinateFromPadding();
                    return true;
                }
                int startGap = mOrientationHelper.getDecoratedStart(anchorView)
                        - mOrientationHelper.getStartAfterPadding();
                if (startGap < 0) {
                    anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
                    anchorInfo.mLayoutFromEnd = false;
                    return true;
                }

                int endGap = mOrientationHelper.getEndAfterPadding()
                        - mOrientationHelper.getDecoratedEnd(anchorView);
                if (endGap < 0) {
                    anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
                    anchorInfo.mLayoutFromEnd = true;
                    return true;
                }
                anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
                        ? (mOrientationHelper.getDecoratedEnd(anchorView)
                                + mOrientationHelper.getTotalSpaceChange())
                        : mOrientationHelper.getDecoratedStart(anchorView);
            } else {
                if (getChildCount() > 0) {
                    int position = getPosition(getChildAt(0));
                    anchorInfo.mLayoutFromEnd = mPendingScrollPosition < position;
                }
                anchorInfo.assignCoordinateFromPadding();
            }
            return true;
        }

        // TODO: Support reverse layout when flex wrap == FlexWrap.WRAP_REVERSE
        if (!isMainAxisDirectionHorizontal() && mIsRtl) {
            anchorInfo.mCoordinate = mPendingScrollPositionOffset - mOrientationHelper.getEndPadding();
        } else {
            anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() + mPendingScrollPositionOffset;
        }
        return true;
    }

    /**
     * Finds an anchor child from existing Views. Most of the time, this is the view closest to
     * start or end that has a valid position (e.g. not removed).
     * Large part refers to the same method from LinearLayout#updateAnchorFromChildren
     */
    private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) {
        if (getChildCount() == 0) {
            return false;
        }
        // TODO: Consider the focused view

        View referenceChild = anchorInfo.mLayoutFromEnd ? findLastReferenceChild(state.getItemCount())
                : findFirstReferenceChild(state.getItemCount());
        if (referenceChild != null) {
            anchorInfo.assignFromView(referenceChild);
            // If all visible views are removed in 1 pass, reference child might be out of bounds.
            // If that is the case, offset it back to 0 so that we use these pre-layout children.
            if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
                // validate this child is at least partially visible. if not, offset it to start
                final boolean notVisible = mOrientationHelper
                        .getDecoratedStart(referenceChild) >= mOrientationHelper.getEndAfterPadding()
                        || mOrientationHelper.getDecoratedEnd(referenceChild) < mOrientationHelper
                                .getStartAfterPadding();
                if (notVisible) {
                    anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd ? mOrientationHelper.getEndAfterPadding()
                            : mOrientationHelper.getStartAfterPadding();
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Find the reference view to be used as an anchor. It tries to find the view who has the
     * maximum/minimum start/end (differs depending on if the container if RTL and the main axis
     * direction) coordinate in the first visible flex line.
     *
     * @param itemCount the number of the items in this layout including invisible items
     * @return the reference view
     */
    private View findFirstReferenceChild(int itemCount) {
        assert mFlexboxHelper.mIndexToFlexLine != null;
        View firstFound = findReferenceChild(0, getChildCount(), itemCount);
        if (firstFound == null) {
            return null;
        }
        int firstFoundPosition = getPosition(firstFound);
        int firstFoundLinePosition = mFlexboxHelper.mIndexToFlexLine[firstFoundPosition];
        if (firstFoundLinePosition == NO_POSITION) {
            return null;
        }
        FlexLine firstFoundLine = mFlexLines.get(firstFoundLinePosition);
        return findFirstReferenceViewInLine(firstFound, firstFoundLine);
    }

    /**
     * Find the reference view to be used as an anchor. It tries to find the view who has the
     * maximum/minimum start/end (differs depending on if the container if RTL and the main axis
     * direction) coordinate in the last visible flex line.
     *
     * @param itemCount the number of the items in this layout including invisible items
     * @return the reference view
     */
    private View findLastReferenceChild(int itemCount) {
        assert mFlexboxHelper.mIndexToFlexLine != null;
        View lastFound = findReferenceChild(getChildCount() - 1, -1, itemCount);
        if (lastFound == null) {
            return null;
        }
        int lastFoundPosition = getPosition(lastFound);
        int lastFoundLinePosition = mFlexboxHelper.mIndexToFlexLine[lastFoundPosition];
        FlexLine lastFoundLine = mFlexLines.get(lastFoundLinePosition);
        return findLastReferenceViewInLine(lastFound, lastFoundLine);
    }

    /**
     * Find a visible (or less preferred invisible) view within the given start and end index.
     * Large part refers to the same method in LinearLayoutManager#findReferenceChild
     *
     * @param start     the start index within the range to find a view
     * @param end       the end index within the range to find a view
     * @param itemCount the item count
     * @return the found view within the range of the given start and
     */
    private View findReferenceChild(int start, int end, int itemCount) {
        ensureOrientationHelper();
        ensureLayoutState();
        View invalidMatch = null;
        View outOfBoundsMatch = null;
        int boundStart = mOrientationHelper.getStartAfterPadding();
        int boundEnd = mOrientationHelper.getEndAfterPadding();
        int diff = end > start ? 1 : -1;
        for (int i = start; i != end; i += diff) {
            View view = getChildAt(i);
            int position = getPosition(view);
            if (position >= 0 && position < itemCount) {
                if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
                    if (invalidMatch == null) {
                        invalidMatch = view;
                    }
                } else if (mOrientationHelper.getDecoratedStart(view) < boundStart
                        || mOrientationHelper.getDecoratedEnd(view) > boundEnd) {
                    if (outOfBoundsMatch == null) {
                        outOfBoundsMatch = view;
                    }
                } else {
                    return view;
                }
            }
        }
        return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
    }

    private View getChildClosestToStart() {
        // TODO: Find from end when mFlexWrap == FlexWrap.WRAP_REVERSE
        return getChildAt(0);
    }

    /**
     * Fills the remaining space defined by the layoutState on
     * how many pixels should be filled (defined by {@link LayoutState#mAvailable}.
     * The large part refers to the LinearLayoutManager#fill method except for the fill direction.
     * Because FlexboxLayoutManager needs to care two scrolling directions:
     * <li>
     * <ul>Along the cross axis - When flex wrap is set to either FlexWrap.WRAP or
     * FlexWrap.WRAP_REVERSE, the layout needs to scroll along the cross axis.</ul>
     * <ul>Along the main axis - When flex wrap is set t FlexWrap.NOWRAP, the layout needs
     * to scroll along the main axis if there are overflowing flex items.</ul>
     * </li>
     *
     * @return the amount of pixels filled
     */
    private int fill(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState) {
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        int start = layoutState.mAvailable;
        int remainingSpace = layoutState.mAvailable;
        int consumed = 0;
        boolean mainAxisHorizontal = isMainAxisDirectionHorizontal();
        while ((remainingSpace > 0 || mLayoutState.mInfinite) && layoutState.hasMore(state, mFlexLines)) {
            FlexLine flexLine = mFlexLines.get(layoutState.mFlexLinePosition);
            layoutState.mPosition = flexLine.mFirstIndex;
            consumed += layoutFlexLine(flexLine, layoutState);

            if (!mainAxisHorizontal && mIsRtl) {
                layoutState.mOffset -= flexLine.getCrossSize() * layoutState.mLayoutDirection;
            } else {
                layoutState.mOffset += flexLine.getCrossSize() * layoutState.mLayoutDirection;
            }

            remainingSpace -= flexLine.getCrossSize();
        }
        layoutState.mAvailable -= consumed;
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += consumed;
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        return start - layoutState.mAvailable;
    }

    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mShouldRecycle) {
            return;
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            // TODO: Consider the case mFlexWrap is set to nowrap and view is recycled individually
            recycleFlexLinesFromEnd(recycler, layoutState);
        } else {
            recycleFlexLinesFromStart(recycler, layoutState);
        }
    }

    private void recycleFlexLinesFromStart(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (layoutState.mScrollingOffset < 0) {
            return;
        }
        assert mFlexboxHelper.mIndexToFlexLine != null;
        int childCount = getChildCount();
        if (childCount == 0) {
            return;
        }
        View firstView = getChildAt(0);

        int currentLineIndex = mFlexboxHelper.mIndexToFlexLine[getPosition(firstView)];
        if (currentLineIndex == NO_POSITION) {
            return;
        }
        FlexLine flexLine = mFlexLines.get(currentLineIndex);
        int recycleTo = -1;
        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            if (canViewBeRecycledFromStart(view, layoutState.mScrollingOffset)) {
                if (flexLine.mLastIndex == getPosition(view)) {
                    // Recycle the views in a flex line if all views end positions are lower than
                    // the scrolling offset because the views are laid out as a flex line unit.
                    // We need to also recycle the views as an unit of a flex line
                    recycleTo = i;
                    if (currentLineIndex >= mFlexLines.size() - 1) {
                        // Reached to the last line
                        break;
                    } else {
                        currentLineIndex += layoutState.mLayoutDirection;
                        flexLine = mFlexLines.get(currentLineIndex);
                    }
                }
            } else {
                break;
            }
        }
        recycleChildren(recycler, 0, recycleTo);
    }

    private boolean canViewBeRecycledFromStart(View view, int scrollingOffset) {
        if (!isMainAxisDirectionHorizontal() && mIsRtl) {
            return mOrientationHelper.getEnd() - mOrientationHelper.getDecoratedStart(view) <= scrollingOffset;
        } else {
            return mOrientationHelper.getDecoratedEnd(view) <= scrollingOffset;
        }
    }

    private void recycleFlexLinesFromEnd(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (layoutState.mScrollingOffset < 0) {
            return;
        }
        assert mFlexboxHelper.mIndexToFlexLine != null;
        int limit = mOrientationHelper.getEnd() - layoutState.mScrollingOffset;
        int childCount = getChildCount();
        if (childCount == 0) {
            return;
        }

        View lastView = getChildAt(childCount - 1);
        int currentLineIndex = mFlexboxHelper.mIndexToFlexLine[getPosition(lastView)];
        if (currentLineIndex == NO_POSITION) {
            return;
        }
        int recycleTo = childCount - 1;
        int recycleFrom = childCount;
        FlexLine flexLine = mFlexLines.get(currentLineIndex);
        for (int i = childCount - 1; i >= 0; i--) {
            View view = getChildAt(i);
            if (canViewBeRecycledFromEnd(view, layoutState.mScrollingOffset)) {
                if (flexLine.mFirstIndex == getPosition(view)) {
                    // Recycle the views in a flex line if all views start positions are beyond the
                    // limit because the views are laid out as a flex line unit. We need to also
                    // recycle the views as an unit of a flex line
                    recycleFrom = i;
                    if (currentLineIndex <= 0) {
                        // Reached to the first flex line
                        break;
                    } else {
                        currentLineIndex += layoutState.mLayoutDirection;
                        flexLine = mFlexLines.get(currentLineIndex);
                    }
                }
            } else {
                break;
            }
        }
        recycleChildren(recycler, recycleFrom, recycleTo);
    }

    private boolean canViewBeRecycledFromEnd(View view, int scrollingOffset) {
        if (!isMainAxisDirectionHorizontal() && mIsRtl) {
            return mOrientationHelper.getDecoratedEnd(view) <= scrollingOffset;
        } else {
            return mOrientationHelper.getDecoratedStart(view) >= mOrientationHelper.getEnd() - scrollingOffset;
        }
    }

    /**
     * Recycles children between given indices.
     *
     * @param startIndex inclusive
     * @param endIndex   inclusive
     */
    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        for (int i = endIndex; i >= startIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    }

    private int layoutFlexLine(FlexLine flexLine, LayoutState layoutState) {
        if (isMainAxisDirectionHorizontal()) {
            return layoutFlexLineMainAxisHorizontal(flexLine, layoutState);
        } else {
            return layoutFlexLineMainAxisVertical(flexLine, layoutState);
        }
    }

    private int layoutFlexLineMainAxisHorizontal(FlexLine flexLine, LayoutState layoutState) {
        assert mFlexboxHelper.mMeasureSpecCache != null;

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int parentWidth = getWidth();

        int childTop = layoutState.mOffset;
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            childTop = childTop - flexLine.mCrossSize;
        }
        int startPosition = layoutState.mPosition;

        float childLeft;

        // Only used when mIsRtl is true
        float childRight;
        float spaceBetweenItem = 0f;
        switch (mJustifyContent) {
        case JustifyContent.FLEX_START:
            childLeft = paddingLeft;
            childRight = parentWidth - paddingRight;
            break;
        case JustifyContent.FLEX_END:
            childLeft = parentWidth - flexLine.mMainSize + paddingRight;
            childRight = flexLine.mMainSize - paddingLeft;
            break;
        case JustifyContent.CENTER:
            childLeft = paddingLeft + (parentWidth - flexLine.mMainSize) / 2f;
            childRight = parentWidth - paddingRight - (parentWidth - flexLine.mMainSize) / 2f;
            break;
        case JustifyContent.SPACE_AROUND:
            if (flexLine.mItemCount != 0) {
                spaceBetweenItem = (parentWidth - flexLine.mMainSize) / (float) flexLine.mItemCount;
            }
            childLeft = paddingLeft + spaceBetweenItem / 2f;
            childRight = parentWidth - paddingRight - spaceBetweenItem / 2f;
            break;
        case JustifyContent.SPACE_BETWEEN:
            childLeft = paddingLeft;
            float denominator = flexLine.mItemCount != 1 ? flexLine.mItemCount - 1 : 1f;
            spaceBetweenItem = (parentWidth - flexLine.mMainSize) / denominator;
            childRight = parentWidth - paddingRight;
            break;
        case JustifyContent.SPACE_EVENLY:
            if (flexLine.mItemCount != 0) {
                spaceBetweenItem = (parentWidth - flexLine.mMainSize) / (float) (flexLine.mItemCount + 1);
            }
            childLeft = paddingLeft + spaceBetweenItem;
            childRight = parentWidth - paddingRight - spaceBetweenItem;
            break;
        default:
            throw new IllegalStateException("Invalid justifyContent is set: " + mJustifyContent);
        }
        childLeft -= mAnchorInfo.mPerpendicularCoordinate;
        childRight -= mAnchorInfo.mPerpendicularCoordinate;
        spaceBetweenItem = Math.max(spaceBetweenItem, 0);

        // Used only when mLayoutDirection == LayoutState.LAYOUT_START to remember the index
        // a flex item should be inserted
        int indexInFlexLine = 0;
        for (int i = startPosition, itemCount = flexLine.getItemCount(); i < startPosition + itemCount; i++) {
            View view = getFlexItemAt(i);
            if (view == null) {
                continue;
            }

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
                calculateItemDecorationsForChild(view, TEMP_RECT);
                addView(view);
            } else {
                calculateItemDecorationsForChild(view, TEMP_RECT);
                addView(view, indexInFlexLine);
                indexInFlexLine++;
            }

            // Retrieve the measure spec from the cache because the view may be re-created when
            // retrieved from Recycler, in that case measured width/height are set to 0 even
            // each visible child should be measured at least once in the FlexboxHelper
            long measureSpec = mFlexboxHelper.mMeasureSpecCache[i];
            int widthSpec = mFlexboxHelper.extractLowerInt(measureSpec);
            int heightSpec = mFlexboxHelper.extractHigherInt(measureSpec);
            LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (shouldMeasureChild(view, widthSpec, heightSpec, lp)) {
                view.measure(widthSpec, heightSpec);
            }

            childLeft += (lp.leftMargin + getLeftDecorationWidth(view));
            childRight -= (lp.rightMargin + getRightDecorationWidth(view));

            int topWithDecoration = childTop + getTopDecorationHeight(view);
            if (mIsRtl) {
                mFlexboxHelper.layoutSingleChildHorizontal(view, flexLine,
                        Math.round(childRight) - view.getMeasuredWidth(), topWithDecoration, Math.round(childRight),
                        topWithDecoration + view.getMeasuredHeight());
            } else {
                mFlexboxHelper.layoutSingleChildHorizontal(view, flexLine, Math.round(childLeft), topWithDecoration,
                        Math.round(childLeft) + view.getMeasuredWidth(),
                        topWithDecoration + view.getMeasuredHeight());
            }
            childLeft += (view.getMeasuredWidth() + lp.rightMargin + getRightDecorationWidth(view)
                    + spaceBetweenItem);
            childRight -= (view.getMeasuredWidth() + lp.leftMargin + getLeftDecorationWidth(view)
                    + spaceBetweenItem);
        }
        layoutState.mFlexLinePosition += mLayoutState.mLayoutDirection;
        return flexLine.getCrossSize();
    }

    private int layoutFlexLineMainAxisVertical(FlexLine flexLine, LayoutState layoutState) {
        assert mFlexboxHelper.mMeasureSpecCache != null;

        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        int parentHeight = getHeight();

        int childLeft = layoutState.mOffset;
        // childRight is used only for the layout is RTL
        int childRight = layoutState.mOffset;
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            childLeft = childLeft - flexLine.mCrossSize;
            childRight = childRight + flexLine.mCrossSize;
        }
        int startPosition = layoutState.mPosition;

        float childTop;

        // Only used when mFromBottomToTop is true
        float childBottom;
        float spaceBetweenItem = 0f;
        switch (mJustifyContent) {
        case JustifyContent.FLEX_START:
            childTop = paddingTop;
            childBottom = parentHeight - paddingBottom;
            break;
        case JustifyContent.FLEX_END:
            childTop = parentHeight - flexLine.mMainSize + paddingBottom;
            childBottom = flexLine.mMainSize - paddingTop;
            break;
        case JustifyContent.CENTER:
            childTop = paddingTop + (parentHeight - flexLine.mMainSize) / 2f;
            childBottom = parentHeight - paddingBottom - (parentHeight - flexLine.mMainSize) / 2f;
            break;
        case JustifyContent.SPACE_AROUND:
            if (flexLine.mItemCount != 0) {
                spaceBetweenItem = (parentHeight - flexLine.mMainSize) / (float) flexLine.mItemCount;
            }
            childTop = paddingTop + spaceBetweenItem / 2f;
            childBottom = parentHeight - paddingBottom - spaceBetweenItem / 2f;
            break;
        case JustifyContent.SPACE_BETWEEN:
            childTop = paddingTop;
            float denominator = flexLine.mItemCount != 1 ? flexLine.mItemCount - 1 : 1f;
            spaceBetweenItem = (parentHeight - flexLine.mMainSize) / denominator;
            childBottom = parentHeight - paddingBottom;
            break;
        case JustifyContent.SPACE_EVENLY:
            if (flexLine.mItemCount != 0) {
                spaceBetweenItem = (parentHeight - flexLine.mMainSize) / (float) (flexLine.mItemCount + 1);
            }
            childTop = paddingTop + spaceBetweenItem;
            childBottom = parentHeight - paddingBottom - spaceBetweenItem;
            break;
        default:
            throw new IllegalStateException("Invalid justifyContent is set: " + mJustifyContent);
        }
        childTop -= mAnchorInfo.mPerpendicularCoordinate;
        childBottom -= mAnchorInfo.mPerpendicularCoordinate;
        spaceBetweenItem = Math.max(spaceBetweenItem, 0);

        // Used only when mLayoutDirection == LayoutState.LAYOUT_START to remember the index
        // a flex item should be inserted
        int indexInFlexLine = 0;
        for (int i = startPosition, itemCount = flexLine.getItemCount(); i < startPosition + itemCount; i++) {
            View view = getFlexItemAt(i);
            if (view == null) {
                continue;
            }

            // Retrieve the measure spec from the cache because the view may be re-created when
            // retrieved from Recycler, in that case measured width/height are set to 0 even
            // each visible child should be measured at least once in the FlexboxHelper
            long measureSpec = mFlexboxHelper.mMeasureSpecCache[i];
            int widthSpec = mFlexboxHelper.extractLowerInt(measureSpec);
            int heightSpec = mFlexboxHelper.extractHigherInt(measureSpec);
            LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (shouldMeasureChild(view, widthSpec, heightSpec, lp)) {
                view.measure(widthSpec, heightSpec);
            }

            childTop += (lp.topMargin + getTopDecorationHeight(view));
            childBottom -= (lp.rightMargin + getBottomDecorationHeight(view));

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
                calculateItemDecorationsForChild(view, TEMP_RECT);
                addView(view);
            } else {
                calculateItemDecorationsForChild(view, TEMP_RECT);
                addView(view, indexInFlexLine);
                indexInFlexLine++;
            }

            int leftWithDecoration = childLeft + getLeftDecorationWidth(view);
            int rightWithDecoration = childRight - getRightDecorationWidth(view);
            if (mIsRtl) {
                if (mFromBottomToTop) {
                    mFlexboxHelper.layoutSingleChildVertical(view, flexLine, mIsRtl,
                            rightWithDecoration - view.getMeasuredWidth(),
                            Math.round(childBottom) - view.getMeasuredHeight(), rightWithDecoration,
                            Math.round(childBottom));
                } else {
                    mFlexboxHelper.layoutSingleChildVertical(view, flexLine, mIsRtl,
                            rightWithDecoration - view.getMeasuredWidth(), Math.round(childTop),
                            rightWithDecoration, Math.round(childTop) + view.getMeasuredHeight());
                }
            } else {
                if (mFromBottomToTop) {
                    mFlexboxHelper.layoutSingleChildVertical(view, flexLine, mIsRtl, leftWithDecoration,
                            Math.round(childBottom) - view.getMeasuredHeight(),
                            leftWithDecoration + view.getMeasuredWidth(), Math.round(childBottom));
                } else {
                    mFlexboxHelper.layoutSingleChildVertical(view, flexLine, mIsRtl, leftWithDecoration,
                            Math.round(childTop), leftWithDecoration + view.getMeasuredWidth(),
                            Math.round(childTop) + view.getMeasuredHeight());
                }
            }
            childTop += (view.getMeasuredHeight() + lp.topMargin + getBottomDecorationHeight(view)
                    + spaceBetweenItem);
            childBottom -= (view.getMeasuredHeight() + lp.bottomMargin + getTopDecorationHeight(view)
                    + spaceBetweenItem);
        }
        layoutState.mFlexLinePosition += mLayoutState.mLayoutDirection;
        return flexLine.getCrossSize();
    }

    @Override
    public boolean isMainAxisDirectionHorizontal() {
        return mFlexDirection == FlexDirection.ROW || mFlexDirection == FlexDirection.ROW_REVERSE;
    }

    /**
     * Update the layout state based on the anchor information.
     * The view holders are going to be filled toward the end position (bottom if the main axis
     * direction is horizontal, right if the main axis direction if vertical).
     *
     * @param anchorInfo       the anchor information where layout should start
     * @param fromNextLine     if set to {@code true}, layout starts from the next flex line set to
     *                         the anchor information
     * @param considerInfinite if set to {@code true}, the judgement if the infinite available
     *                         space
     *                         needs to be considered.
     */
    private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo, boolean fromNextLine, boolean considerInfinite) {
        if (considerInfinite) {
            resolveInfiniteAmount();
        } else {
            mLayoutState.mInfinite = false;
        }
        if (!isMainAxisDirectionHorizontal() && mIsRtl) {
            mLayoutState.mAvailable = anchorInfo.mCoordinate - getPaddingRight();
        } else {
            mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - anchorInfo.mCoordinate;
        }
        mLayoutState.mPosition = anchorInfo.mPosition;
        mLayoutState.mItemDirection = LayoutState.ITEM_DIRECTION_TAIL;
        mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
        mLayoutState.mOffset = anchorInfo.mCoordinate;
        mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
        mLayoutState.mFlexLinePosition = anchorInfo.mFlexLinePosition;

        if (fromNextLine && mFlexLines.size() > 1 && anchorInfo.mFlexLinePosition >= 0
                && anchorInfo.mFlexLinePosition < mFlexLines.size() - 1) {
            FlexLine currentLine = mFlexLines.get(anchorInfo.mFlexLinePosition);
            mLayoutState.mFlexLinePosition++;
            mLayoutState.mPosition += currentLine.getItemCount();
        }
    }

    /**
     * Update the layout state based on the anchor information.
     * The view holders are going to be filled toward the start position (top if the main axis
     * direction is horizontal, left if the main axis direction if vertical).
     *
     * @param anchorInfo       the anchor information where layout should start
     * @param fromPreviousLine if set to {@code true}, layout starts from the next flex line set to
     *                         the anchor information
     * @param considerInfinite if set to {@code true}, the judgement if the infinite available
     *                         space
     *                         needs to be considered.
     */
    private void updateLayoutStateToFillStart(AnchorInfo anchorInfo, boolean fromPreviousLine,
            boolean considerInfinite) {
        if (considerInfinite) {
            resolveInfiniteAmount();
        } else {
            mLayoutState.mInfinite = false;
        }
        if (!isMainAxisDirectionHorizontal() && mIsRtl) {
            mLayoutState.mAvailable = mParent.getWidth() - anchorInfo.mCoordinate
                    - mOrientationHelper.getStartAfterPadding();
        } else {
            mLayoutState.mAvailable = anchorInfo.mCoordinate - mOrientationHelper.getStartAfterPadding();
        }
        mLayoutState.mPosition = anchorInfo.mPosition;
        mLayoutState.mItemDirection = LayoutState.ITEM_DIRECTION_TAIL;
        mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START;
        mLayoutState.mOffset = anchorInfo.mCoordinate;
        mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
        mLayoutState.mFlexLinePosition = anchorInfo.mFlexLinePosition;

        if (fromPreviousLine && anchorInfo.mFlexLinePosition > 0
                && mFlexLines.size() > anchorInfo.mFlexLinePosition) {
            FlexLine currentLine = mFlexLines.get(anchorInfo.mFlexLinePosition);
            mLayoutState.mFlexLinePosition--;
            mLayoutState.mPosition -= currentLine.getItemCount();
        }
    }

    private void resolveInfiniteAmount() {
        int crossMode;
        if (isMainAxisDirectionHorizontal()) {
            crossMode = getHeightMode();
        } else {
            crossMode = getWidthMode();
        }
        // Setting the infinite flag so that the LayoutManager tries to fill the available space
        // as much as possible. E.g. this is needed in the case RecyclerView is wrapped with another
        // scrollable container (another RecyclerView or ScrollView) on the condition
        // layout_height="wrap_content" and flexDirection="row". In such a case, the height of the
        // inner RecyclerView (attached RecyclerView for this LayoutManager) is set to 0 at this
        // moment, so the value of the mAvailable doesn't have enough value enough to put the
        // already calculated flex lines.
        mLayoutState.mInfinite = crossMode == View.MeasureSpec.UNSPECIFIED || crossMode == View.MeasureSpec.AT_MOST;
    }

    private void ensureOrientationHelper() {
        if (mOrientationHelper != null) {
            return;
        }
        // There are two cases for each of main axis direction. In either case the scroll happens
        // along the cross axis:
        // -- Scroll vertically when mFlexWrap != FlexWrap.NOWRAP. In this case scroll happens
        //    along the cross axis
        //
        // When scroll direction is vertical:
        // -- Scroll horizontally when mFlexWrap != FlexWrap.NOWRAP. In this case scroll happens
        //    along the cross axis
        if (isMainAxisDirectionHorizontal()) {
            if (mFlexWrap == FlexWrap.NOWRAP) {
                mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
                mSubOrientationHelper = OrientationHelper.createVerticalHelper(this);
            } else {
                mOrientationHelper = OrientationHelper.createVerticalHelper(this);
                mSubOrientationHelper = OrientationHelper.createHorizontalHelper(this);
            }
        } else {
            if (mFlexWrap == FlexWrap.NOWRAP) {
                mOrientationHelper = OrientationHelper.createVerticalHelper(this);
                mSubOrientationHelper = OrientationHelper.createHorizontalHelper(this);
            } else {
                mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
                mSubOrientationHelper = OrientationHelper.createVerticalHelper(this);
            }
        }
    }

    private void ensureLayoutState() {
        if (mLayoutState == null) {
            mLayoutState = new LayoutState();
        }
    }

    @Override
    public void scrollToPosition(int position) {
        mPendingScrollPosition = position;
        mPendingScrollPositionOffset = INVALID_OFFSET;
        if (mPendingSavedState != null) {
            mPendingSavedState.invalidateAnchor();
        }
        requestLayout();
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        LinearSmoothScroller smoothScroller = new LinearSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    /**
     * @return true if LayoutManager will recycle its children when it is detached from
     * RecyclerView.
     */
    @SuppressWarnings("UnusedDeclaration")
    public boolean getRecycleChildrenOnDetach() {
        return mRecycleChildrenOnDetach;
    }

    /**
     * Set whether this LayoutManager will recycle its children when it is detached from
     * RecyclerView.
     * <p>
     * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
     * this flag to <code>true</code> so that views will be available to other RecyclerViews
     * immediately.
     * <p>
     * Note that, setting this flag will result in a performance drop if RecyclerView
     * is restored.
     *
     * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
     */
    @SuppressWarnings("UnusedDeclaration")
    public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
        mRecycleChildrenOnDetach = recycleChildrenOnDetach;
    }

    @Override
    public void onAttachedToWindow(RecyclerView recyclerView) {
        super.onAttachedToWindow(recyclerView);
        mParent = (View) recyclerView.getParent();
    }

    @Override
    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
        super.onDetachedFromWindow(view, recycler);
        if (mRecycleChildrenOnDetach) {
            if (DEBUG) {
                Log.d(TAG, "onDetachedFromWindow. Recycling children in the recycler");
            }
            removeAndRecycleAllViews(recycler);
            recycler.clear();
        }
    }

    @Override
    public boolean canScrollHorizontally() {
        return !isMainAxisDirectionHorizontal() || getWidth() > mParent.getWidth();
    }

    @Override
    public boolean canScrollVertically() {
        return isMainAxisDirectionHorizontal() || getHeight() > mParent.getHeight();
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (!isMainAxisDirectionHorizontal()) {
            int scrolled = handleScrollingCrossAxis(dx, recycler, state);
            mViewCache.clear();
            return scrolled;
        } else {
            int scrolled = handleScrollingMainAxis(dx);
            mAnchorInfo.mPerpendicularCoordinate += scrolled;
            mSubOrientationHelper.offsetChildren(-scrolled);
            return scrolled;
        }
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (isMainAxisDirectionHorizontal()) {
            int scrolled = handleScrollingCrossAxis(dy, recycler, state);
            mViewCache.clear();
            return scrolled;
        } else {
            int scrolled = handleScrollingMainAxis(dy);
            mAnchorInfo.mPerpendicularCoordinate += scrolled;
            mSubOrientationHelper.offsetChildren(-scrolled);
            return scrolled;
        }
    }

    /**
     * @param delta    the delta for the amount that is being scrolled
     *                 (either horizontally or vertically)
     * @param recycler the Recycler instance
     * @param state    the Recycler.State instance
     * @return the amount actually scrolled
     */
    private int handleScrollingCrossAxis(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || delta == 0) {
            return 0;
        }
        ensureOrientationHelper();
        mLayoutState.mShouldRecycle = true;
        int layoutDirection;
        boolean columnAndRtl = !isMainAxisDirectionHorizontal() && mIsRtl;
        if (columnAndRtl) {
            layoutDirection = delta < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        } else {
            layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        }
        int absDelta = Math.abs(delta);

        updateLayoutState(layoutDirection, absDelta);

        int freeScroll = mLayoutState.mScrollingOffset;
        int consumed = freeScroll + fill(recycler, state, mLayoutState);
        if (consumed < 0) {
            return 0;
        }
        int scrolled;
        if (columnAndRtl) {
            scrolled = absDelta > consumed ? -layoutDirection * consumed : delta;
        } else {
            scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
        }
        mOrientationHelper.offsetChildren(-scrolled);
        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }

    private int handleScrollingMainAxis(int delta) {
        if (getChildCount() == 0 || delta == 0) {
            return 0;
        }
        ensureOrientationHelper();
        boolean isMainAxisHorizontal = isMainAxisDirectionHorizontal();
        int parentLength = isMainAxisHorizontal ? mParent.getWidth() : mParent.getHeight();
        int mainAxisLength = isMainAxisHorizontal ? getWidth() : getHeight();

        boolean layoutRtl = getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
        if (layoutRtl) {
            int absDelta = Math.abs(delta);
            if (delta < 0) {
                delta = Math.min(mainAxisLength + mAnchorInfo.mPerpendicularCoordinate - parentLength, absDelta);
                delta = -delta;
            } else {
                delta = mAnchorInfo.mPerpendicularCoordinate + delta > 0 ? -mAnchorInfo.mPerpendicularCoordinate
                        : delta;
            }
        } else {
            if (delta > 0) {
                delta = Math.min(mainAxisLength - mAnchorInfo.mPerpendicularCoordinate - parentLength, delta);
            } else {
                delta = mAnchorInfo.mPerpendicularCoordinate + delta >= 0 ? delta
                        : -mAnchorInfo.mPerpendicularCoordinate;
            }
        }
        return delta;
    }

    /**
     * Update the layout state as part of the scrolling. This method also update the flex lines
     * enough to display the view port including the delta of the scroll.
     *
     * @param layoutDirection the layout direction value. Either of {@link LayoutState#LAYOUT_END}
     *                        or {@link LayoutState#LAYOUT_START}
     * @param absDelta        the absolute value of the delta that is about to be scrolled.
     */
    private void updateLayoutState(int layoutDirection, int absDelta) {
        assert mFlexboxHelper.mIndexToFlexLine != null;
        mLayoutState.mLayoutDirection = layoutDirection;
        boolean mainAxisHorizontal = isMainAxisDirectionHorizontal();

        //noinspection ResourceType
        int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), getWidthMode());
        //noinspection ResourceType
        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(getHeight(), getHeightMode());
        boolean columnAndRtl = !mainAxisHorizontal && mIsRtl;
        if (layoutDirection == LayoutState.LAYOUT_END) {
            View lastVisible = getChildAt(getChildCount() - 1);
            mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(lastVisible);
            int lastVisiblePosition = getPosition(lastVisible);
            int lastVisibleLinePosition = mFlexboxHelper.mIndexToFlexLine[lastVisiblePosition];
            FlexLine lastVisibleLine = mFlexLines.get(lastVisibleLinePosition);

            // The reference view which has the maximum end (or minimum if the layout is RTL and
            // the main axis direction is horizontal) coordinate in  the last visible flex line.
            View referenceView = findLastReferenceViewInLine(lastVisible, lastVisibleLine);
            mLayoutState.mItemDirection = LayoutState.ITEM_DIRECTION_TAIL;
            mLayoutState.mPosition = lastVisiblePosition + mLayoutState.mItemDirection;
            if (mFlexboxHelper.mIndexToFlexLine.length <= mLayoutState.mPosition) {
                mLayoutState.mFlexLinePosition = NO_POSITION;
            } else {
                mLayoutState.mFlexLinePosition = mFlexboxHelper.mIndexToFlexLine[mLayoutState.mPosition];
            }

            if (columnAndRtl) {
                mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(referenceView);
                mLayoutState.mScrollingOffset = -mOrientationHelper.getDecoratedStart(referenceView)
                        + mOrientationHelper.getStartAfterPadding();
                mLayoutState.mScrollingOffset = mLayoutState.mScrollingOffset >= 0 ? mLayoutState.mScrollingOffset
                        : 0;
            } else {
                mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(referenceView);
                mLayoutState.mScrollingOffset = mOrientationHelper.getDecoratedEnd(referenceView)
                        - mOrientationHelper.getEndAfterPadding();
            }

            if ((mLayoutState.mFlexLinePosition == NO_POSITION
                    || mLayoutState.mFlexLinePosition > mFlexLines.size() - 1)
                    && mLayoutState.mPosition <= getFlexItemCount()) {
                // If the RecyclerView tries to scroll beyond the already calculated
                // flex container, need to calculate beyond the amount that needs to be filled

                int needsToFill = absDelta - mLayoutState.mScrollingOffset;
                mFlexLinesResult.reset();
                if (needsToFill > 0) {
                    if (mainAxisHorizontal) {
                        mFlexboxHelper.calculateHorizontalFlexLines(mFlexLinesResult, widthMeasureSpec,
                                heightMeasureSpec, needsToFill, mLayoutState.mPosition, mFlexLines);
                    } else {
                        mFlexboxHelper.calculateVerticalFlexLines(mFlexLinesResult, widthMeasureSpec,
                                heightMeasureSpec, needsToFill, mLayoutState.mPosition, mFlexLines);
                    }
                    mFlexboxHelper.determineMainSize(widthMeasureSpec, heightMeasureSpec, mLayoutState.mPosition);
                    mFlexboxHelper.stretchViews(mLayoutState.mPosition);
                }
            }
        } else {
            View firstVisible = getChildAt(0);

            mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(firstVisible);
            int firstVisiblePosition = getPosition(firstVisible);
            int firstVisibleLinePosition = mFlexboxHelper.mIndexToFlexLine[firstVisiblePosition];
            FlexLine firstVisibleLine = mFlexLines.get(firstVisibleLinePosition);

            // The reference view which has the minimum start (or maximum if the layout is RTL and
            // the main axis direction is horizontal) coordinate in the first visible flex line
            View referenceView = findFirstReferenceViewInLine(firstVisible, firstVisibleLine);

            mLayoutState.mItemDirection = LayoutState.ITEM_DIRECTION_TAIL;
            int flexLinePosition = mFlexboxHelper.mIndexToFlexLine[firstVisiblePosition];
            if (flexLinePosition == NO_POSITION) {
                flexLinePosition = 0;
            }
            if (flexLinePosition > 0) {
                FlexLine previousLine = mFlexLines.get(flexLinePosition - 1);
                // The position of the next item toward start should be on the next flex line,
                // shifting the position by the number of the items in the previous line.
                mLayoutState.mPosition = firstVisiblePosition - previousLine.getItemCount();
            } else {
                mLayoutState.mPosition = NO_POSITION;
            }
            mLayoutState.mFlexLinePosition = flexLinePosition > 0 ? flexLinePosition - 1 : 0;

            if (columnAndRtl) {
                mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(referenceView);
                mLayoutState.mScrollingOffset = mOrientationHelper.getDecoratedEnd(referenceView)
                        - mOrientationHelper.getEndAfterPadding();
                mLayoutState.mScrollingOffset = mLayoutState.mScrollingOffset >= 0 ? mLayoutState.mScrollingOffset
                        : 0;
            } else {
                mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(referenceView);
                mLayoutState.mScrollingOffset = -mOrientationHelper.getDecoratedStart(referenceView)
                        + mOrientationHelper.getStartAfterPadding();
            }
        }
        mLayoutState.mAvailable = absDelta - mLayoutState.mScrollingOffset;
    }

    /**
     * Loop through the first visible flex line to find the reference view, which has the minimum
     * start (or maximum if the layout is RTL and main axis direction is horizontal) coordinate.
     *
     * @param firstView        the first visible view
     * @param firstVisibleLine the first visible flex line
     * @return the reference view
     */
    private View findFirstReferenceViewInLine(View firstView, FlexLine firstVisibleLine) {
        boolean mainAxisHorizontal = isMainAxisDirectionHorizontal();
        View referenceView = firstView;
        for (int i = 1, to = firstVisibleLine.mItemCount; i < to; i++) {
            View viewInSameLine = getChildAt(i);
            if (viewInSameLine == null || viewInSameLine.getVisibility() == View.GONE) {
                continue;
            }
            if (mIsRtl && !mainAxisHorizontal) {
                if (mOrientationHelper.getDecoratedEnd(referenceView) < mOrientationHelper
                        .getDecoratedEnd(viewInSameLine)) {
                    referenceView = viewInSameLine;
                }
            } else {
                if (mOrientationHelper.getDecoratedStart(referenceView) > mOrientationHelper
                        .getDecoratedStart(viewInSameLine)) {
                    referenceView = viewInSameLine;
                }
            }
        }
        return referenceView;
    }

    /**
     * Loop through the last visible flex line to find the reference view, which has the maximum
     * end (or minimum if the layout is RTL and main axis direction is horizontal) coordinate.
     *
     * @param lastView        the last visible view
     * @param lastVisibleLine the last visible flex line
     * @return the reference view
     */
    private View findLastReferenceViewInLine(View lastView, FlexLine lastVisibleLine) {
        boolean mainAxisHorizontal = isMainAxisDirectionHorizontal();
        View referenceView = lastView;
        for (int i = getChildCount() - 2, to = getChildCount() - lastVisibleLine.mItemCount - 1; i > to; i--) {
            View viewInSameLine = getChildAt(i);
            if (viewInSameLine == null || viewInSameLine.getVisibility() == View.GONE) {
                continue;
            }
            if (mIsRtl && !mainAxisHorizontal) {
                // The end edge of the view is left, should be the minimum left edge
                // where the next view should be placed
                if (mOrientationHelper.getDecoratedStart(referenceView) > mOrientationHelper
                        .getDecoratedStart(viewInSameLine)) {
                    referenceView = viewInSameLine;
                }
            } else {
                if (mOrientationHelper.getDecoratedEnd(referenceView) < mOrientationHelper
                        .getDecoratedEnd(viewInSameLine)) {
                    referenceView = viewInSameLine;
                }
            }
        }
        return referenceView;
    }

    @Override
    public int computeHorizontalScrollExtent(RecyclerView.State state) {
        int scrollExtent = computeScrollExtent(state);
        if (DEBUG) {
            Log.d(TAG, "computeHorizontalScrollExtent: " + scrollExtent);
        }
        return scrollExtent;
    }

    @Override
    public int computeVerticalScrollExtent(RecyclerView.State state) {
        int scrollExtent = computeScrollExtent(state);
        if (DEBUG) {
            Log.d(TAG, "computeVerticalScrollExtent: " + scrollExtent);
        }
        return scrollExtent;
    }

    private int computeScrollExtent(RecyclerView.State state) {
        if (getChildCount() == 0) {
            return 0;
        }
        int allChildrenCount = state.getItemCount();
        ensureOrientationHelper();
        View firstReferenceView = findFirstReferenceChild(allChildrenCount);
        View lastReferenceView = findLastReferenceChild(allChildrenCount);
        if (state.getItemCount() == 0 || firstReferenceView == null || lastReferenceView == null) {
            return 0;
        }
        // TODO: Need to consider the reverse pattern when flexWrap == wrap_reverse is implemented
        int extend = mOrientationHelper.getDecoratedEnd(lastReferenceView)
                - mOrientationHelper.getDecoratedStart(firstReferenceView);
        return Math.min(mOrientationHelper.getTotalSpace(), extend);
    }

    @Override
    public int computeHorizontalScrollOffset(RecyclerView.State state) {
        int scrollOffset = computeScrollOffset(state);
        if (DEBUG) {
            Log.d(TAG, "computeHorizontalScrollOffset: " + scrollOffset);
        }
        return scrollOffset;
    }

    @Override
    public int computeVerticalScrollOffset(RecyclerView.State state) {
        int scrollOffset = computeScrollOffset(state);
        if (DEBUG) {
            Log.d(TAG, "computeVerticalScrollOffset: " + scrollOffset);
        }
        return scrollOffset;
    }

    private int computeScrollOffset(RecyclerView.State state) {
        if (getChildCount() == 0) {
            return 0;
        }
        int allChildrenCount = state.getItemCount();
        View firstReferenceView = findFirstReferenceChild(allChildrenCount);
        View lastReferenceView = findLastReferenceChild(allChildrenCount);
        if (state.getItemCount() == 0 || firstReferenceView == null || lastReferenceView == null) {
            return 0;
        }
        assert mFlexboxHelper.mIndexToFlexLine != null;
        int minPosition = getPosition(firstReferenceView);
        int maxPosition = getPosition(lastReferenceView);
        int laidOutArea = Math.abs(mOrientationHelper.getDecoratedEnd(lastReferenceView)
                - mOrientationHelper.getDecoratedStart(firstReferenceView));
        int firstLinePosition = mFlexboxHelper.mIndexToFlexLine[minPosition];
        if (firstLinePosition == 0 || firstLinePosition == NO_POSITION) {
            return 0;
        }
        int lastLinePosition = mFlexboxHelper.mIndexToFlexLine[maxPosition];
        int lineRange = lastLinePosition - firstLinePosition + 1;
        float averageSizePerLine = (float) laidOutArea / lineRange;
        // The number of lines before the first line is equal to the value of firstLinePosition
        return Math.round(firstLinePosition * averageSizePerLine + (mOrientationHelper.getStartAfterPadding()
                - mOrientationHelper.getDecoratedStart(firstReferenceView)));
    }

    @Override
    public int computeHorizontalScrollRange(RecyclerView.State state) {
        int scrollRange = computeScrollRange(state);
        if (DEBUG) {
            Log.d(TAG, "computeHorizontalScrollRange: " + scrollRange);
        }
        return scrollRange;
    }

    @Override
    public int computeVerticalScrollRange(RecyclerView.State state) {
        int scrollRange = computeScrollRange(state);
        if (DEBUG) {
            Log.d(TAG, "computeVerticalScrollRange: " + scrollRange);
        }
        return scrollRange;
    }

    /**
     * @return the estimate total length of the flex container.
     * Note that this method estimates the length by using the number of the visible items and the
     * rest of the non-visible items. So the value returned by this method isn't the exact length
     * of the container. So the scroll bar position may be slightly different from the actual
     * expected position
     */
    private int computeScrollRange(RecyclerView.State state) {
        if (getChildCount() == 0) {
            return 0;
        }
        int allItemCount = state.getItemCount();
        View firstReferenceView = findFirstReferenceChild(allItemCount);
        View lastReferenceView = findLastReferenceChild(allItemCount);
        if (state.getItemCount() == 0 || firstReferenceView == null || lastReferenceView == null) {
            return 0;
        }
        assert mFlexboxHelper.mIndexToFlexLine != null;
        int firstVisiblePosition = findFirstVisibleItemPosition();
        int lastVisiblePosition = findLastVisibleItemPosition();
        int laidOutArea = Math.abs(mOrientationHelper.getDecoratedEnd(lastReferenceView)
                - mOrientationHelper.getDecoratedStart(firstReferenceView));
        int laidOutRange = lastVisiblePosition - firstVisiblePosition + 1;
        return (int) ((float) laidOutArea / laidOutRange * state.getItemCount());
    }

    /**
     * Copied from {@link android.support.v7.widget.RecyclerView.LayoutManager#shouldMeasureChild
     * (View,
     * int, int, RecyclerView.LayoutParams)}}
     */
    private boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, RecyclerView.LayoutParams lp) {
        return child.isLayoutRequested() || !isMeasurementCacheEnabled()
                || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width)
                || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);
    }

    /**
     * Copied from
     * {@link android.support.v7.widget.RecyclerView.LayoutManager#isMeasurementUpToDate(int, int,
     * int)}
     */
    private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) {
        final int specMode = View.MeasureSpec.getMode(spec);
        final int specSize = View.MeasureSpec.getSize(spec);
        if (dimension > 0 && childSize != dimension) {
            return false;
        }
        switch (specMode) {
        case View.MeasureSpec.UNSPECIFIED:
            return true;
        case View.MeasureSpec.AT_MOST:
            return specSize >= childSize;
        case View.MeasureSpec.EXACTLY:
            return specSize == childSize;
        }
        return false;
    }

    private void clearFlexLines() {
        mFlexLines.clear();
        mAnchorInfo.reset();
        mAnchorInfo.mPerpendicularCoordinate = 0;
    }

    private int getChildLeft(View view) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        return getDecoratedLeft(view) - params.leftMargin;
    }

    private int getChildRight(View view) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        return getDecoratedRight(view) + params.rightMargin;
    }

    private int getChildTop(View view) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        return getDecoratedTop(view) - params.topMargin;
    }

    private int getChildBottom(View view) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        return getDecoratedBottom(view) + params.bottomMargin;
    }

    /**
     * @param view              the view to be examined if it's visible
     * @param completelyVisible when passed as {@code true}, this method checks if the view bounds
     *                          don't overlap the bounds of the RecyclerView. When passed as
     *                          {@code false}, this method checks if the view bounds are partially
     *                          visible within the RecyclerView.
     * @return if the view passed as an argument is visible (view bounds are within the parent
     * RecyclerView)
     */
    private boolean isViewVisible(View view, boolean completelyVisible) {
        int left = getPaddingLeft();
        int top = getPaddingTop();
        int right = getWidth() - getPaddingRight();
        int bottom = getHeight() - getPaddingBottom();
        int childLeft = getChildLeft(view);
        int childTop = getChildTop(view);
        int childRight = getChildRight(view);
        int childBottom = getChildBottom(view);

        boolean horizontalCompletelyVisible = false;
        boolean horizontalPartiallyVisible = false;
        boolean verticalCompletelyVisible = false;
        boolean verticalPartiallyVisible = false;
        if (left <= childLeft && right >= childRight) {
            horizontalCompletelyVisible = true;
        }
        if (childLeft >= right || childRight >= left) {
            horizontalPartiallyVisible = true;
        }

        if (top <= childTop && bottom >= childBottom) {
            verticalCompletelyVisible = true;
        }
        if (childTop >= bottom || childBottom >= top) {
            verticalPartiallyVisible = true;
        }
        if (completelyVisible) {
            return horizontalCompletelyVisible && verticalCompletelyVisible;
        } else {
            return horizontalPartiallyVisible && verticalPartiallyVisible;
        }
    }

    /**
     * Returns the adapter position of the first visible view. This position does not include
     * adapter changes that were dispatched after the last layout pass.
     *
     * If RecyclerView has item decorators, they will be considered in calculations as well.
     * LayoutManager may pre-cache some views that are not necessarily visible. Those views
     * are ignored in this method.
     *
     * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
     * there aren't any visible items.
     * @see #findFirstCompletelyVisibleItemPosition()
     * @see #findLastVisibleItemPosition()
     */
    @SuppressWarnings("WeakerAccess")
    public int findFirstVisibleItemPosition() {
        final View child = findOneVisibleChild(0, getChildCount(), false);
        return child == null ? NO_POSITION : getPosition(child);
    }

    /**
     * Returns the adapter position of the first fully visible view. This position does not include
     * adapter changes that were dispatched after the last layout pass.
     *
     * @return The adapter position of the first fully visible item or
     * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
     * @see #findFirstVisibleItemPosition()
     * @see #findLastCompletelyVisibleItemPosition()
     */
    @SuppressWarnings("WeakerAccess")
    public int findFirstCompletelyVisibleItemPosition() {
        final View child = findOneVisibleChild(0, getChildCount(), true);
        return child == null ? NO_POSITION : getPosition(child);
    }

    /**
     * Returns the adapter position of the last visible view. This position does not include
     * adapter changes that were dispatched after the last layout pass.
     *
     * If RecyclerView has item decorators, they will be considered in calculations as well.
     * LayoutManager may pre-cache some views that are not necessarily visible. Those views
     * are ignored in this method.
     *
     * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
     * there aren't any visible items.
     * @see #findLastCompletelyVisibleItemPosition()
     * @see #findFirstVisibleItemPosition()
     */
    @SuppressWarnings("WeakerAccess")
    public int findLastVisibleItemPosition() {
        final View child = findOneVisibleChild(getChildCount() - 1, -1, false);
        return child == null ? NO_POSITION : getPosition(child);
    }

    /**
     * Returns the adapter position of the last fully visible view. This position does not include
     * adapter changes that were dispatched after the last layout pass.
     *
     * @return The adapter position of the last fully visible view or
     * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
     * @see #findLastVisibleItemPosition()
     * @see #findFirstCompletelyVisibleItemPosition()
     */
    @SuppressWarnings("WeakerAccess")
    public int findLastCompletelyVisibleItemPosition() {
        final View child = findOneVisibleChild(getChildCount() - 1, -1, true);
        return child == null ? NO_POSITION : getPosition(child);
    }

    /**
     * Returns the first child that is visible in the provided index range, i.e. either partially or
     * fully visible depending on the arguments provided.
     *
     * @param fromIndex         the start index for searching the visible child
     * @param toIndex           the last index for searching the visible child
     * @param completelyVisible when passed as {@code true}, this method checks if the view bounds
     *                          don't overlap the bounds of the RecyclerView. When passed as
     *                          {@code false}, this method checks if the view bounds are partially
     *                          visible within the RecyclerView.
     * @return the first child that is visible.
     */
    private View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
        int next = toIndex > fromIndex ? 1 : -1;
        for (int i = fromIndex; i != toIndex; i += next) {
            View view = getChildAt(i);
            if (isViewVisible(view, completelyVisible)) {
                return view;
            }
        }
        return null;
    }

    /**
     * @param position the index of the view
     * @return the index of the {@link FlexLine}, which includes the view whose index is passed as
     *         the position argument.
     */
    int getPositionToFlexLineIndex(int position) {
        assert mFlexboxHelper.mIndexToFlexLine != null;
        return mFlexboxHelper.mIndexToFlexLine[position];
    }

    /**
     * LayoutParams used by the {@link FlexboxLayoutManager}, which stores per-child information
     * required for the Flexbox.
     *
     * Note that some parent fields (which are not primitive nor a class implements
     * {@link Parcelable}) are not included as the stored/restored fields after this class
     * is serialized/de-serialized as an {@link Parcelable}.
     */
    public static class LayoutParams extends RecyclerView.LayoutParams implements FlexItem {

        /**
         * @see FlexItem#getFlexGrow()
         */
        private float mFlexGrow = FlexItem.FLEX_GROW_DEFAULT;

        /**
         * @see FlexItem#getFlexShrink()
         */
        private float mFlexShrink = FlexItem.FLEX_SHRINK_DEFAULT;

        /**
         * @see FlexItem#getAlignSelf()
         */
        private int mAlignSelf = AlignSelf.AUTO;

        /**
         * @see FlexItem#getFlexBasisPercent()
         */
        private float mFlexBasisPercent = FlexItem.FLEX_BASIS_PERCENT_DEFAULT;

        /**
         * @see FlexItem#getMinWidth()
         */
        private int mMinWidth;

        /**
         * @see FlexItem#getMinHeight()
         */
        private int mMinHeight;

        /**
         * @see FlexItem#getMaxWidth()
         */
        private int mMaxWidth = MAX_SIZE;

        /**
         * @see FlexItem#getMaxHeight()
         */
        private int mMaxHeight = MAX_SIZE;

        /**
         * @see FlexItem#isWrapBefore()
         */
        private boolean mWrapBefore;

        @Override
        public int getWidth() {
            return width;
        }

        @Override
        public void setWidth(int width) {
            this.width = width;
        }

        @Override
        public int getHeight() {
            return height;
        }

        @Override
        public void setHeight(int height) {
            this.height = height;
        }

        @Override
        public float getFlexGrow() {
            return mFlexGrow;
        }

        @Override
        public void setFlexGrow(float flexGrow) {
            this.mFlexGrow = flexGrow;
        }

        @Override
        public float getFlexShrink() {
            return mFlexShrink;
        }

        @Override
        public void setFlexShrink(float flexShrink) {
            this.mFlexShrink = flexShrink;
        }

        @AlignSelf
        @Override
        public int getAlignSelf() {
            return mAlignSelf;
        }

        @Override
        public void setAlignSelf(@AlignSelf int alignSelf) {
            this.mAlignSelf = alignSelf;
        }

        @Override
        public int getMinWidth() {
            return mMinWidth;
        }

        @Override
        public void setMinWidth(int minWidth) {
            this.mMinWidth = minWidth;
        }

        @Override
        public int getMinHeight() {
            return mMinHeight;
        }

        @Override
        public void setMinHeight(int minHeight) {
            this.mMinHeight = minHeight;
        }

        @Override
        public int getMaxWidth() {
            return mMaxWidth;
        }

        @Override
        public void setMaxWidth(int maxWidth) {
            this.mMaxWidth = maxWidth;
        }

        @Override
        public int getMaxHeight() {
            return mMaxHeight;
        }

        @Override
        public void setMaxHeight(int maxHeight) {
            this.mMaxHeight = maxHeight;
        }

        @Override
        public boolean isWrapBefore() {
            return mWrapBefore;
        }

        @Override
        public void setWrapBefore(boolean wrapBefore) {
            this.mWrapBefore = wrapBefore;
        }

        @Override
        public float getFlexBasisPercent() {
            return mFlexBasisPercent;
        }

        @Override
        public void setFlexBasisPercent(float flexBasisPercent) {
            this.mFlexBasisPercent = flexBasisPercent;
        }

        @Override
        public int getMarginLeft() {
            return leftMargin;
        }

        @Override
        public int getMarginTop() {
            return topMargin;
        }

        @Override
        public int getMarginRight() {
            return rightMargin;
        }

        @Override
        public int getMarginBottom() {
            return bottomMargin;
        }

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(RecyclerView.LayoutParams source) {
            super(source);
        }

        public LayoutParams(LayoutParams source) {
            super(source);

            mFlexGrow = source.mFlexGrow;
            mFlexShrink = source.mFlexShrink;
            mAlignSelf = source.mAlignSelf;
            mFlexBasisPercent = source.mFlexBasisPercent;
            mMinWidth = source.mMinWidth;
            mMinHeight = source.mMinHeight;
            mMaxWidth = source.mMaxWidth;
            mMaxHeight = source.mMaxHeight;
            mWrapBefore = source.mWrapBefore;
        }

        @Override
        public int getOrder() {
            return FlexItem.ORDER_DEFAULT;
        }

        @Override
        public void setOrder(int order) {
            // Unlike the FlexboxLayout, the order attribute is not supported, we don't calculate
            // the order attribute because preparing the order attribute requires all
            // view holders to be inflated at least once, which is inefficient if the number of
            // items in the adapter is large
            throw new UnsupportedOperationException(
                    "Setting the order in the " + "FlexboxLayoutManager is not supported. Use FlexboxLayout "
                            + "if you need to reorder using the attribute.");
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeFloat(this.mFlexGrow);
            dest.writeFloat(this.mFlexShrink);
            dest.writeInt(this.mAlignSelf);
            dest.writeFloat(this.mFlexBasisPercent);
            dest.writeInt(this.mMinWidth);
            dest.writeInt(this.mMinHeight);
            dest.writeInt(this.mMaxWidth);
            dest.writeInt(this.mMaxHeight);
            dest.writeByte(this.mWrapBefore ? (byte) 1 : (byte) 0);
            dest.writeInt(this.bottomMargin);
            dest.writeInt(this.leftMargin);
            dest.writeInt(this.rightMargin);
            dest.writeInt(this.topMargin);
            dest.writeInt(this.height);
            dest.writeInt(this.width);
        }

        protected LayoutParams(Parcel in) {
            super(WRAP_CONTENT, WRAP_CONTENT);
            this.mFlexGrow = in.readFloat();
            this.mFlexShrink = in.readFloat();
            this.mAlignSelf = in.readInt();
            this.mFlexBasisPercent = in.readFloat();
            this.mMinWidth = in.readInt();
            this.mMinHeight = in.readInt();
            this.mMaxWidth = in.readInt();
            this.mMaxHeight = in.readInt();
            this.mWrapBefore = in.readByte() != 0;
            this.bottomMargin = in.readInt();
            this.leftMargin = in.readInt();
            this.rightMargin = in.readInt();
            this.topMargin = in.readInt();
            this.height = in.readInt();
            this.width = in.readInt();
        }

        public static final Parcelable.Creator<LayoutParams> CREATOR = new Parcelable.Creator<LayoutParams>() {
            @Override
            public LayoutParams createFromParcel(Parcel source) {
                return new LayoutParams(source);
            }

            @Override
            public LayoutParams[] newArray(int size) {
                return new LayoutParams[size];
            }
        };
    }

    /**
     * A class that holds the information about an anchor position like from what pixels layout
     * should start.
     */
    private class AnchorInfo {

        private int mPosition;

        private int mFlexLinePosition;

        private int mCoordinate;

        /**
         * The anchor position of the main axis, which is the secondary scrolling direction.
         */
        private int mPerpendicularCoordinate = 0;

        private boolean mLayoutFromEnd;

        private boolean mValid;

        private boolean mAssignedFromSavedState;

        private void reset() {
            mPosition = NO_POSITION;
            mFlexLinePosition = NO_POSITION;
            mCoordinate = INVALID_OFFSET;
            mValid = false;
            mAssignedFromSavedState = false;
            if (isMainAxisDirectionHorizontal()) {
                if (mFlexWrap == FlexWrap.NOWRAP) {
                    mLayoutFromEnd = mFlexDirection == FlexDirection.ROW_REVERSE;
                } else {
                    mLayoutFromEnd = mFlexWrap == FlexWrap.WRAP_REVERSE;
                }
            } else {
                if (mFlexWrap == FlexWrap.NOWRAP) {
                    mLayoutFromEnd = mFlexDirection == FlexDirection.COLUMN_REVERSE;
                } else {
                    mLayoutFromEnd = mFlexWrap == FlexWrap.WRAP_REVERSE;
                }
            }
        }

        private void assignCoordinateFromPadding() {
            if (!isMainAxisDirectionHorizontal() && mIsRtl) {
                mCoordinate = mLayoutFromEnd ? mOrientationHelper.getEndAfterPadding()
                        : getWidth() - mOrientationHelper.getStartAfterPadding();
            } else {
                mCoordinate = mLayoutFromEnd ? mOrientationHelper.getEndAfterPadding()
                        : mOrientationHelper.getStartAfterPadding();
            }
        }

        private void assignFromView(View anchor) {
            if (!isMainAxisDirectionHorizontal() && mIsRtl) {
                // We need to use the anchor view as starting from right if the flex direction is
                // (column or column_reverse) and layout direction is RTL.
                if (mLayoutFromEnd) {
                    mCoordinate = mOrientationHelper.getDecoratedStart(anchor)
                            + mOrientationHelper.getTotalSpaceChange();
                } else {
                    mCoordinate = mOrientationHelper.getDecoratedEnd(anchor);
                }
            } else {
                if (mLayoutFromEnd) {
                    mCoordinate = mOrientationHelper.getDecoratedEnd(anchor)
                            + mOrientationHelper.getTotalSpaceChange();
                } else {
                    mCoordinate = mOrientationHelper.getDecoratedStart(anchor);
                }
            }
            mPosition = getPosition(anchor);
            mAssignedFromSavedState = false;
            assert mFlexboxHelper.mIndexToFlexLine != null;
            int flexLinePosition = mFlexboxHelper.mIndexToFlexLine[mPosition != NO_POSITION ? mPosition : 0];
            mFlexLinePosition = flexLinePosition != NO_POSITION ? flexLinePosition : 0;
            // It's likely that the view is the first item in a flex line, but if not get the
            // index of the first item in the same line because the calculation of the flex lines
            // expects that it starts from the first item in a flex line
            if (mFlexLines.size() > mFlexLinePosition) {
                mPosition = mFlexLines.get(mFlexLinePosition).mFirstIndex;
            }
        }

        @Override
        public String toString() {
            return "AnchorInfo{" + "mPosition=" + mPosition + ", mFlexLinePosition=" + mFlexLinePosition
                    + ", mCoordinate=" + mCoordinate + ", mPerpendicularCoordinate=" + mPerpendicularCoordinate
                    + ", mLayoutFromEnd=" + mLayoutFromEnd + ", mValid=" + mValid + ", mAssignedFromSavedState="
                    + mAssignedFromSavedState + '}';
        }
    }

    /**
     * Helper class that keeps temporary state while the FlexboxLayoutManager is filling out the
     * empty space.
     */
    private static class LayoutState {

        private final static int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE;

        private static final int LAYOUT_START = -1;
        private static final int LAYOUT_END = 1;

        private static final int ITEM_DIRECTION_TAIL = 1;

        /** Number of pixels that we should fill, in the layout direction. */
        private int mAvailable;

        /** If set to true, the value of {@link #mAvailable} is considered as infinite. */
        private boolean mInfinite;

        // TODO: Add mExtra to support better smooth scrolling

        /** Current position on the flex lines being laid out in the layout call */
        private int mFlexLinePosition;

        /** Current position on the adapter to get the next item. */
        private int mPosition;

        /** Pixel offset where layout should start */
        private int mOffset;

        /**
         * Used when LayoutState is constructed in a scrolling state.
         * It should be set the amount of scrolling we can make without creating a new view.
         * Settings this is required for efficient view recycling.
         */
        private int mScrollingOffset;

        /**
         * The most recent
         * {@link #scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)} or
         * {@link #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)} amount.
         */
        private int mLastScrollDelta;

        private int mItemDirection = LayoutState.ITEM_DIRECTION_TAIL;

        private int mLayoutDirection = LayoutState.LAYOUT_END;

        private boolean mShouldRecycle;

        /**
         * @return {@code true} if there are more items to layout
         */
        private boolean hasMore(RecyclerView.State state, List<FlexLine> flexLines) {
            return mPosition >= 0 && mPosition < state.getItemCount() && mFlexLinePosition >= 0
                    && mFlexLinePosition < flexLines.size();
        }

        @Override
        public String toString() {
            return "LayoutState{" + "mAvailable=" + mAvailable + ", mFlexLinePosition=" + mFlexLinePosition
                    + ", mPosition=" + mPosition + ", mOffset=" + mOffset + ", mScrollingOffset=" + mScrollingOffset
                    + ", mLastScrollDelta=" + mLastScrollDelta + ", mItemDirection=" + mItemDirection
                    + ", mLayoutDirection=" + mLayoutDirection + '}';
        }
    }

    /**
     * The saved state that needs to be restored after the RecyclerView is recreated.
     */
    private static class SavedState implements Parcelable {

        /** The adapter position of the first visible view */
        private int mAnchorPosition;

        /**
         * The offset of the first visible view.
         * E.g. if this value is set as -30, the fist visible view's top is off screen by 30 pixels
         */
        private int mAnchorOffset;

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(this.mAnchorPosition);
            dest.writeInt(this.mAnchorOffset);
        }

        SavedState() {
        }

        private SavedState(Parcel in) {
            this.mAnchorPosition = in.readInt();
            this.mAnchorOffset = in.readInt();
        }

        private SavedState(SavedState savedState) {
            mAnchorPosition = savedState.mAnchorPosition;
            mAnchorOffset = savedState.mAnchorOffset;
        }

        private void invalidateAnchor() {
            mAnchorPosition = NO_POSITION;
        }

        private boolean hasValidAnchor(int itemCount) {
            return mAnchorPosition >= 0 && mAnchorPosition < itemCount;
        }

        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel source) {
                return new SavedState(source);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };

        @Override
        public String toString() {
            return "SavedState{" + "mAnchorPosition=" + mAnchorPosition + ", mAnchorOffset=" + mAnchorOffset + '}';
        }
    }
}