12c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer/*
22c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * Copyright (C) 2015 The Android Open Source Project
32c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *
42c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * Licensed under the Apache License, Version 2.0 (the "License");
52c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * you may not use this file except in compliance with the License.
62c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * You may obtain a copy of the License at
72c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *
82c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *      http://www.apache.org/licenses/LICENSE-2.0
92c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *
102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * Unless required by applicable law or agreed to in writing, software
112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * distributed under the License is distributed on an "AS IS" BASIS,
122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * See the License for the specific language governing permissions and
142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * limitations under the License.
152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer */
162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerpackage com.android.car.view;
172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.content.Context;
192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.graphics.PointF;
202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.support.annotation.IntDef;
212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.support.annotation.NonNull;
222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.support.v7.widget.LinearSmoothScroller;
232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.support.v7.widget.RecyclerView;
242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.util.DisplayMetrics;
252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.util.Log;
262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.view.View;
272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.view.ViewGroup;
282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.view.animation.AccelerateInterpolator;
292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.view.animation.DecelerateInterpolator;
302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport android.view.animation.Interpolator;
312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport com.android.car.stream.ui.R;
332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport java.lang.annotation.Retention;
352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport java.lang.annotation.RetentionPolicy;
362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerimport java.util.ArrayList;
372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer/**
392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * Custom {@link RecyclerView.LayoutManager} that behaves similar to LinearLayoutManager except that
402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * it has a few tricks up its sleeve.
412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * <ol>
422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *    <li>In a normal ListView, when views reach the top of the list, they are clipped. In
432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *        CarLayoutManager, views have the option of flying off of the top of the screen as the
442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *        next row settles in to place. This functionality can be enabled or disabled with
452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *        {@link #setOffsetRows(boolean)}.
462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *    <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle
472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *        on the next page. {@link #FLING_THRESHOLD_TO_PAGINATE} and
482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *        {@link #DRAG_DISTANCE_TO_PAGINATE} can be set to have the list settle on the next item
492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *        instead of the next page for small gestures.
502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *    <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that
512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *        the last page can be properly aligned.
522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * </ol>
532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer *
542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer * This LayoutManger should be used with {@link CarRecyclerView}.
552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer */
562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyerpublic class CarLayoutManager extends RecyclerView.LayoutManager {
572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final String TAG = "CarLayoutManager";
582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final boolean DEBUG = false;
592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Any fling below the threshold will just scroll to the top fully visible row. The units is
622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * whatever {@link android.widget.Scroller} would return.
632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * A reasonable value is ~200
652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * This can be disabled by setting the threshold to -1.
672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final int FLING_THRESHOLD_TO_PAGINATE = -1;
692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Any fling shorter than this threshold (in px) will just scroll to the top fully visible row.
722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * A reasonable value is 15.
742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * This can be disabled by setting the distance to -1.
762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final int DRAG_DISTANCE_TO_PAGINATE = -1;
782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * If you scroll really quickly, you can hit the end of the laid out rows before Android has a
812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * chance to layout more. To help counter this, we can layout a number of extra rows past
822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * wherever the focus is if necessary.
832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final int NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS = 2;
852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Scroll bar calculation is a bit complicated. This basically defines the granularity we want
882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * our scroll bar to move. Set this to 1 means our scrollbar will have really jerky movement.
892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Setting it too big will risk an overflow (although there is no performance impact). Ideally
902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * we want to set this higher than the height of our list view. We can't use our list view
912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * height directly though because we might run into situations where getHeight() returns 0, for
922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * example, when the view is not yet measured.
932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final int SCROLL_RANGE = 1000;
952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @ScrollStyle private final int SCROLL_TYPE = MARIO;
972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Retention(RetentionPolicy.SOURCE)
992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @IntDef({MARIO, SUPER_MARIO})
1002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private @interface ScrollStyle {}
1012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final int MARIO = 0;
1022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final int SUPER_MARIO = 1;
1032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Retention(RetentionPolicy.SOURCE)
1052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @IntDef({BEFORE, AFTER})
1062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private @interface LayoutDirection {}
1072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final int BEFORE = 0;
1082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static final int AFTER = 1;
1092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Retention(RetentionPolicy.SOURCE)
1112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @IntDef({ROW_OFFSET_MODE_INDIVIDUAL, ROW_OFFSET_MODE_PAGE})
1122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public @interface RowOffsetMode {}
1132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public static final int ROW_OFFSET_MODE_INDIVIDUAL = 0;
1142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public static final int ROW_OFFSET_MODE_PAGE = 1;
1152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public interface OnItemsChangedListener {
1172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        void onItemsChanged();
1182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
1192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private final AccelerateInterpolator mDanglingRowInterpolator = new AccelerateInterpolator(2);
1212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private final Context mContext;
1222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /** Determines whether or not rows will be offset as they slide off screen **/
1242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private boolean mOffsetRows = false;
1252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /** Determines whether rows will be offset individually or a page at a time **/
1262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @RowOffsetMode private int mRowOffsetMode = ROW_OFFSET_MODE_PAGE;
1272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
1292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * The LayoutManager only gets {@link #onScrollStateChanged(int)} updates. This enables the
1302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * scroll state to be used anywhere.
1312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
1322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
1332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
1342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Used to inspect the current scroll state to help with the various calculations.
1352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     **/
1362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private CarSmoothScroller mSmoothScroller;
1372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private OnItemsChangedListener mItemsChangedListener;
1382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /** The distance that the list has actually scrolled in the most recent drag gesture **/
1402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mLastDragDistance = 0;
1412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /** True if the current drag was limited/capped because it was at some boundary **/
1422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private boolean mReachedLimitOfDrag;
1432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
1442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * The values are continuously updated to keep track of where the current page boundaries are
1452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * on screen. The anchor page break is the page break that is currently within or at the
1462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * top of the viewport. The Upper page break is the page break before it and the lower page
1472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * break is the page break after it.
1482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
1492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * A page break will be set to -1 if it is unknown or n/a.
1502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @see #updatePageBreakPositions()
1512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
1522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mItemCountDuringLastPageBreakUpdate;
1532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    // The index of the first item on the current page
1542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mAnchorPageBreakPosition = 0;
1552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    // The index of the first item on the previous page
1562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mUpperPageBreakPosition = -1;
1572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    // The index of the first item on the next page
1582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mLowerPageBreakPosition = -1;
1592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /** Used in the bookkeeping of mario style scrolling to prevent extra calculations. **/
1602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mLastChildPositionToRequestFocus = -1;
1612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mSampleViewHeight = -1;
1622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
1642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Set the anchor to the following position on the next layout pass.
1652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
1662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int mPendingScrollPosition = -1;
1672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public CarLayoutManager(Context context) {
1692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mContext = context;
1702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
1712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
1732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
1742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return new RecyclerView.LayoutParams(
1752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                ViewGroup.LayoutParams.MATCH_PARENT,
1762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                ViewGroup.LayoutParams.WRAP_CONTENT);
1772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
1782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
1802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public boolean canScrollVertically() {
1812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return true;
1822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
1832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
1842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
1852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * onLayoutChildren is sort of like a "reset" for the layout state. At a high level, it should:
1862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * <ol>
1872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *    <li>Check the current views to get the current state of affairs
1882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *    <li>Detach all views from the window (a lightweight operation) so that rows
1892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *        not re-added will be removed after onLayoutChildren.
1902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *    <li>Re-add rows as necessary.
1912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * </ol>
1922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
1932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @see super#onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)
1942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
1952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
1962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
1972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        /**
1982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * The anchor view is the first fully visible view on screen at the beginning
1992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * of onLayoutChildren (or 0 if there is none). This row will be laid out first. After that,
2002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * layoutNextRow will layout rows above and below it until the boundaries of what should
2012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * be laid out have been reached. See {@link #shouldLayoutNextRow(View, int)} for
2022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * more information.
2032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         */
2042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int anchorPosition = 0;
2052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int anchorTop = -1;
2062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (mPendingScrollPosition == -1) {
2072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View anchor = getFirstFullyVisibleChild();
2082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (anchor != null) {
2092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                anchorPosition = getPosition(anchor);
2102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                anchorTop = getDecoratedTop(anchor);
2112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
2122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
2132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            anchorPosition = mPendingScrollPosition;
2142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mPendingScrollPosition = -1;
2152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mAnchorPageBreakPosition = anchorPosition;
2162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mUpperPageBreakPosition = -1;
2172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mLowerPageBreakPosition = -1;
2182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
2192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
2202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG) {
2212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.v(TAG, String.format(
2222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    ":: onLayoutChildren anchorPosition:%s, anchorTop:%s,"
2232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                            + " mPendingScrollPosition: %s, mAnchorPageBreakPosition:%s,"
2242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                            + " mUpperPageBreakPosition:%s, mLowerPageBreakPosition:%s",
2252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    anchorPosition, anchorTop, mPendingScrollPosition, mAnchorPageBreakPosition,
2262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    mUpperPageBreakPosition, mLowerPageBreakPosition));
2272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
2282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
2292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        /**
2302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * Detach all attached view for 2 reasons:
2312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * <ol>
2322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         *     <li> So that views are put in the scrap heap. This enables us to call
2332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         *          {@link RecyclerView.Recycler#getViewForPosition(int)} which will either return
2342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         *          one of these detached views if it is in the scrap heap, one from the
2352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         *          recycled pool (will only call onBind in the adapter), or create an entirely new
2362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         *          row if needed (will call onCreate and onBind in the adapter).
2372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         *     <li> So that views are automatically removed if they are not manually re-added.
2382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * </ol>
2392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         */
2402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        detachAndScrapAttachedViews(recycler);
2412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
2422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Layout new rows.
2432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View anchor = layoutAnchor(recycler, anchorPosition, anchorTop);
2442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (anchor != null) {
2452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View adjacentRow = anchor;
2462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
2472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
2482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
2492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            adjacentRow = anchor;
2502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
2512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
2522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
2532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
2542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
2552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        updatePageBreakPositions();
2562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        offsetRows();
2572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
2582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG&& getChildCount() > 1) {
2592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.v(TAG, "Currently showing " + getChildCount() + " views " +
2602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    getPosition(getChildAt(0)) + " to " +
2612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    getPosition(getChildAt(getChildCount() - 1)) + " anchor " + anchorPosition);
2622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
2632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
2642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
2652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
2662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * scrollVerticallyBy does the work of what should happen when the list scrolls in addition
2672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * to handling cases where the list hits the end. It should be lighter weight than
2682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * onLayoutChildren. It doesn't have to detach all views. It only looks at the end of the list
2692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * and removes views that have gone out of bounds and lays out new ones that scroll in.
2702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
2712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param dy The amount that the list is supposed to scroll.
2722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *               > 0 means the list is scrolling down.
2732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *               < 0 means the list is scrolling up.
2742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param recycler The recycler that enables views to be reused or created as they scroll in.
2752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param state Various information about the current state of affairs.
2762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The amount the list actually scrolled.
2772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
2782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @see super#scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)
2792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
2802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
2812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public int scrollVerticallyBy(
2822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int dy, @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) {
2832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // If the list is empty, we can prevent the overscroll glow from showing by just
2842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // telling RecycerView that we scrolled.
2852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getItemCount() == 0) {
2862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return dy;
2872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
2882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
2892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Prevent redundant computations if there is definitely nowhere to scroll to.
2902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getChildCount() <= 1 || dy == 0) {
2912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return 0;
2922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
2932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
2942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View firstChild = getChildAt(0);
2952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (firstChild == null) {
2962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return 0;
2972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
2982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstChildPosition = getPosition(firstChild);
2992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView.LayoutParams firstChildParams = getParams(firstChild);
3002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstChildTopWithMargin = getDecoratedTop(firstChild) - firstChildParams.topMargin;
3012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View lastFullyVisibleView = getChildAt(getLastFullyVisibleChildIndex());
3032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (lastFullyVisibleView == null) {
3042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return 0;
3052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
3062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        boolean isLastViewVisible = getPosition(lastFullyVisibleView) == getItemCount() - 1;
3072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View firstFullyVisibleChild = getFirstFullyVisibleChild();
3092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (firstFullyVisibleChild == null) {
3102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return 0;
3112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
3122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstFullyVisiblePosition = getPosition(firstFullyVisibleChild);
3132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView.LayoutParams firstFullyVisibleChildParams = getParams(firstFullyVisibleChild);
3142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int topRemainingSpace = getDecoratedTop(firstFullyVisibleChild)
3152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                - firstFullyVisibleChildParams.topMargin - getPaddingTop();
3162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (isLastViewVisible && firstFullyVisiblePosition == mAnchorPageBreakPosition
3182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                && dy > topRemainingSpace && dy > 0) {
3192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // Prevent dragging down more than 1 page. As a side effect, this also prevents you
3202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // from dragging past the bottom because if you are on the second to last page, it
3212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // prevents you from dragging past the last page.
3222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            dy = topRemainingSpace;
3232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mReachedLimitOfDrag = true;
3242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (dy < 0 && firstChildPosition == 0
3252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                && firstChildTopWithMargin + Math.abs(dy) > getPaddingTop()) {
3262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // Prevent scrolling past the beginning
3272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            dy = firstChildTopWithMargin - getPaddingTop();
3282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mReachedLimitOfDrag = true;
3292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
3302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mReachedLimitOfDrag = false;
3312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
3322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        boolean isDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING;
3342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (isDragging) {
3352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mLastDragDistance += dy;
3362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
3372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // We offset by -dy because the views translate in the opposite direction that the
3382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // list scrolls (think about it.)
3392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        offsetChildrenVertical(-dy);
3402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // The last item in the layout should never scroll above the viewport
3422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View view = getChildAt(getChildCount() - 1);
3432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (view.getTop() < 0) {
3442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            view.setTop(0);
3452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
3462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // This is the meat of this function. We remove views on the trailing edge of the scroll
3482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // and add views at the leading edge as necessary.
3492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View adjacentRow;
3502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (dy > 0) {
3512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            recycleChildrenFromStart(recycler);
3522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            adjacentRow = getChildAt(getChildCount() - 1);
3532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
3542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
3552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
3562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
3572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            recycleChildrenFromEnd(recycler);
3582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            adjacentRow = getChildAt(0);
3592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
3602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
3612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
3622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
3632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Now that the correct views are laid out, offset rows as necessary so we can do whatever
3642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // fancy animation we want such as having the top view fly off the screen as the next one
3652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // settles in to place.
3662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        updatePageBreakPositions();
3672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        offsetRows();
3682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getChildCount() >  1) {
3702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (DEBUG) {
3712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                Log.v(TAG, String.format("Currently showing  %d views (%d to %d)",
3722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                        getChildCount(), getPosition(getChildAt(0)),
3732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                        getPosition(getChildAt(getChildCount() - 1))));
3742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
3752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
3762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return dy;
3782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
3792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
3812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void scrollToPosition(int position) {
3822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mPendingScrollPosition = position;
3832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        requestLayout();
3842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
3852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
3872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void smoothScrollToPosition(
3882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            RecyclerView recyclerView, RecyclerView.State state, int position) {
3892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        /**
3902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * startSmoothScroll will handle stopping the old one if there is one.
3912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * We only keep a copy of it to handle the translation of rows as they slide off the screen
3922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         * in {@link #offsetRowsWithPageBreak()}
3932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer         */
3942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mSmoothScroller = new CarSmoothScroller(mContext, position);
3952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mSmoothScroller.setTargetPosition(position);
3962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        startSmoothScroll(mSmoothScroller);
3972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
3982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
3992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
4002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Miscellaneous bookkeeping.
4012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
4022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
4032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void onScrollStateChanged(int state) {
4042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG) {
4052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.v(TAG, ":: onScrollStateChanged " + state);
4062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
4072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (state == RecyclerView.SCROLL_STATE_IDLE) {
4082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // If the focused view is off screen, give focus to one that is.
4092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // If the first fully visible view is first in the list, focus the first item.
4102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // Otherwise, focus the second so that you have the first item as scrolling context.
4112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View focusedChild = getFocusedChild();
4122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (focusedChild != null
4132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    && (getDecoratedTop(focusedChild) >= getHeight() - getPaddingBottom()
4142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    || getDecoratedBottom(focusedChild) <= getPaddingTop())) {
4152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                focusedChild.clearFocus();
4162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                requestLayout();
4172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
4182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (state == RecyclerView.SCROLL_STATE_DRAGGING) {
4202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mLastDragDistance = 0;
4212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
4222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (state != RecyclerView.SCROLL_STATE_SETTLING) {
4242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mSmoothScroller = null;
4252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
4262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mScrollState = state;
4282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        updatePageBreakPositions();
4292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
4302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
4322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void onItemsChanged(RecyclerView recyclerView) {
4332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        super.onItemsChanged(recyclerView);
4342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (mItemsChangedListener != null) {
4352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mItemsChangedListener.onItemsChanged();
4362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
4372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // When item changed, our sample view height is no longer accurate, and need to be
4382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // recomputed.
4392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mSampleViewHeight = -1;
4402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
4412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
4432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Gives us the opportunity to override the order of the focused views.
4442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * By default, it will just go from top to bottom. However, if there is no focused views, we
4452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * take over the logic and start the focused views from the middle of what is visible and move
4462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * from there until the end of the laid out views in the specified direction.
4472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
4482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
4492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public boolean onAddFocusables(
4502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) {
4512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View focusedChild = getFocusedChild();
4522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (focusedChild != null) {
4532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // If there is a view that already has focus, we can just return false and the normal
4542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // Android addFocusables will work fine.
4552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return false;
4562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
4572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Now we know that there isn't a focused view. We need to set up focusables such that
4592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // instead of just focusing the first item that has been laid out, it focuses starting
4602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // from a visible item.
4612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
4632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (firstFullyVisibleChildIndex == -1) {
4642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // Somehow there is a focused view but there is no fully visible view. There shouldn't
4652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // be a way for this to happen but we'd better stop here and return instead of
4662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // continuing on with -1.
4672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.w(TAG, "There is a focused child but no first fully visible child.");
4682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return false;
4692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
4702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View firstFullyVisibleChild = getChildAt(firstFullyVisibleChildIndex);
4712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstFullyVisibleChildPosition = getPosition(firstFullyVisibleChild);
4722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstFocusableChildIndex = firstFullyVisibleChildIndex;
4742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (firstFullyVisibleChildPosition > 0 && firstFocusableChildIndex + 1 < getItemCount()) {
4752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // We are somewhere in the middle of the list. Instead of starting focus on the first
4762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // item, start focus on the second item to give some context that we aren't at
4772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // the beginning.
4782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            firstFocusableChildIndex++;
4792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
4802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (direction == View.FOCUS_FORWARD) {
4822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // Iterate from the first focusable view to the end.
4832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            for (int i = firstFocusableChildIndex; i < getChildCount(); i++) {
4842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                views.add(getChildAt(i));
4852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
4862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return true;
4872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (direction == View.FOCUS_BACKWARD) {
4882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // Iterate from the first focusable view to the beginning.
4892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            for (int i = firstFocusableChildIndex; i >= 0; i--) {
4902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                views.add(getChildAt(i));
4912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
4922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return true;
4932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
4942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return false;
4952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
4962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
4972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
4982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
4992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                                    RecyclerView.State state) {
5002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return null;
5012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
5022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
5042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * This is the function that decides where to scroll to when a new view is focused.
5052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * You can get the position of the currently focused child through the child parameter.
5062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Once you have that, determine where to smooth scroll to and scroll there.
5072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
5082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param parent The RecyclerView hosting this LayoutManager
5092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param state Current state of RecyclerView
5102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param child Direct child of the RecyclerView containing the newly focused view
5112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param focused The newly focused view. This may be the same view as child or it may be null
5122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return true if the default scroll behavior should be suppressed
5132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
5142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
5152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public boolean onRequestChildFocus(RecyclerView parent, RecyclerView.State state,
5162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                                       View child, View focused) {
5172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (child == null) {
5182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.w(TAG, "onRequestChildFocus with a null child!");
5192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return true;
5202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
5212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG) {
5232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.v(TAG, String.format(":: onRequestChildFocus child: %s, focused: %s", child,
5242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    focused));
5252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
5262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // We have several distinct scrolling methods. Each implementation has been delegated
5282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // to its own method.
5292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (SCROLL_TYPE == MARIO) {
5302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return onRequestChildFocusMarioStyle(parent, child);
5312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (SCROLL_TYPE == SUPER_MARIO) {
5322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return onRequestChildFocusSuperMarioStyle(parent, state, child);
5332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
5342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            throw new IllegalStateException("Unknown scroll type (" + SCROLL_TYPE + ")");
5352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
5362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
5372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
5392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Goal: the scrollbar maintains the same size throughout scrolling and that the scrollbar
5402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * reaches the bottom of the screen when the last item is fully visible. This is because
5412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * there are multiple points that could be considered the bottom since the last item can scroll
5422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * past the bottom edge of the screen.
5432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
5442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * To find the extent, we divide the number of items that can fit on screen by the number of
5452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * items in total.
5462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
5472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
5482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public int computeVerticalScrollExtent(RecyclerView.State state) {
5492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getChildCount() <= 1) {
5502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return 0;
5512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
5522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int sampleViewHeight = getSampleViewHeight();
5542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int availableHeight = getAvailableHeight();
5552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int sampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
5562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (state.getItemCount() <= sampleViewsThatCanFitOnScreen) {
5582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return SCROLL_RANGE;
5592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
5602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return SCROLL_RANGE * sampleViewsThatCanFitOnScreen / state.getItemCount();
5612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
5622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
5632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
5652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * The scrolling offset is calculated by determining what position is at the top of the list.
5662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * However, instead of using fixed integer positions for each row, the scroll position is
5672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * factored in and the position is recalculated as a float that takes in to account the
5682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * current scroll state. This results in a smooth animation for the scrollbar when the user
5692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * scrolls the list.
5702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
5712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
5722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public int computeVerticalScrollOffset(RecyclerView.State state) {
5732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View firstChild = getFirstFullyVisibleChild();
5742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (firstChild == null) {
5752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return 0;
5762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
5772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView.LayoutParams params = getParams(firstChild);
5792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstChildPosition = getPosition(firstChild);
5802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Assume the previous view is the same height as the current one.
5822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        float percentOfPreviousViewShowing = (getDecoratedTop(firstChild) - params.topMargin)
5832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                / (float) (getDecoratedMeasuredHeight(firstChild)
5842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                + params.topMargin + params.bottomMargin);
5852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // If the previous view is actually larger than the current one then this the percent
5862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // can be greater than 1.
5872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        percentOfPreviousViewShowing = Math.min(percentOfPreviousViewShowing, 1);
5882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        float currentPosition = (float) firstChildPosition - percentOfPreviousViewShowing;
5902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int sampleViewHeight = getSampleViewHeight();
5922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int availableHeight = getAvailableHeight();
5932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int numberOfSampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
5942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int positionWhenLastItemIsVisible =
5952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                state.getItemCount() - numberOfSampleViewsThatCanFitOnScreen;
5962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
5972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (positionWhenLastItemIsVisible <= 0) {
5982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return 0;
5992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
6002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (currentPosition >= positionWhenLastItemIsVisible) {
6022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return SCROLL_RANGE;
6032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
6042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return (int) (SCROLL_RANGE * currentPosition / positionWhenLastItemIsVisible);
6062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
6072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
6092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * The range of the scrollbar can be understood as the granularity of how we want the
6102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * scrollbar to scroll.
6112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
6122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    @Override
6132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public int computeVerticalScrollRange(RecyclerView.State state) {
6142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return SCROLL_RANGE;
6152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
6162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
6182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The first view that starts on screen. It assumes that it fully fits on the screen
6192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         though. If the first fully visible child is also taller than the screen then it will
6202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         still be returned. However, since the LayoutManager snaps to view starts, having
6212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         a row that tall would lead to a broken experience anyways.
6222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
6232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public int getFirstFullyVisibleChildIndex() {
6242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        for (int i = 0; i < getChildCount(); i++) {
6252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View child = getChildAt(i);
6262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            RecyclerView.LayoutParams params = getParams(child);
6272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (getDecoratedTop(child) - params.topMargin >= getPaddingTop()) {
6282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return i;
6292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
6302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
6312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return -1;
6322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
6332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public View getFirstFullyVisibleChild() {
6352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
6362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View firstChild = null;
6372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (firstFullyVisibleChildIndex != -1) {
6382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            firstChild = getChildAt(firstFullyVisibleChildIndex);
6392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
6402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return firstChild;
6412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
6422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
6442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The last view that ends on screen. It assumes that the start is also on screen
6452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         though. If the last fully visible child is also taller than the screen then it will
6462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         still be returned. However, since the LayoutManager snaps to view starts, having
6472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         a row that tall would lead to a broken experience anyways.
6482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
6492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public int getLastFullyVisibleChildIndex() {
6502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        for (int i = getChildCount() - 1; i >= 0; i--) {
6512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View child = getChildAt(i);
6522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            RecyclerView.LayoutParams params = getParams(child);
6532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int childBottom = getDecoratedBottom(child) + params.bottomMargin;
6542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int listBottom = getHeight() - getPaddingBottom();
6552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (childBottom <= listBottom) {
6562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return i;
6572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
6582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
6592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return -1;
6602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
6612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
6632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return Whether or not the first view is fully visible.
6642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
6652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public boolean isAtTop() {
6662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // getFirstFullyVisibleChildIndex() can return -1 which indicates that there are no views
6672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // and also means that the list is at the top.
6682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return getFirstFullyVisibleChildIndex() <= 0;
6692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
6702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
6722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return Whether or not the last view is fully visible.
6732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
6742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public boolean isAtBottom() {
6752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex();
6762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (lastFullyVisibleChildIndex == -1) {
6772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return true;
6782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
6792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View lastFullyVisibleChild = getChildAt(lastFullyVisibleChildIndex);
6802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return getPosition(lastFullyVisibleChild) == getItemCount() - 1;
6812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
6822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void setOffsetRows(boolean offsetRows) {
6842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mOffsetRows = offsetRows;
6852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (offsetRows) {
6862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            offsetRows();
6872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
6882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int childCount = getChildCount();
6892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            for (int i = 0; i < childCount; i++) {
6902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                getChildAt(i).setTranslationY(0);
6912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
6922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
6932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
6942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
6952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void setRowOffsetMode(@RowOffsetMode int mode) {
6962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (mode == mRowOffsetMode) {
6972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return;
6982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
6992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mRowOffsetMode = mode;
7002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        offsetRows();
7012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
7022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void setItemsChangedListener(OnItemsChangedListener listener) {
7042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mItemsChangedListener = listener;
7052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
7062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
7082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Finish the pagination taking into account where the gesture started (not where we are now).
7092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
7102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return Whether the list was scrolled as a result of the fling.
7112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
7122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public boolean settleScrollForFling(RecyclerView parent, int flingVelocity) {
7132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getChildCount() == 0) {
7142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return false;
7152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
7162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (mReachedLimitOfDrag) {
7182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return false;
7192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
7202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // If the fling was too slow or too short, settle on the first fully visible row instead.
7222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (Math.abs(flingVelocity) <= FLING_THRESHOLD_TO_PAGINATE
7232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                || Math.abs(mLastDragDistance) <= DRAG_DISTANCE_TO_PAGINATE) {
7242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
7252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (firstFullyVisibleChildIndex != -1) {
7262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                int scrollPosition = getPosition(getChildAt(firstFullyVisibleChildIndex));
7272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                parent.smoothScrollToPosition(scrollPosition);
7282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return true;
7292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
7302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return false;
7312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
7322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Finish the pagination taking into account where the gesture
7342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // started (not where we are now).
7352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        boolean isDownGesture = flingVelocity > 0
7362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                || (flingVelocity == 0 && mLastDragDistance >= 0);
7372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        boolean isUpGesture = flingVelocity < 0
7382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                || (flingVelocity == 0 && mLastDragDistance < 0);
7392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (isDownGesture && mLowerPageBreakPosition != -1) {
7402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // If the last view is fully visible then only settle on the first fully visible view
7412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // instead of the original page down position. However, don't page down if the last
7422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // item has come fully into view.
7432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            parent.smoothScrollToPosition(mAnchorPageBreakPosition);
7442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return true;
7452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (isUpGesture && mUpperPageBreakPosition != -1) {
7462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            parent.smoothScrollToPosition(mUpperPageBreakPosition);
7472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return true;
7482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
7492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.e(TAG, "Error setting scroll for fling! flingVelocity: \t" + flingVelocity +
7502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    "\tlastDragDistance: " + mLastDragDistance + "\tpageUpAtStartOfDrag: " +
7512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    mUpperPageBreakPosition + "\tpageDownAtStartOfDrag: " +
7522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    mLowerPageBreakPosition);
7532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // As a last resort, at the last smooth scroller target position if there is one.
7542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (mSmoothScroller != null) {
7552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                parent.smoothScrollToPosition(mSmoothScroller.getTargetPosition());
7562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return true;
7572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
7582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
7592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return false;
7602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
7612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
7632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The position that paging up from the current position would settle at.
7642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
7652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public int getPageUpPosition() {
7662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return mUpperPageBreakPosition;
7672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
7682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
7702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The position that paging down from the current position would settle at.
7712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
7722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public int getPageDownPosition() {
7732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return mLowerPageBreakPosition;
7742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
7752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
7772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Layout the anchor row. The anchor row is the first fully visible row.
7782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
7792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param anchorTop The decorated top of the anchor. If it is not known or should be reset
7802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *                  to the top, pass -1.
7812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
7822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private View layoutAnchor(RecyclerView.Recycler recycler, int anchorPosition, int anchorTop) {
7832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (anchorPosition > getItemCount() - 1) {
7842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return null;
7852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
7862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View anchor = recycler.getViewForPosition(anchorPosition);
7872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView.LayoutParams params = getParams(anchor);
7882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        measureChildWithMargins(anchor, 0, 0);
7892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int left = getPaddingLeft() + params.leftMargin;
7902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int top = (anchorTop == -1) ? params.topMargin : anchorTop;
7912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int right = left + getDecoratedMeasuredWidth(anchor);
7922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int bottom = top + getDecoratedMeasuredHeight(anchor);
7932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        layoutDecorated(anchor, left, top, right, bottom);
7942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        addView(anchor);
7952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return anchor;
7962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
7972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
7982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
7992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Lays out the next row in the specified direction next to the specified adjacent row.
8002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
8012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param recycler The recycler from which a new view can be created.
8022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param adjacentRow The View of the adjacent row which will be used to position the new one.
8032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @param layoutDirection The side of the adjacent row that the new row will be laid out on.
8042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
8052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The new row that was laid out.
8062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
8072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private View layoutNextRow(RecyclerView.Recycler recycler, View adjacentRow,
8082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                               @LayoutDirection int layoutDirection) {
8092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int adjacentRowPosition = getPosition(adjacentRow);
8112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int newRowPosition = adjacentRowPosition;
8122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (layoutDirection == BEFORE) {
8132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            newRowPosition = adjacentRowPosition - 1;
8142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (layoutDirection == AFTER) {
8152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            newRowPosition = adjacentRowPosition + 1;
8162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
8172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Because we detach all rows in onLayoutChildren, this will often just return a view from
8192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // the scrap heap.
8202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View newRow = recycler.getViewForPosition(newRowPosition);
8212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        measureChildWithMargins(newRow, 0, 0);
8232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView.LayoutParams newRowParams =
8242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                (RecyclerView.LayoutParams) newRow.getLayoutParams();
8252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView.LayoutParams adjacentRowParams =
8262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                (RecyclerView.LayoutParams) adjacentRow.getLayoutParams();
8272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int left = getPaddingLeft() + newRowParams.leftMargin;
8282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int right = left + getDecoratedMeasuredWidth(newRow);
8292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int top, bottom;
8302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (layoutDirection == BEFORE) {
8312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            bottom = adjacentRow.getTop() - adjacentRowParams.topMargin - newRowParams.bottomMargin;
8322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            top = bottom - getDecoratedMeasuredHeight(newRow);
8332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
8342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            top = getDecoratedBottom(adjacentRow) +
8352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    adjacentRowParams.bottomMargin + newRowParams.topMargin;
8362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            bottom = top + getDecoratedMeasuredHeight(newRow);
8372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
8382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        layoutDecorated(newRow, left, top, right, bottom);
8392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (layoutDirection == BEFORE) {
8412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            addView(newRow, 0);
8422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
8432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            addView(newRow);
8442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
8452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return newRow;
8472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
8482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
8502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return Whether another row should be laid out in the specified direction.
8512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
8522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private boolean shouldLayoutNextRow(RecyclerView.State state, View adjacentRow,
8532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                                        @LayoutDirection int layoutDirection) {
8542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int adjacentRowPosition = getPosition(adjacentRow);
8552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (layoutDirection == BEFORE) {
8572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (adjacentRowPosition == 0) {
8582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // We already laid out the first row.
8592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return false;
8602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
8612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (layoutDirection == AFTER) {
8622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (adjacentRowPosition >= state.getItemCount() - 1) {
8632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // We already laid out the last row.
8642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return false;
8652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
8662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
8672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // If we are scrolling layout views until the target position.
8692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (mSmoothScroller != null) {
8702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (layoutDirection == BEFORE
8712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    && adjacentRowPosition >= mSmoothScroller.getTargetPosition()) {
8722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return true;
8732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            } else if (layoutDirection == AFTER
8742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    && adjacentRowPosition <= mSmoothScroller.getTargetPosition()) {
8752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return true;
8762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
8772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
8782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View focusedRow = getFocusedChild();
8802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (focusedRow != null) {
8812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int focusedRowPosition = getPosition(focusedRow);
8822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (layoutDirection == BEFORE && adjacentRowPosition
8832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    >= focusedRowPosition - NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
8842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return true;
8852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            } else if (layoutDirection == AFTER && adjacentRowPosition
8862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    <= focusedRowPosition + NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
8872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return true;
8882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
8892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
8902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
8912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView.LayoutParams params = getParams(adjacentRow);
8922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int adjacentRowTop = getDecoratedTop(adjacentRow) - params.topMargin;
8932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int adjacentRowBottom = getDecoratedBottom(adjacentRow) - params.bottomMargin;
8942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (layoutDirection == BEFORE
8952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                && adjacentRowTop < getPaddingTop() - getHeight()) {
8962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // View is more than 1 page past the top of the screen and also past where the user has
8972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // scrolled to. We want to keep one page past the top to make the scroll up calculation
8982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // easier and scrolling smoother.
8992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return false;
9002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (layoutDirection == AFTER
9012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                && adjacentRowBottom > getHeight() - getPaddingBottom()) {
9022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // View is off of the bottom and also past where the user has scrolled to.
9032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return false;
9042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return true;
9072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
9082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
9102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Remove and recycle views that are no longer needed.
9112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
9122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private void recycleChildrenFromStart(RecyclerView.Recycler recycler) {
9132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Start laying out children one page before the top of the viewport.
9142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int childrenStart = getPaddingTop() - getHeight();
9152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int focusedChildPosition = Integer.MAX_VALUE;
9172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View focusedChild = getFocusedChild();
9182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (focusedChild != null) {
9192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            focusedChildPosition = getPosition(focusedChild);
9202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Count the number of views that should be removed.
9232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int detachedCount = 0;
9242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int childCount = getChildCount();
9252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        for (int i = 0; i < childCount; i++) {
9262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            final View child = getChildAt(i);
9272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int childEnd = getDecoratedBottom(child);
9282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int childPosition = getPosition(child);
9292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (childEnd >= childrenStart || childPosition >= focusedChildPosition - 1) {
9312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                break;
9322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
9332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            detachedCount++;
9352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Remove the number of views counted above. Done by removing the first child n times.
9382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        while (--detachedCount >= 0) {
9392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            final View child = getChildAt(0);
9402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            removeAndRecycleView(child, recycler);
9412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
9432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
9452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Remove and recycle views that are no longer needed.
9462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
9472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private void recycleChildrenFromEnd(RecyclerView.Recycler recycler) {
9482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Layout views until the end of the viewport.
9492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int childrenEnd = getHeight();
9502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int focusedChildPosition = Integer.MIN_VALUE + 1;
9522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View focusedChild = getFocusedChild();
9532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (focusedChild != null) {
9542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            focusedChildPosition = getPosition(focusedChild);
9552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Count the number of views that should be removed.
9582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int firstDetachedPos = 0;
9592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int detachedCount = 0;
9602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int childCount = getChildCount();
9612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        for (int i = childCount - 1; i >= 0; i--) {
9622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            final View child = getChildAt(i);
9632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int childStart = getDecoratedTop(child);
9642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int childPosition = getPosition(child);
9652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (childStart <= childrenEnd || childPosition <= focusedChildPosition - 1) {
9672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                break;
9682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
9692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            firstDetachedPos = i;
9712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            detachedCount++;
9722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        while (--detachedCount >= 0) {
9752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            final View child = getChildAt(firstDetachedPos);
9762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            removeAndRecycleView(child, recycler);
9772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
9792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
9812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Offset rows to do fancy animations. If {@link #mOffsetRows} is false, this will do nothing.
9822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
9832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @see #offsetRowsIndividually()
9842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @see #offsetRowsByPage()
9852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
9862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    public void offsetRows() {
9872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (!mOffsetRows) {
9882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return;
9892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (mRowOffsetMode == ROW_OFFSET_MODE_PAGE) {
9922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            offsetRowsByPage();
9932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (mRowOffsetMode == ROW_OFFSET_MODE_INDIVIDUAL) {
9942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            offsetRowsIndividually();
9952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
9962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
9972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
9982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
9992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Offset the single row that is scrolling off the screen such that by the time the next row
10002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * reaches the top, it will have accelerated completely off of the screen.
10012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
10022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private void offsetRowsIndividually() {
10032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getChildCount() == 0) {
10042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (DEBUG) {
10052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                Log.d(TAG, ":: offsetRowsIndividually getChildCount=0");
10062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
10072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return;
10082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
10092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Identify the dangling row. It will be the first row that is at the top of the
10112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // list or above.
10122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int danglingChildIndex = -1;
10132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        for (int i = getChildCount() - 1; i >= 0; i--) {
10142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View child = getChildAt(i);
10152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (getDecoratedTop(child) - getParams(child).topMargin <= getPaddingTop()) {
10162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                danglingChildIndex = i;
10172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                break;
10182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
10192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
10202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mAnchorPageBreakPosition = danglingChildIndex;
10222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG) {
10242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.v(TAG, ":: offsetRowsIndividually danglingChildIndex: " + danglingChildIndex);
10252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
10262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Calculate the total amount that the view will need to scroll in order to go completely
10282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // off screen.
10292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
10302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int[] locs = new int[2];
10312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        rv.getLocationInWindow(locs);
10322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int listTopInWindow = locs[1] + rv.getPaddingTop();
10332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int maxDanglingViewTranslation;
10342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int childCount = getChildCount();
10362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        for (int i = 0; i < childCount; i++) {
10372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View child = getChildAt(i);
10382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            RecyclerView.LayoutParams params = getParams(child);
10392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            maxDanglingViewTranslation = listTopInWindow;
10412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // If the child has a negative margin, we'll actually need to translate the view a
10422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // little but further to get it completely off screen.
10432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (params.topMargin < 0) {
10442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                maxDanglingViewTranslation -= params.topMargin;
10452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
10462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (params.bottomMargin < 0) {
10472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                maxDanglingViewTranslation -= params.bottomMargin;
10482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
10492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (i < danglingChildIndex) {
10512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setAlpha(0f);
10522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            } else if (i > danglingChildIndex) {
10532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setAlpha(1f);
10542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setTranslationY(0);
10552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            } else {
10562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                int totalScrollDistance = getDecoratedMeasuredHeight(child) +
10572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                        params.topMargin + params.bottomMargin;
10582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                int distanceLeftInScroll = getDecoratedBottom(child) +
10602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                        params.bottomMargin - getPaddingTop();
10612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                float percentageIntoScroll = 1 - distanceLeftInScroll / (float) totalScrollDistance;
10622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                float interpolatedPercentage =
10632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                        mDanglingRowInterpolator.getInterpolation(percentageIntoScroll);
10642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setAlpha(1f);
10662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setTranslationY(-(maxDanglingViewTranslation * interpolatedPercentage));
10672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
10682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
10692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
10702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
10722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * When the list scrolls, the entire page of rows will offset in one contiguous block. This
10732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * significantly reduces the amount of extra motion at the top of the screen.
10742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
10752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private void offsetRowsByPage() {
10762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View anchorView = findViewByPosition(mAnchorPageBreakPosition);
10772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (anchorView == null) {
10782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (DEBUG) {
10792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                Log.d(TAG, ":: offsetRowsByPage anchorView null");
10802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
10812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return;
10822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
10832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int anchorViewTop = getDecoratedTop(anchorView) - getParams(anchorView).topMargin;
10842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
10862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int upperViewTop = getDecoratedTop(upperPageBreakView)
10872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                - getParams(upperPageBreakView).topMargin;
10882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int scrollDistance = upperViewTop - anchorViewTop;
10902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int distanceLeft = anchorViewTop - getPaddingTop();
10922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        float scrollPercentage = (Math.abs(scrollDistance) - distanceLeft)
10932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                / (float) Math.abs(scrollDistance);
10942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
10952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG) {
10962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.d(TAG, String.format(
10972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    ":: offsetRowsByPage scrollDistance:%s, distanceLeft:%s, scrollPercentage:%s",
10982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    scrollDistance, distanceLeft, scrollPercentage));
10992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
11002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Calculate the total amount that the view will need to scroll in order to go completely
11022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // off screen.
11032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
11042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int[] locs = new int[2];
11052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        rv.getLocationInWindow(locs);
11062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int listTopInWindow = locs[1] + rv.getPaddingTop();
11072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int childCount = getChildCount();
11092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        for (int i = 0; i < childCount; i++) {
11102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View child = getChildAt(i);
11112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int position = getPosition(child);
11122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (position < mUpperPageBreakPosition) {
11132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setAlpha(0f);
11142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setTranslationY(-listTopInWindow);
11152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            } else if (position < mAnchorPageBreakPosition) {
11162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // If the child has a negative margin, we need to offset the row by a little bit
11172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // extra so that it moves completely off screen.
11182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                RecyclerView.LayoutParams params = getParams(child);
11192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                int extraTranslation = 0;
11202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                if (params.topMargin < 0) {
11212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    extraTranslation -= params.topMargin;
11222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                }
11232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                if (params.bottomMargin < 0) {
11242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    extraTranslation -= params.bottomMargin;
11252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                }
11262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                int translation = (int) ((listTopInWindow + extraTranslation)
11272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                        * mDanglingRowInterpolator.getInterpolation(scrollPercentage));
11282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setAlpha(1f);
11292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setTranslationY(-translation);
11302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            } else {
11312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setAlpha(1f);
11322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                child.setTranslationY(0);
11332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
11342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
11352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
11362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
11382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Update the page break positions based on the position of the views on screen. This should
11392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * be called whenever view move or change such as during a scroll or layout.
11402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
11412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private void updatePageBreakPositions() {
11422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getChildCount() == 0) {
11432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (DEBUG) {
11442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                Log.d(TAG, ":: updatePageBreakPosition getChildCount: 0");
11452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
11462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return;
11472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
11482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG) {
11502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.v(TAG, String.format(":: #BEFORE updatePageBreakPositions " +
11512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                            "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
11522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                            + "mLowerPageBreakPosition:%s",
11532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition));
11542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
11552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // If the item count has changed, our page boundaries may no longer be accurate. This will
11572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // force the page boundaries to reset around the current view that is closest to the top.
11582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getItemCount() != mItemCountDuringLastPageBreakUpdate) {
11592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (DEBUG) {
11602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                Log.d(TAG, "Item count changed. Resetting page break positions.");
11612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
11622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mAnchorPageBreakPosition = getPosition(getFirstFullyVisibleChild());
11632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
11642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mItemCountDuringLastPageBreakUpdate = getItemCount();
11652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (mAnchorPageBreakPosition == -1) {
11672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.w(TAG, "Unable to update anchor positions. There is no anchor position.");
11682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return;
11692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
11702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View anchorPageBreakView = findViewByPosition(mAnchorPageBreakPosition);
11722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (anchorPageBreakView == null) {
11732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return;
11742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
11752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int topMargin = getParams(anchorPageBreakView).topMargin;
11762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int anchorTop = getDecoratedTop(anchorPageBreakView) - topMargin;
11772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
11782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int upperPageBreakTop = upperPageBreakView == null ? Integer.MIN_VALUE :
11792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin;
11802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG) {
11822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.v(TAG, String.format(":: #MID updatePageBreakPositions topMargin:%s, anchorTop:%s"
11832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                            + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
11842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                            + "mLowerPageBreakPosition:%s", topMargin, anchorTop,
11852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition));
11862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
11872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
11882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (anchorTop < getPaddingTop()) {
11892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // The anchor has moved above the viewport. We are now on the next page. Shift the page
11902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // break positions and calculate a new lower one.
11912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mUpperPageBreakPosition = mAnchorPageBreakPosition;
11922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mAnchorPageBreakPosition = mLowerPageBreakPosition;
11932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
11942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (mAnchorPageBreakPosition > 0 && upperPageBreakTop >= getPaddingTop()) {
11952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // The anchor has moved below the viewport. We are now on the previous page. Shift
11962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // the page break positions and calculate a new upper one.
11972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mLowerPageBreakPosition = mAnchorPageBreakPosition;
11982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mAnchorPageBreakPosition = mUpperPageBreakPosition;
11992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
12002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
12012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
12022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
12032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
12042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (DEBUG) {
12062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.v(TAG, String.format(":: #AFTER updatePageBreakPositions " +
12072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                            "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
12082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                            + "mLowerPageBreakPosition:%s",
12092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition));
12102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
12112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
12122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
12142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The page break position of the page before the anchor page break position. However,
12152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         if it reaches the end of the laid out children or position 0, it will just return
12162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         that.
12172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
12182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int calculatePreviousPageBreakPosition(int position) {
12192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (position == -1) {
12202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return -1;
12212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
12222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View referenceView = findViewByPosition(position);
12232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
12242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int previousPagePosition = position;
12262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        while (previousPagePosition > 0) {
12272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            previousPagePosition--;
12282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View child = findViewByPosition(previousPagePosition);
12292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (child == null) {
12302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // View has not been laid out yet.
12312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return previousPagePosition + 1;
12322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
12332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int childTop = getDecoratedTop(child) - getParams(child).topMargin;
12352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (childTop < referenceViewTop - getHeight()) {
12372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return previousPagePosition + 1;
12382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
12392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
12402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Beginning of the list.
12412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return 0;
12422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
12432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
12452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The page break position of the next page after the anchor page break position.
12462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         However, if it reaches the end of the laid out children or end of the list, it will
12472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         just return that.
12482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
12492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int calculateNextPageBreakPosition(int position) {
12502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (position == -1) {
12512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return -1;
12522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
12532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View referenceView = findViewByPosition(position);
12552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (referenceView == null) {
12562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return position;
12572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
12582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
12592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int nextPagePosition = position;
12612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Search for the first child item after the referenceView that didn't fully fit on to the
12632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // screen. The next page should start from the item before this child, so that users have
12642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // a visual anchoring point of the page change.
12652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        while (position < getItemCount() - 1) {
12662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            nextPagePosition++;
12672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View child = findViewByPosition(nextPagePosition);
12682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (child == null) {
12692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // The next view has not been laid out yet.
12702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return nextPagePosition - 1;
12712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
12722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int childBottom = getDecoratedBottom(child) + getParams(child).bottomMargin;
12742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (childBottom - referenceViewTop > getHeight() - getPaddingTop()) {
12752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // If choosing the previous child causes the view to snap back to the referenceView
12762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // position, then skip that and go directly to the child. This avoids the case
12772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // where a tall card in the layout causes the view to constantly snap back to
12782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                // the top when scrolled.
12792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return nextPagePosition - 1 == position ? nextPagePosition : nextPagePosition - 1;
12802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
12812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
12822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // End of the list.
12832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return nextPagePosition;
12842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
12852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
12872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * In this style, the focus will scroll down to the middle of the screen and lock there
12882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * so that moving in either direction will move the entire list by 1.
12892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
12902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private boolean onRequestChildFocusMarioStyle(RecyclerView parent, View child) {
12912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int focusedPosition = getPosition(child);
12922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (focusedPosition == mLastChildPositionToRequestFocus) {
12932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return true;
12942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
12952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mLastChildPositionToRequestFocus = focusedPosition;
12962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
12972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int availableHeight = getAvailableHeight();
12982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int focusedChildTop = getDecoratedTop(child);
12992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int focusedChildBottom = getDecoratedBottom(child);
13002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int childIndex = parent.indexOfChild(child);
13022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // Iterate through children starting at the focused child to find the child above it to
13032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // smooth scroll to such that the focused child will be as close to the middle of the screen
13042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // as possible.
13052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        for (int i = childIndex; i >= 0; i--) {
13062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View childAtI = getChildAt(i);
13072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (childAtI == null) {
13082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                Log.e(TAG, "Child is null at index " + i);
13092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                continue;
13102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
13112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // We haven't found a view that is more than half of the recycler view height above it
13122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // but we've reached the top so we can't go any further.
13132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (i == 0) {
13142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                parent.smoothScrollToPosition(getPosition(childAtI));
13152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                break;
13162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
13172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // Because we want to scroll to the first view that is less than half of the screen
13192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // away from the focused view, we "look ahead" one view. When the look ahead view
13202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // is more than availableHeight / 2 away, the current child at i is the one we want to
13212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // scroll to. However, sometimes, that view can be null (ie, if the view is in
13222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // transition). In that case, just skip that view.
13232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View childBefore = getChildAt(i - 1);
13252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (childBefore == null) {
13262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                continue;
13272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
13282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int distanceToChildBeforeFromTop = focusedChildTop - getDecoratedTop(childBefore);
13292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int distanceToChildBeforeFromBottom = focusedChildBottom - getDecoratedTop(childBefore);
13302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (distanceToChildBeforeFromTop > availableHeight / 2
13322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    || distanceToChildBeforeFromBottom > availableHeight) {
13332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                parent.smoothScrollToPosition(getPosition(childAtI));
13342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                break;
13352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
13362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
13372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return true;
13382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
13392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
13412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * In this style, you can free scroll in the middle of the list but if you get to the edge,
13422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * the list will advance to ensure that there is context ahead of the focused item.
13432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
13442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private boolean onRequestChildFocusSuperMarioStyle(RecyclerView parent,
13452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                                                       RecyclerView.State state, View child) {
13462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int focusedPosition = getPosition(child);
13472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (focusedPosition == mLastChildPositionToRequestFocus) {
13482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return true;
13492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
13502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        mLastChildPositionToRequestFocus = focusedPosition;
13512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int bottomEdgeThatMustBeOnScreen;
13532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int focusedIndex = parent.indexOfChild(child);
13542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        // The amount of the last card at the end that must be showing to count as visible.
13552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int peekAmount = mContext.getResources()
13562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                .getDimensionPixelSize(R.dimen.car_last_card_peek_amount);
13572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (focusedPosition == state.getItemCount() - 1) {
13582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // The last item is focused.
13592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            bottomEdgeThatMustBeOnScreen = getDecoratedBottom(child);
13602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else if (focusedIndex == getChildCount() - 1) {
13612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // The last laid out item is focused. Scroll enough so that the next card has at least
13622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // the peek size visible
13632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            ViewGroup.MarginLayoutParams params =
13642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    (ViewGroup.MarginLayoutParams) child.getLayoutParams();
13652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // We add params.topMargin as an estimate because we don't actually know the top margin
13662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // of the next row.
13672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            bottomEdgeThatMustBeOnScreen = getDecoratedBottom(child) +
13682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    params.bottomMargin + params.topMargin + peekAmount;
13692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
13702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View nextChild = getChildAt(focusedIndex + 1);
13712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            bottomEdgeThatMustBeOnScreen = getDecoratedTop(nextChild) + peekAmount;
13722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
13732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (bottomEdgeThatMustBeOnScreen > getHeight()) {
13752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // We're going to have to scroll because the bottom edge that must be on screen is past
13762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // the bottom.
13772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int topEdgeToFindViewUnder = getPaddingTop() +
13782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    bottomEdgeThatMustBeOnScreen - getHeight();
13792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            View nextChild = null;
13812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            for (int i = 0; i < getChildCount(); i++) {
13822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                View potentialNextChild = getChildAt(i);
13832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                RecyclerView.LayoutParams params = getParams(potentialNextChild);
13842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                float top = getDecoratedTop(potentialNextChild) - params.topMargin;
13852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                if (top >= topEdgeToFindViewUnder) {
13862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    nextChild = potentialNextChild;
13872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    break;
13882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                }
13892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
13902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
13912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (nextChild == null) {
13922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                Log.e(TAG, "There is no view under " + topEdgeToFindViewUnder);
13932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return true;
13942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
13952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int nextChildPosition = getPosition(nextChild);
13962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            parent.smoothScrollToPosition(nextChildPosition);
13972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
13982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int firstFullyVisibleIndex = getFirstFullyVisibleChildIndex();
13992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (focusedIndex <= firstFullyVisibleIndex) {
14002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                parent.smoothScrollToPosition(Math.max(focusedPosition - 1, 0));
14012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
14022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
14032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return true;
14042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
14052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
14072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * We don't actually know the size of every single view, only what is currently laid out.
14082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * This makes it difficult to do accurate scrollbar calculations. However, lists in the car
14092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * often consist of views with identical heights. Because of that, we can use
14102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * a single sample view to do our calculations for. The main exceptions are in the first items
14112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * of a list (hero card, last call card, etc) so if the first view is at position 0, we pick
14122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * the next one.
14132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *
14142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The decorated measured height of the sample view plus its margins.
14152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
14162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int getSampleViewHeight() {
14172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (mSampleViewHeight != -1) {
14182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return mSampleViewHeight;
14192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
14202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int sampleViewIndex = getFirstFullyVisibleChildIndex();
14212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        View sampleView = getChildAt(sampleViewIndex);
14222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (getPosition(sampleView) == 0 && sampleViewIndex < getChildCount() - 1) {
14232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            sampleView = getChildAt(++sampleViewIndex);
14242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
14252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        RecyclerView.LayoutParams params = getParams(sampleView);
14262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        int height =
14272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                getDecoratedMeasuredHeight(sampleView) + params.topMargin + params.bottomMargin;
14282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        if (height == 0) {
14292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // This can happen if the view isn't measured yet.
14302c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            Log.w(TAG, "The sample view has a height of 0. Returning a dummy value for now " +
14312c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    "that won't be cached.");
14322c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            height = mContext.getResources().getDimensionPixelSize(R.dimen.car_sample_row_height);
14332c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        } else {
14342c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mSampleViewHeight = height;
14352c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
14362c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return height;
14372c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
14382c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14392c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
14402c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return The height of the RecyclerView excluding padding.
14412c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
14422c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private int getAvailableHeight() {
14432c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return getHeight() - getPaddingTop() - getPaddingBottom();
14442c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
14452c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14462c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
14472c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * @return {@link RecyclerView.LayoutParams} for the given view or null if it isn't a child
14482c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *         of {@link RecyclerView}.
14492c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
14502c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private static RecyclerView.LayoutParams getParams(View view) {
14512c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        return (RecyclerView.LayoutParams) view.getLayoutParams();
14522c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
14532c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14542c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    /**
14552c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     * Custom {@link LinearSmoothScroller} that has:
14562c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *     a) Custom control over the speed of scrolls.
14572c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *     b) Scrolling snaps to start. All of our scrolling logic depends on that.
14582c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *     c) Keeps track of some state of the current scroll so that can aid in things like
14592c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     *        the scrollbar calculations.
14602c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer     */
14612c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    private final class CarSmoothScroller extends LinearSmoothScroller {
14622c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        /** This value (150) was hand tuned by UX for what felt right. **/
14632c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        private static final float MILLISECONDS_PER_INCH = 150f;
14642c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        /** This value (0.45) was hand tuned by UX for what felt right. **/
14652c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        private static final float DECELERATION_TIME_DIVISOR = 0.45f;
14662c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        private static final int NON_TOUCH_MAX_DECELERATION_MS = 1000;
14672c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14682c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        /** This value (1.8) was hand tuned by UX for what felt right. **/
14692c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        private final Interpolator mInterpolator = new DecelerateInterpolator(1.8f);
14702c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14712c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        private final boolean mHasTouch;
14722c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        private final int mTargetPosition;
14732c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14742c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14752c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        public CarSmoothScroller(Context context, int targetPosition) {
14762c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            super(context);
14772c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mTargetPosition = targetPosition;
14782c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            mHasTouch = mContext.getResources().getBoolean(R.bool.car_true_for_touch);
14792c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
14802c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14812c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        @Override
14822c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        public PointF computeScrollVectorForPosition(int i) {
14832c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (getChildCount() == 0) {
14842c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return null;
14852c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
14862c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            final int firstChildPos = getPosition(getChildAt(getFirstFullyVisibleChildIndex()));
14872c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            final int direction = (mTargetPosition < firstChildPos) ? -1 : 1;
14882c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return new PointF(0, direction);
14892c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
14902c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14912c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        @Override
14922c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        protected int getVerticalSnapPreference() {
14932c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // This is key for most of the scrolling logic that guarantees that scrolling
14942c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            // will settle with a view aligned to the top.
14952c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return LinearSmoothScroller.SNAP_TO_START;
14962c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
14972c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
14982c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        @Override
14992c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
15002c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START);
15012c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (dy == 0) {
15022c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                if (DEBUG) {
15032c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                    Log.d(TAG, "Scroll distance is 0");
15042c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                }
15052c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                return;
15062c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
15072c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
15082c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            final int time = calculateTimeForDeceleration(dy);
15092c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            if (time > 0) {
15102c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer                action.update(0, -dy, time, mInterpolator);
15112c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            }
15122c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
15132c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
15142c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        @Override
15152c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
15162c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
15172c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
15182c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
15192c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        @Override
15202c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        protected int calculateTimeForDeceleration(int dx) {
15212c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            int time = (int) Math.ceil(calculateTimeForScrolling(dx) / DECELERATION_TIME_DIVISOR);
15222c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return mHasTouch ? time : Math.min(time, NON_TOUCH_MAX_DECELERATION_MS);
15232c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
15242c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer
15252c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        public int getTargetPosition() {
15262c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer            return mTargetPosition;
15272c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer        }
15282c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer    }
15292c147cd63a518c3b779697d7b5cb53d86bfc2a00Rakesh Iyer}
1530