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