18b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen/* 28b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Copyright (C) 2015 The Android Open Source Project 38b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 48b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Licensed under the Apache License, Version 2.0 (the "License"); 58b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * you may not use this file except in compliance with the License. 68b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * You may obtain a copy of the License at 78b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 88b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * http://www.apache.org/licenses/LICENSE-2.0 98b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Unless required by applicable law or agreed to in writing, software 118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * distributed under the License is distributed on an "AS IS" BASIS, 128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * See the License for the specific language governing permissions and 148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * limitations under the License. 158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenpackage android.support.car.ui; 178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.content.Context; 198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.graphics.PointF; 208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.support.annotation.IntDef; 218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.support.annotation.NonNull; 228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.support.v7.widget.LinearSmoothScroller; 238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.support.v7.widget.RecyclerView; 248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.util.DisplayMetrics; 258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.util.Log; 268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.view.View; 278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.view.ViewGroup; 288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.view.animation.AccelerateInterpolator; 298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.view.animation.DecelerateInterpolator; 308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport android.view.animation.Interpolator; 318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport java.lang.annotation.Retention; 338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport java.lang.annotation.RetentionPolicy; 348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenimport java.util.ArrayList; 358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen/** 378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Custom {@link RecyclerView.LayoutManager} that behaves similar to LinearLayoutManager except that 388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * it has a few tricks up its sleeve. 398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <ol> 408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <li>In a normal ListView, when views reach the top of the list, they are clipped. In 418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * CarLayoutManager, views have the option of flying off of the top of the screen as the 428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * next row settles in to place. This functionality can be enabled or disabled with 438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * {@link #setOffsetRows(boolean)}. 448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle 458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * on the next page. {@link #FLING_THRESHOLD_TO_PAGINATE} and 468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * {@link #DRAG_DISTANCE_TO_PAGINATE} can be set to have the list settle on the next item 478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * instead of the next page for small gestures. 488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that 498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * the last page can be properly aligned. 508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * </ol> 518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * This LayoutManger should be used with {@link CarRecyclerView}. 5357de61296cc8f29d8740fc7e6983af9553e7a410Jason Tholstrup * @hide 548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chenpublic class CarLayoutManager extends RecyclerView.LayoutManager { 568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final String TAG = "CarLayoutManager"; 571b1247b5648975dd41ee73c25425825abb256234Vitalii Tomkiv private static final boolean DEBUG = false; 588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Any fling below the threshold will just scroll to the top fully visible row. The units is 618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * whatever {@link android.widget.Scroller} would return. 628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * A reasonable value is ~200 648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * This can be disabled by setting the threshold to -1. 668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int FLING_THRESHOLD_TO_PAGINATE = -1; 688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Any fling shorter than this threshold (in px) will just scroll to the top fully visible row. 718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * A reasonable value is 15. 738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * This can be disabled by setting the distance to -1. 758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int DRAG_DISTANCE_TO_PAGINATE = -1; 778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * If you scroll really quickly, you can hit the end of the laid out rows before Android has a 808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * chance to layout more. To help counter this, we can layout a number of extra rows past 818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * wherever the focus is if necessary. 828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS = 2; 848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Scroll bar calculation is a bit complicated. This basically defines the granularity we want 878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * our scroll bar to move. Set this to 1 means our scrollbar will have really jerky movement. 888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Setting it too big will risk an overflow (although there is no performance impact). Ideally 898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * we want to set this higher than the height of our list view. We can't use our list view 908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * height directly though because we might run into situations where getHeight() returns 0, for 918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * example, when the view is not yet measured. 928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int SCROLL_RANGE = 1000; 948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @ScrollStyle private final int SCROLL_TYPE = MARIO; 968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Retention(RetentionPolicy.SOURCE) 988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @IntDef({MARIO, SUPER_MARIO}) 998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private @interface ScrollStyle {} 1008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int MARIO = 0; 1018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int SUPER_MARIO = 1; 1028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Retention(RetentionPolicy.SOURCE) 1048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @IntDef({BEFORE, AFTER}) 1058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private @interface LayoutDirection {} 1068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int BEFORE = 0; 1078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int AFTER = 1; 1088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Retention(RetentionPolicy.SOURCE) 1108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @IntDef({ROW_OFFSET_MODE_INDIVIDUAL, ROW_OFFSET_MODE_PAGE}) 1118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public @interface RowOffsetMode {} 1128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public static final int ROW_OFFSET_MODE_INDIVIDUAL = 0; 1138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public static final int ROW_OFFSET_MODE_PAGE = 1; 1148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public interface OnItemsChangedListener { 1168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen void onItemsChanged(); 1178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 1188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private final AccelerateInterpolator mDanglingRowInterpolator = new AccelerateInterpolator(2); 1208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private final Context mContext; 1218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** Determines whether or not rows will be offset as they slide off screen **/ 1238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private boolean mOffsetRows = false; 1248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** Determines whether rows will be offset individually or a page at a time **/ 1258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @RowOffsetMode private int mRowOffsetMode = ROW_OFFSET_MODE_PAGE; 1268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 1288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * The LayoutManager only gets {@link #onScrollStateChanged(int)} updates. This enables the 1298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * scroll state to be used anywhere. 1308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 1318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mScrollState = RecyclerView.SCROLL_STATE_IDLE; 1328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 1338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Used to inspect the current scroll state to help with the various calculations. 1348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen **/ 1358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private CarSmoothScroller mSmoothScroller; 1368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private OnItemsChangedListener mItemsChangedListener; 1378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** The distance that the list has actually scrolled in the most recent drag gesture **/ 1398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mLastDragDistance = 0; 1408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** True if the current drag was limited/capped because it was at some boundary **/ 1418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private boolean mReachedLimitOfDrag; 1428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 1438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * The values are continuously updated to keep track of where the current page boundaries are 1448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * on screen. The anchor page break is the page break that is currently within or at the 1458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * top of the viewport. The Upper page break is the page break before it and the lower page 1468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * break is the page break after it. 1478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 1488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * A page break will be set to -1 if it is unknown or n/a. 1498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @see #updatePageBreakPositions() 1508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 1518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mItemCountDuringLastPageBreakUpdate; 152a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // The index of the first item on the current page 1538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mAnchorPageBreakPosition = 0; 154a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // The index of the first item on the previous page 1558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mUpperPageBreakPosition = -1; 156a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // The index of the first item on the next page 1578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mLowerPageBreakPosition = -1; 1588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** Used in the bookkeeping of mario style scrolling to prevent extra calculations. **/ 1598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mLastChildPositionToRequestFocus = -1; 1608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mSampleViewHeight = -1; 1618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 1638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Set the anchor to the following position on the next layout pass. 1648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 1658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int mPendingScrollPosition = -1; 1668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public CarLayoutManager(Context context) { 1688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mContext = context; 1698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 1708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 1728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public RecyclerView.LayoutParams generateDefaultLayoutParams() { 1738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return new RecyclerView.LayoutParams( 1748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen ViewGroup.LayoutParams.MATCH_PARENT, 1758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen ViewGroup.LayoutParams.WRAP_CONTENT); 1768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 1778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 1798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public boolean canScrollVertically() { 1808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 1818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 1828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 1838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 1848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * onLayoutChildren is sort of like a "reset" for the layout state. At a high level, it should: 1858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <ol> 1868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <li>Check the current views to get the current state of affairs 1878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <li>Detach all views from the window (a lightweight operation) so that rows 1888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * not re-added will be removed after onLayoutChildren. 1898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <li>Re-add rows as necessary. 1908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * </ol> 1918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 1928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @see super#onLayoutChildren(RecyclerView.Recycler, RecyclerView.State) 1938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 1948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 1958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 1968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 1978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * The anchor view is the first fully visible view on screen at the beginning 1988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * of onLayoutChildren (or 0 if there is none). This row will be laid out first. After that, 1998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * layoutNextRow will layout rows above and below it until the boundaries of what should 2008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * be laid out have been reached. See {@link #shouldLayoutNextRow(View, int)} for 2018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * more information. 2028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 2038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int anchorPosition = 0; 2048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int anchorTop = -1; 2058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mPendingScrollPosition == -1) { 2068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View anchor = getFirstFullyVisibleChild(); 2078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (anchor != null) { 2088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen anchorPosition = getPosition(anchor); 2098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen anchorTop = getDecoratedTop(anchor); 2108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 2128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen anchorPosition = mPendingScrollPosition; 2138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mPendingScrollPosition = -1; 2148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mAnchorPageBreakPosition = anchorPosition; 2158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mUpperPageBreakPosition = -1; 2168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLowerPageBreakPosition = -1; 2178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 2198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 2208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, String.format( 2218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen ":: onLayoutChildren anchorPosition:%s, anchorTop:%s," 2228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen + " mPendingScrollPosition: %s, mAnchorPageBreakPosition:%s," 2238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen + " mUpperPageBreakPosition:%s, mLowerPageBreakPosition:%s", 2248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen anchorPosition, anchorTop, mPendingScrollPosition, mAnchorPageBreakPosition, 2258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mUpperPageBreakPosition, mLowerPageBreakPosition)); 2268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 2288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 2298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Detach all attached view for 2 reasons: 2308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <ol> 2318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <li> So that views are put in the scrap heap. This enables us to call 2328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * {@link RecyclerView.Recycler#getViewForPosition(int)} which will either return 2338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * one of these detached views if it is in the scrap heap, one from the 2348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * recycled pool (will only call onBind in the adapter), or create an entirely new 2358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * row if needed (will call onCreate and onBind in the adapter). 2368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * <li> So that views are automatically removed if they are not manually re-added. 2378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * </ol> 2388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 2398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen detachAndScrapAttachedViews(recycler); 2408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 2418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Layout new rows. 2428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View anchor = layoutAnchor(recycler, anchorPosition, anchorTop); 2438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (anchor != null) { 2448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View adjacentRow = anchor; 2458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) { 2468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE); 2478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen adjacentRow = anchor; 2498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen while (shouldLayoutNextRow(state, adjacentRow, AFTER)) { 2508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER); 2518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 2548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen updatePageBreakPositions(); 2558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen offsetRows(); 2568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 2578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG&& getChildCount() > 1) { 2588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, "Currently showing " + getChildCount() + " views " + 2598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen getPosition(getChildAt(0)) + " to " + 2608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen getPosition(getChildAt(getChildCount() - 1)) + " anchor " + anchorPosition); 2618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 2648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 2658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * scrollVerticallyBy does the work of what should happen when the list scrolls in addition 2668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * to handling cases where the list hits the end. It should be lighter weight than 2678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * onLayoutChildren. It doesn't have to detach all views. It only looks at the end of the list 2688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * and removes views that have gone out of bounds and lays out new ones that scroll in. 2698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 2708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param dy The amount that the list is supposed to scroll. 2718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * > 0 means the list is scrolling down. 2728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * < 0 means the list is scrolling up. 2738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param recycler The recycler that enables views to be reused or created as they scroll in. 2748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param state Various information about the current state of affairs. 2758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The amount the list actually scrolled. 2768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 2778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @see super#scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State) 2788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 2798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 2808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int scrollVerticallyBy( 2818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int dy, @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) { 2828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the list is empty, we can prevent the overscroll glow from showing by just 2838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // telling RecycerView that we scrolled. 2848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getItemCount() == 0) { 2858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return dy; 2868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 2888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Prevent redundant computations if there is definitely nowhere to scroll to. 2898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getChildCount() <= 1 || dy == 0) { 2908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return 0; 2918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 2938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View firstChild = getChildAt(0); 2948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (firstChild == null) { 2958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return 0; 2968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 2978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstChildPosition = getPosition(firstChild); 2988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams firstChildParams = getParams(firstChild); 2998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstChildTopWithMargin = getDecoratedTop(firstChild) - firstChildParams.topMargin; 3008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View lastFullyVisibleView = getChildAt(getLastFullyVisibleChildIndex()); 3028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (lastFullyVisibleView == null) { 3038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return 0; 3048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen boolean isLastViewVisible = getPosition(lastFullyVisibleView) == getItemCount() - 1; 3068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View firstFullyVisibleChild = getFirstFullyVisibleChild(); 3088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (firstFullyVisibleChild == null) { 3098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return 0; 3108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstFullyVisiblePosition = getPosition(firstFullyVisibleChild); 3128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams firstFullyVisibleChildParams = getParams(firstFullyVisibleChild); 3138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int topRemainingSpace = getDecoratedTop(firstFullyVisibleChild) 3148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen - firstFullyVisibleChildParams.topMargin - getPaddingTop(); 3158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (isLastViewVisible && firstFullyVisiblePosition == mAnchorPageBreakPosition 3178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen && dy > topRemainingSpace && dy > 0) { 3188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Prevent dragging down more than 1 page. As a side effect, this also prevents you 3198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // from dragging past the bottom because if you are on the second to last page, it 3208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // prevents you from dragging past the last page. 3218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen dy = topRemainingSpace; 3228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mReachedLimitOfDrag = true; 3238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (dy < 0 && firstChildPosition == 0 3248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen && firstChildTopWithMargin + Math.abs(dy) > getPaddingTop()) { 3258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Prevent scrolling past the beginning 3268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen dy = firstChildTopWithMargin - getPaddingTop(); 3278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mReachedLimitOfDrag = true; 3288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 3298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mReachedLimitOfDrag = false; 3308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen boolean isDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING; 3338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (isDragging) { 3348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLastDragDistance += dy; 3358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // We offset by -dy because the views translate in the opposite direction that the 3378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // list scrolls (think about it.) 3388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen offsetChildrenVertical(-dy); 3398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 340a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // The last item in the layout should never scroll above the viewport 341a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan View view = getChildAt(getChildCount() - 1); 342a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan if (view.getTop() < 0) { 343a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan view.setTop(0); 344a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan } 345a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan 3468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // This is the meat of this function. We remove views on the trailing edge of the scroll 3478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // and add views at the leading edge as necessary. 3488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View adjacentRow; 3498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (dy > 0) { 3508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen recycleChildrenFromStart(recycler); 3518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen adjacentRow = getChildAt(getChildCount() - 1); 3528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen while (shouldLayoutNextRow(state, adjacentRow, AFTER)) { 3538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER); 3548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 3568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen recycleChildrenFromEnd(recycler); 3578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen adjacentRow = getChildAt(0); 3588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) { 3598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE); 3608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Now that the correct views are laid out, offset rows as necessary so we can do whatever 3638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // fancy animation we want such as having the top view fly off the screen as the next one 3648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // settles in to place. 3658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen updatePageBreakPositions(); 3668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen offsetRows(); 3678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getChildCount() > 1) { 3698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 3708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, String.format("Currently showing %d views (%d to %d)", 3718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen getChildCount(), getPosition(getChildAt(0)), 3728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen getPosition(getChildAt(getChildCount() - 1)))); 3738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return dy; 3778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 3808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void scrollToPosition(int position) { 3818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mPendingScrollPosition = position; 3828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen requestLayout(); 3838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 3868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void smoothScrollToPosition( 3878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView recyclerView, RecyclerView.State state, int position) { 3888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 3898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * startSmoothScroll will handle stopping the old one if there is one. 3908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * We only keep a copy of it to handle the translation of rows as they slide off the screen 3918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * in {@link #offsetRowsWithPageBreak()} 3928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 3938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mSmoothScroller = new CarSmoothScroller(mContext, position); 3948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mSmoothScroller.setTargetPosition(position); 3958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen startSmoothScroll(mSmoothScroller); 3968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 3978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 3988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 3998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Miscellaneous bookkeeping. 4008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 4018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 4028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void onScrollStateChanged(int state) { 4038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 4048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, ":: onScrollStateChanged " + state); 4058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (state == RecyclerView.SCROLL_STATE_IDLE) { 4078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the focused view is off screen, give focus to one that is. 4088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the first fully visible view is first in the list, focus the first item. 4098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Otherwise, focus the second so that you have the first item as scrolling context. 4108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View focusedChild = getFocusedChild(); 4118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedChild != null 4128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen && (getDecoratedTop(focusedChild) >= getHeight() - getPaddingBottom() 4138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen || getDecoratedBottom(focusedChild) <= getPaddingTop())) { 4148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen focusedChild.clearFocus(); 4158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen requestLayout(); 4168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (state == RecyclerView.SCROLL_STATE_DRAGGING) { 4198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLastDragDistance = 0; 4208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (state != RecyclerView.SCROLL_STATE_SETTLING) { 4238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mSmoothScroller = null; 4248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mScrollState = state; 4278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen updatePageBreakPositions(); 4288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 4318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void onItemsChanged(RecyclerView recyclerView) { 4328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen super.onItemsChanged(recyclerView); 4338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mItemsChangedListener != null) { 4348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mItemsChangedListener.onItemsChanged(); 4358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // When item changed, our sample view height is no longer accurate, and need to be 4378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // recomputed. 4388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mSampleViewHeight = -1; 4398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 4428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Gives us the opportunity to override the order of the focused views. 4438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * By default, it will just go from top to bottom. However, if there is no focused views, we 4448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * take over the logic and start the focused views from the middle of what is visible and move 4458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * from there until the end of the laid out views in the specified direction. 4468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 4478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 4488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public boolean onAddFocusables( 4498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) { 4508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View focusedChild = getFocusedChild(); 4518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedChild != null) { 4528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If there is a view that already has focus, we can just return false and the normal 4538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Android addFocusables will work fine. 4548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 4558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Now we know that there isn't a focused view. We need to set up focusables such that 4588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // instead of just focusing the first item that has been laid out, it focuses starting 4598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // from a visible item. 4608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex(); 4628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (firstFullyVisibleChildIndex == -1) { 4638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Somehow there is a focused view but there is no fully visible view. There shouldn't 4648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // be a way for this to happen but we'd better stop here and return instead of 4658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // continuing on with -1. 4668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.w(TAG, "There is a focused child but no first fully visible child."); 4678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 4688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View firstFullyVisibleChild = getChildAt(firstFullyVisibleChildIndex); 4708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstFullyVisibleChildPosition = getPosition(firstFullyVisibleChild); 4718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstFocusableChildIndex = firstFullyVisibleChildIndex; 4738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (firstFullyVisibleChildPosition > 0 && firstFocusableChildIndex + 1 < getItemCount()) { 4748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // We are somewhere in the middle of the list. Instead of starting focus on the first 4758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // item, start focus on the second item to give some context that we aren't at 4768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // the beginning. 4778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen firstFocusableChildIndex++; 4788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (direction == View.FOCUS_FORWARD) { 4818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Iterate from the first focusable view to the end. 4828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = firstFocusableChildIndex; i < getChildCount(); i++) { 4838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen views.add(getChildAt(i)); 4848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 4868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (direction == View.FOCUS_BACKWARD) { 4878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Iterate from the first focusable view to the beginning. 4888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = firstFocusableChildIndex; i >= 0; i--) { 4898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen views.add(getChildAt(i)); 4908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 4928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 4948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 4958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 4968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 4978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, 4988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.State state) { 4998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return null; 5008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 5038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * This is the function that decides where to scroll to when a new view is focused. 5048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * You can get the position of the currently focused child through the child parameter. 5058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Once you have that, determine where to smooth scroll to and scroll there. 5068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 5078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param parent The RecyclerView hosting this LayoutManager 5088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param state Current state of RecyclerView 5098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param child Direct child of the RecyclerView containing the newly focused view 5108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param focused The newly focused view. This may be the same view as child or it may be null 5118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return true if the default scroll behavior should be suppressed 5128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 5138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 5148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public boolean onRequestChildFocus(RecyclerView parent, RecyclerView.State state, 5158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View child, View focused) { 5168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (child == null) { 5178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.w(TAG, "onRequestChildFocus with a null child!"); 5188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 5198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 5228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, String.format(":: onRequestChildFocus child: %s, focused: %s", child, 5238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen focused)); 5248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // We have several distinct scrolling methods. Each implementation has been delegated 5278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // to its own method. 5288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (SCROLL_TYPE == MARIO) { 5298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return onRequestChildFocusMarioStyle(parent, child); 5308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (SCROLL_TYPE == SUPER_MARIO) { 5318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return onRequestChildFocusSuperMarioStyle(parent, state, child); 5328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 5338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen throw new IllegalStateException("Unknown scroll type (" + SCROLL_TYPE + ")"); 5348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 5388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Goal: the scrollbar maintains the same size throughout scrolling and that the scrollbar 5398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * reaches the bottom of the screen when the last item is fully visible. This is because 5408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * there are multiple points that could be considered the bottom since the last item can scroll 5418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * past the bottom edge of the screen. 5428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 5438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * To find the extent, we divide the number of items that can fit on screen by the number of 5448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * items in total. 5458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 5468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 5478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int computeVerticalScrollExtent(RecyclerView.State state) { 5488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getChildCount() <= 1) { 5498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return 0; 5508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int sampleViewHeight = getSampleViewHeight(); 5538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int availableHeight = getAvailableHeight(); 5548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int sampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight; 5558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (state.getItemCount() <= sampleViewsThatCanFitOnScreen) { 5578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return SCROLL_RANGE; 5588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 5598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return SCROLL_RANGE * sampleViewsThatCanFitOnScreen / state.getItemCount(); 5608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 5648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * The scrolling offset is calculated by determining what position is at the top of the list. 5658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * However, instead of using fixed integer positions for each row, the scroll position is 5668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * factored in and the position is recalculated as a float that takes in to account the 5678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * current scroll state. This results in a smooth animation for the scrollbar when the user 5688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * scrolls the list. 5698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 5708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 5718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int computeVerticalScrollOffset(RecyclerView.State state) { 5728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View firstChild = getFirstFullyVisibleChild(); 5738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (firstChild == null) { 5748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return 0; 5758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(firstChild); 5788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstChildPosition = getPosition(firstChild); 5798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Assume the previous view is the same height as the current one. 5818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen float percentOfPreviousViewShowing = (getDecoratedTop(firstChild) - params.topMargin) 5828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen / (float) (getDecoratedMeasuredHeight(firstChild) 5838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen + params.topMargin + params.bottomMargin); 5848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the previous view is actually larger than the current one then this the percent 5858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // can be greater than 1. 5868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen percentOfPreviousViewShowing = Math.min(percentOfPreviousViewShowing, 1); 5878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen float currentPosition = (float) firstChildPosition - percentOfPreviousViewShowing; 5898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int sampleViewHeight = getSampleViewHeight(); 5918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int availableHeight = getAvailableHeight(); 5928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int numberOfSampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight; 5938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int positionWhenLastItemIsVisible = 5948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen state.getItemCount() - numberOfSampleViewsThatCanFitOnScreen; 5958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 5968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (positionWhenLastItemIsVisible <= 0) { 5978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return 0; 5988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 5998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (currentPosition >= positionWhenLastItemIsVisible) { 6018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return SCROLL_RANGE; 6028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return (int) (SCROLL_RANGE * currentPosition / positionWhenLastItemIsVisible); 6058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 6088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * The range of the scrollbar can be understood as the granularity of how we want the 6098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * scrollbar to scroll. 6108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 6118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 6128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int computeVerticalScrollRange(RecyclerView.State state) { 6138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return SCROLL_RANGE; 6148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 6178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The first view that starts on screen. It assumes that it fully fits on the screen 6188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * though. If the first fully visible child is also taller than the screen then it will 6198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * still be returned. However, since the LayoutManager snaps to view starts, having 6208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * a row that tall would lead to a broken experience anyways. 6218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 6228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int getFirstFullyVisibleChildIndex() { 6238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = 0; i < getChildCount(); i++) { 6248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View child = getChildAt(i); 6258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(child); 6268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getDecoratedTop(child) - params.topMargin >= getPaddingTop()) { 6278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return i; 6288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return -1; 6318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public View getFirstFullyVisibleChild() { 6348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex(); 6358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View firstChild = null; 6368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (firstFullyVisibleChildIndex != -1) { 6378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen firstChild = getChildAt(firstFullyVisibleChildIndex); 6388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return firstChild; 6408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 6438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The last view that ends on screen. It assumes that the start is also on screen 6448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * though. If the last fully visible child is also taller than the screen then it will 6458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * still be returned. However, since the LayoutManager snaps to view starts, having 6468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * a row that tall would lead to a broken experience anyways. 6478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 6488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int getLastFullyVisibleChildIndex() { 6498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = getChildCount() - 1; i >= 0; i--) { 6508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View child = getChildAt(i); 6518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(child); 6528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childBottom = getDecoratedBottom(child) + params.bottomMargin; 6538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int listBottom = getHeight() - getPaddingBottom(); 6548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (childBottom <= listBottom) { 6558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return i; 6568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return -1; 6598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 6628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return Whether or not the first view is fully visible. 6638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 6648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public boolean isAtTop() { 6658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // getFirstFullyVisibleChildIndex() can return -1 which indicates that there are no views 6668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // and also means that the list is at the top. 6678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return getFirstFullyVisibleChildIndex() <= 0; 6688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 6718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return Whether or not the last view is fully visible. 6728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 6738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public boolean isAtBottom() { 6748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex(); 6758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (lastFullyVisibleChildIndex == -1) { 6768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 6778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View lastFullyVisibleChild = getChildAt(lastFullyVisibleChildIndex); 6798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return getPosition(lastFullyVisibleChild) == getItemCount() - 1; 6808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void setOffsetRows(boolean offsetRows) { 6838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mOffsetRows = offsetRows; 6848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (offsetRows) { 6858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen offsetRows(); 6868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 6878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childCount = getChildCount(); 6888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = 0; i < childCount; i++) { 6898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen getChildAt(i).setTranslationY(0); 6908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 6948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void setRowOffsetMode(@RowOffsetMode int mode) { 6958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mode == mRowOffsetMode) { 6968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return; 6978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 6988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mRowOffsetMode = mode; 6998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen offsetRows(); 7008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void setItemsChangedListener(OnItemsChangedListener listener) { 7038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mItemsChangedListener = listener; 7048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 7078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Finish the pagination taking into account where the gesture started (not where we are now). 7088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 7098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return Whether the list was scrolled as a result of the fling. 7108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 7118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public boolean settleScrollForFling(RecyclerView parent, int flingVelocity) { 7128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getChildCount() == 0) { 7138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 7148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mReachedLimitOfDrag) { 7178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 7188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the fling was too slow or too short, settle on the first fully visible row instead. 7218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (Math.abs(flingVelocity) <= FLING_THRESHOLD_TO_PAGINATE 7228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen || Math.abs(mLastDragDistance) <= DRAG_DISTANCE_TO_PAGINATE) { 7238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex(); 7248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (firstFullyVisibleChildIndex != -1) { 7258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int scrollPosition = getPosition(getChildAt(firstFullyVisibleChildIndex)); 7268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen parent.smoothScrollToPosition(scrollPosition); 7278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 7288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 7308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Finish the pagination taking into account where the gesture 7338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // started (not where we are now). 7348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen boolean isDownGesture = flingVelocity > 0 7358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen || (flingVelocity == 0 && mLastDragDistance >= 0); 7368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen boolean isUpGesture = flingVelocity < 0 7378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen || (flingVelocity == 0 && mLastDragDistance < 0); 7388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (isDownGesture && mLowerPageBreakPosition != -1) { 7398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the last view is fully visible then only settle on the first fully visible view 7408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // instead of the original page down position. However, don't page down if the last 7418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // item has come fully into view. 7428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen parent.smoothScrollToPosition(mAnchorPageBreakPosition); 7438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 7448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (isUpGesture && mUpperPageBreakPosition != -1) { 7458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen parent.smoothScrollToPosition(mUpperPageBreakPosition); 7468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 7478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 7488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.e(TAG, "Error setting scroll for fling! flingVelocity: \t" + flingVelocity + 7498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen "\tlastDragDistance: " + mLastDragDistance + "\tpageUpAtStartOfDrag: " + 7508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mUpperPageBreakPosition + "\tpageDownAtStartOfDrag: " + 7518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLowerPageBreakPosition); 7528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // As a last resort, at the last smooth scroller target position if there is one. 7538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mSmoothScroller != null) { 7548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen parent.smoothScrollToPosition(mSmoothScroller.getTargetPosition()); 7558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 7568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 7598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 7628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The position that paging up from the current position would settle at. 7638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 7648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int getPageUpPosition() { 7658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return mUpperPageBreakPosition; 7668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 7698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The position that paging down from the current position would settle at. 7708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 7718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int getPageDownPosition() { 7728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return mLowerPageBreakPosition; 7738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 7768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Layout the anchor row. The anchor row is the first fully visible row. 7778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 7788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param anchorTop The decorated top of the anchor. If it is not known or should be reset 7798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * to the top, pass -1. 7808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 7818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private View layoutAnchor(RecyclerView.Recycler recycler, int anchorPosition, int anchorTop) { 7828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (anchorPosition > getItemCount() - 1) { 7838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return null; 7848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View anchor = recycler.getViewForPosition(anchorPosition); 7868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(anchor); 7878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen measureChildWithMargins(anchor, 0, 0); 7888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int left = getPaddingLeft() + params.leftMargin; 7898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int top = (anchorTop == -1) ? params.topMargin : anchorTop; 7908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int right = left + getDecoratedMeasuredWidth(anchor); 7918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int bottom = top + getDecoratedMeasuredHeight(anchor); 7928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen layoutDecorated(anchor, left, top, right, bottom); 7938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen addView(anchor); 7948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return anchor; 7958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 7968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 7978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 7988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Lays out the next row in the specified direction next to the specified adjacent row. 7998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 8008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param recycler The recycler from which a new view can be created. 8018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param adjacentRow The View of the adjacent row which will be used to position the new one. 8028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @param layoutDirection The side of the adjacent row that the new row will be laid out on. 8038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 8048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The new row that was laid out. 8058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 8068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private View layoutNextRow(RecyclerView.Recycler recycler, View adjacentRow, 8078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @LayoutDirection int layoutDirection) { 8088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int adjacentRowPosition = getPosition(adjacentRow); 8108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int newRowPosition = adjacentRowPosition; 8118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (layoutDirection == BEFORE) { 8128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen newRowPosition = adjacentRowPosition - 1; 8138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (layoutDirection == AFTER) { 8148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen newRowPosition = adjacentRowPosition + 1; 8158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Because we detach all rows in onLayoutChildren, this will often just return a view from 8188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // the scrap heap. 8198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View newRow = recycler.getViewForPosition(newRowPosition); 8208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen measureChildWithMargins(newRow, 0, 0); 8228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams newRowParams = 8238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen (RecyclerView.LayoutParams) newRow.getLayoutParams(); 8248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams adjacentRowParams = 8258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen (RecyclerView.LayoutParams) adjacentRow.getLayoutParams(); 8268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int left = getPaddingLeft() + newRowParams.leftMargin; 8278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int right = left + getDecoratedMeasuredWidth(newRow); 8288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int top, bottom; 8298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (layoutDirection == BEFORE) { 8308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen bottom = adjacentRow.getTop() - adjacentRowParams.topMargin - newRowParams.bottomMargin; 8318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen top = bottom - getDecoratedMeasuredHeight(newRow); 8328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 8338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen top = getDecoratedBottom(adjacentRow) + 8348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen adjacentRowParams.bottomMargin + newRowParams.topMargin; 8358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen bottom = top + getDecoratedMeasuredHeight(newRow); 8368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen layoutDecorated(newRow, left, top, right, bottom); 8388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (layoutDirection == BEFORE) { 8408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen addView(newRow, 0); 8418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 8428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen addView(newRow); 8438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return newRow; 8468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 8498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return Whether another row should be laid out in the specified direction. 8508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 8518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private boolean shouldLayoutNextRow(RecyclerView.State state, View adjacentRow, 8528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @LayoutDirection int layoutDirection) { 8538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int adjacentRowPosition = getPosition(adjacentRow); 8548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (layoutDirection == BEFORE) { 8568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (adjacentRowPosition == 0) { 8578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // We already laid out the first row. 8588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 8598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (layoutDirection == AFTER) { 8618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (adjacentRowPosition >= state.getItemCount() - 1) { 8628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // We already laid out the last row. 8638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 8648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If we are scrolling layout views until the target position. 8688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mSmoothScroller != null) { 8698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (layoutDirection == BEFORE 8708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen && adjacentRowPosition >= mSmoothScroller.getTargetPosition()) { 8718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 8728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (layoutDirection == AFTER 8738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen && adjacentRowPosition <= mSmoothScroller.getTargetPosition()) { 8748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 8758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View focusedRow = getFocusedChild(); 8798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedRow != null) { 8808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int focusedRowPosition = getPosition(focusedRow); 8818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (layoutDirection == BEFORE && adjacentRowPosition 8828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen >= focusedRowPosition - NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) { 8838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 8848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (layoutDirection == AFTER && adjacentRowPosition 8858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen <= focusedRowPosition + NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) { 8868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 8878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 8898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 8908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(adjacentRow); 8918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int adjacentRowTop = getDecoratedTop(adjacentRow) - params.topMargin; 8928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int adjacentRowBottom = getDecoratedBottom(adjacentRow) - params.bottomMargin; 8938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (layoutDirection == BEFORE 8948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen && adjacentRowTop < getPaddingTop() - getHeight()) { 8958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // View is more than 1 page past the top of the screen and also past where the user has 8968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // scrolled to. We want to keep one page past the top to make the scroll up calculation 8978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // easier and scrolling smoother. 8988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 8998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (layoutDirection == AFTER 9008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen && adjacentRowBottom > getHeight() - getPaddingBottom()) { 9018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // View is off of the bottom and also past where the user has scrolled to. 9028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return false; 9038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 9068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 9098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Remove and recycle views that are no longer needed. 9108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 9118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private void recycleChildrenFromStart(RecyclerView.Recycler recycler) { 9128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Start laying out children one page before the top of the viewport. 9138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childrenStart = getPaddingTop() - getHeight(); 9148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int focusedChildPosition = Integer.MAX_VALUE; 9168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View focusedChild = getFocusedChild(); 9178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedChild != null) { 9188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen focusedChildPosition = getPosition(focusedChild); 9198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Count the number of views that should be removed. 9228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int detachedCount = 0; 9238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childCount = getChildCount(); 9248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = 0; i < childCount; i++) { 9258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen final View child = getChildAt(i); 9268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childEnd = getDecoratedBottom(child); 9278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childPosition = getPosition(child); 9288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (childEnd >= childrenStart || childPosition >= focusedChildPosition - 1) { 9308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen break; 9318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen detachedCount++; 9348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Remove the number of views counted above. Done by removing the first child n times. 9378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen while (--detachedCount >= 0) { 9388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen final View child = getChildAt(0); 9398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen removeAndRecycleView(child, recycler); 9408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 9448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Remove and recycle views that are no longer needed. 9458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 9468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private void recycleChildrenFromEnd(RecyclerView.Recycler recycler) { 9478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Layout views until the end of the viewport. 9488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childrenEnd = getHeight(); 9498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int focusedChildPosition = Integer.MIN_VALUE + 1; 9518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View focusedChild = getFocusedChild(); 9528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedChild != null) { 9538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen focusedChildPosition = getPosition(focusedChild); 9548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Count the number of views that should be removed. 9578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstDetachedPos = 0; 9588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int detachedCount = 0; 9598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childCount = getChildCount(); 9608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = childCount - 1; i >= 0; i--) { 9618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen final View child = getChildAt(i); 9628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childStart = getDecoratedTop(child); 9638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childPosition = getPosition(child); 9648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (childStart <= childrenEnd || childPosition <= focusedChildPosition - 1) { 9668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen break; 9678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen firstDetachedPos = i; 9708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen detachedCount++; 9718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen while (--detachedCount >= 0) { 9748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen final View child = getChildAt(firstDetachedPos); 9758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen removeAndRecycleView(child, recycler); 9768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 9808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Offset rows to do fancy animations. If {@link #mOffsetRows} is false, this will do nothing. 9818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 9828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @see #offsetRowsIndividually() 9838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @see #offsetRowsByPage() 9848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 9858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public void offsetRows() { 9868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (!mOffsetRows) { 9878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return; 9888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mRowOffsetMode == ROW_OFFSET_MODE_PAGE) { 9918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen offsetRowsByPage(); 9928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (mRowOffsetMode == ROW_OFFSET_MODE_INDIVIDUAL) { 9938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen offsetRowsIndividually(); 9948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 9968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 9978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 9988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Offset the single row that is scrolling off the screen such that by the time the next row 9998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * reaches the top, it will have accelerated completely off of the screen. 10008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 10018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private void offsetRowsIndividually() { 10028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getChildCount() == 0) { 10038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 10048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.d(TAG, ":: offsetRowsIndividually getChildCount=0"); 10058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return; 10078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Identify the dangling row. It will be the first row that is at the top of the 10108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // list or above. 10118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int danglingChildIndex = -1; 10128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = getChildCount() - 1; i >= 0; i--) { 10138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View child = getChildAt(i); 10148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getDecoratedTop(child) - getParams(child).topMargin <= getPaddingTop()) { 10158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen danglingChildIndex = i; 10168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen break; 10178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mAnchorPageBreakPosition = danglingChildIndex; 10218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 10238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, ":: offsetRowsIndividually danglingChildIndex: " + danglingChildIndex); 10248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Calculate the total amount that the view will need to scroll in order to go completely 10278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // off screen. 10288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView rv = (RecyclerView) getChildAt(0).getParent(); 10298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int[] locs = new int[2]; 10308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen rv.getLocationInWindow(locs); 10318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int listTopInWindow = locs[1] + rv.getPaddingTop(); 10328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int maxDanglingViewTranslation; 10338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childCount = getChildCount(); 10358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = 0; i < childCount; i++) { 10368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View child = getChildAt(i); 10378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(child); 10388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen maxDanglingViewTranslation = listTopInWindow; 10408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the child has a negative margin, we'll actually need to translate the view a 10418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // little but further to get it completely off screen. 10428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (params.topMargin < 0) { 10438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen maxDanglingViewTranslation -= params.topMargin; 10448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (params.bottomMargin < 0) { 10468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen maxDanglingViewTranslation -= params.bottomMargin; 10478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (i < danglingChildIndex) { 10508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setAlpha(0f); 10518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (i > danglingChildIndex) { 10528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setAlpha(1f); 10538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setTranslationY(0); 10548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 10558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int totalScrollDistance = getDecoratedMeasuredHeight(child) + 10568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen params.topMargin + params.bottomMargin; 10578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int distanceLeftInScroll = getDecoratedBottom(child) + 10598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen params.bottomMargin - getPaddingTop(); 10608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen float percentageIntoScroll = 1 - distanceLeftInScroll / (float) totalScrollDistance; 10618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen float interpolatedPercentage = 10628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mDanglingRowInterpolator.getInterpolation(percentageIntoScroll); 10638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setAlpha(1f); 10658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setTranslationY(-(maxDanglingViewTranslation * interpolatedPercentage)); 10668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 10718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * When the list scrolls, the entire page of rows will offset in one contiguous block. This 10728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * significantly reduces the amount of extra motion at the top of the screen. 10738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 10748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private void offsetRowsByPage() { 10758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View anchorView = findViewByPosition(mAnchorPageBreakPosition); 10768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (anchorView == null) { 10778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 10788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.d(TAG, ":: offsetRowsByPage anchorView null"); 10798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return; 10818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int anchorViewTop = getDecoratedTop(anchorView) - getParams(anchorView).topMargin; 10838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition); 10858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int upperViewTop = getDecoratedTop(upperPageBreakView) 10868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen - getParams(upperPageBreakView).topMargin; 10878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int scrollDistance = upperViewTop - anchorViewTop; 10898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int distanceLeft = anchorViewTop - getPaddingTop(); 10918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen float scrollPercentage = (Math.abs(scrollDistance) - distanceLeft) 10928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen / (float) Math.abs(scrollDistance); 10938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 10948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 10958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.d(TAG, String.format( 10968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen ":: offsetRowsByPage scrollDistance:%s, distanceLeft:%s, scrollPercentage:%s", 10978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen scrollDistance, distanceLeft, scrollPercentage)); 10988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 10998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Calculate the total amount that the view will need to scroll in order to go completely 11018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // off screen. 11028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView rv = (RecyclerView) getChildAt(0).getParent(); 11038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int[] locs = new int[2]; 11048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen rv.getLocationInWindow(locs); 11058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int listTopInWindow = locs[1] + rv.getPaddingTop(); 11068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childCount = getChildCount(); 11088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = 0; i < childCount; i++) { 11098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View child = getChildAt(i); 11108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int position = getPosition(child); 11118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (position < mUpperPageBreakPosition) { 11128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setAlpha(0f); 11138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setTranslationY(-listTopInWindow); 11148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (position < mAnchorPageBreakPosition) { 11158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the child has a negative margin, we need to offset the row by a little bit 11168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // extra so that it moves completely off screen. 11178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(child); 11188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int extraTranslation = 0; 11198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (params.topMargin < 0) { 11208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen extraTranslation -= params.topMargin; 11218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (params.bottomMargin < 0) { 11238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen extraTranslation -= params.bottomMargin; 11248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int translation = (int) ((listTopInWindow + extraTranslation) 11268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * mDanglingRowInterpolator.getInterpolation(scrollPercentage)); 11278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setAlpha(1f); 11288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setTranslationY(-translation); 11298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 11308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setAlpha(1f); 11318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen child.setTranslationY(0); 11328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 11378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Update the page break positions based on the position of the views on screen. This should 11388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * be called whenever view move or change such as during a scroll or layout. 11398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 11408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private void updatePageBreakPositions() { 11418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getChildCount() == 0) { 11428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 11438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.d(TAG, ":: updatePageBreakPosition getChildCount: 0"); 11448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return; 11468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 11498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, String.format(":: #BEFORE updatePageBreakPositions " + 11508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, " 11518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen + "mLowerPageBreakPosition:%s", 11528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition)); 11538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // If the item count has changed, our page boundaries may no longer be accurate. This will 11568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // force the page boundaries to reset around the current view that is closest to the top. 11578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getItemCount() != mItemCountDuringLastPageBreakUpdate) { 11588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 11598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.d(TAG, "Item count changed. Resetting page break positions."); 11608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mAnchorPageBreakPosition = getPosition(getFirstFullyVisibleChild()); 11628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mItemCountDuringLastPageBreakUpdate = getItemCount(); 11648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mAnchorPageBreakPosition == -1) { 11668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.w(TAG, "Unable to update anchor positions. There is no anchor position."); 11678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return; 11688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View anchorPageBreakView = findViewByPosition(mAnchorPageBreakPosition); 11718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (anchorPageBreakView == null) { 11728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return; 11738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int topMargin = getParams(anchorPageBreakView).topMargin; 11758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int anchorTop = getDecoratedTop(anchorPageBreakView) - topMargin; 11768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition); 11778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int upperPageBreakTop = upperPageBreakView == null ? Integer.MIN_VALUE : 11788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin; 11798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 11818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, String.format(":: #MID updatePageBreakPositions topMargin:%s, anchorTop:%s" 1182a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, " 11838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen + "mLowerPageBreakPosition:%s", topMargin, anchorTop, 11848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition)); 11858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 11868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 11878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (anchorTop < getPaddingTop()) { 11888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // The anchor has moved above the viewport. We are now on the next page. Shift the page 11898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // break positions and calculate a new lower one. 11908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mUpperPageBreakPosition = mAnchorPageBreakPosition; 11918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mAnchorPageBreakPosition = mLowerPageBreakPosition; 11928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition); 11938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (mAnchorPageBreakPosition > 0 && upperPageBreakTop >= getPaddingTop()) { 11948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // The anchor has moved below the viewport. We are now on the previous page. Shift 11958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // the page break positions and calculate a new upper one. 11968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLowerPageBreakPosition = mAnchorPageBreakPosition; 11978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mAnchorPageBreakPosition = mUpperPageBreakPosition; 11988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition); 11998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 12008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition); 12018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition); 12028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 12058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.v(TAG, String.format(":: #AFTER updatePageBreakPositions " + 12068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, " 12078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen + "mLowerPageBreakPosition:%s", 12088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition)); 12098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 12138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The page break position of the page before the anchor page break position. However, 12148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * if it reaches the end of the laid out children or position 0, it will just return 12158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * that. 12168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 12178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int calculatePreviousPageBreakPosition(int position) { 12188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (position == -1) { 12198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return -1; 12208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View referenceView = findViewByPosition(position); 12228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin; 12238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int previousPagePosition = position; 12258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen while (previousPagePosition > 0) { 12268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen previousPagePosition--; 12278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View child = findViewByPosition(previousPagePosition); 12288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (child == null) { 12298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // View has not been laid out yet. 12308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return previousPagePosition + 1; 12318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childTop = getDecoratedTop(child) - getParams(child).topMargin; 12348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (childTop < referenceViewTop - getHeight()) { 12368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return previousPagePosition + 1; 12378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Beginning of the list. 12408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return 0; 12418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 12448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The page break position of the next page after the anchor page break position. 12458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * However, if it reaches the end of the laid out children or end of the list, it will 12468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * just return that. 12478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 12488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int calculateNextPageBreakPosition(int position) { 12498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (position == -1) { 12508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return -1; 12518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View referenceView = findViewByPosition(position); 12548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (referenceView == null) { 12558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return position; 12568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin; 12588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int nextPagePosition = position; 1260a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan 1261a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // Search for the first child item after the referenceView that didn't fully fit on to the 1262a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // screen. The next page should start from the item before this child, so that users have 1263a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // a visual anchoring point of the page change. 12648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen while (position < getItemCount() - 1) { 12658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen nextPagePosition++; 12668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View child = findViewByPosition(nextPagePosition); 12678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (child == null) { 12688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // The next view has not been laid out yet. 12698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return nextPagePosition - 1; 12708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childBottom = getDecoratedBottom(child) + getParams(child).bottomMargin; 12738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (childBottom - referenceViewTop > getHeight() - getPaddingTop()) { 1274a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // If choosing the previous child causes the view to snap back to the referenceView 1275a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // position, then skip that and go directly to the child. This avoids the case 1276a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // where a tall card in the layout causes the view to constantly snap back to 1277a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan // the top when scrolled. 1278a32c79c66f4a134e6650a1acf307523221110e4fVictor Chan return nextPagePosition - 1 == position ? nextPagePosition : nextPagePosition - 1; 12798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // End of the list. 12828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return nextPagePosition; 12838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 12868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * In this style, the focus will scroll down to the middle of the screen and lock there 12878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * so that moving in either direction will move the entire list by 1. 12888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 12898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private boolean onRequestChildFocusMarioStyle(RecyclerView parent, View child) { 12908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int focusedPosition = getPosition(child); 12918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedPosition == mLastChildPositionToRequestFocus) { 12928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 12938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 12948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLastChildPositionToRequestFocus = focusedPosition; 12958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 12968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int availableHeight = getAvailableHeight(); 12978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int focusedChildTop = getDecoratedTop(child); 12988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int focusedChildBottom = getDecoratedBottom(child); 12998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int childIndex = parent.indexOfChild(child); 13018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Iterate through children starting at the focused child to find the child above it to 13028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // smooth scroll to such that the focused child will be as close to the middle of the screen 13038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // as possible. 13048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = childIndex; i >= 0; i--) { 13058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View childAtI = getChildAt(i); 13068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (childAtI == null) { 13078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.e(TAG, "Child is null at index " + i); 13088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen continue; 13098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // We haven't found a view that is more than half of the recycler view height above it 13118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // but we've reached the top so we can't go any further. 13128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (i == 0) { 13138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen parent.smoothScrollToPosition(getPosition(childAtI)); 13148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen break; 13158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // Because we want to scroll to the first view that is less than half of the screen 13188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // away from the focused view, we "look ahead" one view. When the look ahead view 13198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // is more than availableHeight / 2 away, the current child at i is the one we want to 13208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // scroll to. However, sometimes, that view can be null (ie, if the view is in 13218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // transition). In that case, just skip that view. 13228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View childBefore = getChildAt(i - 1); 13248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (childBefore == null) { 13258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen continue; 13268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int distanceToChildBeforeFromTop = focusedChildTop - getDecoratedTop(childBefore); 13288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int distanceToChildBeforeFromBottom = focusedChildBottom - getDecoratedTop(childBefore); 13298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (distanceToChildBeforeFromTop > availableHeight / 2 13318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen || distanceToChildBeforeFromBottom > availableHeight) { 13328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen parent.smoothScrollToPosition(getPosition(childAtI)); 13338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen break; 13348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 13378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 13408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * In this style, you can free scroll in the middle of the list but if you get to the edge, 13418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * the list will advance to ensure that there is context ahead of the focused item. 13428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 13438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private boolean onRequestChildFocusSuperMarioStyle(RecyclerView parent, 13448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.State state, View child) { 13458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int focusedPosition = getPosition(child); 13468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedPosition == mLastChildPositionToRequestFocus) { 13478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 13488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mLastChildPositionToRequestFocus = focusedPosition; 13508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int bottomEdgeThatMustBeOnScreen; 13528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int focusedIndex = parent.indexOfChild(child); 13538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // The amount of the last card at the end that must be showing to count as visible. 13548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int peekAmount = mContext.getResources() 13558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen .getDimensionPixelSize(R.dimen.car_last_card_peek_amount); 13568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedPosition == state.getItemCount() - 1) { 13578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // The last item is focused. 13588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen bottomEdgeThatMustBeOnScreen = getDecoratedBottom(child); 13598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else if (focusedIndex == getChildCount() - 1) { 13608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // The last laid out item is focused. Scroll enough so that the next card has at least 13618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // the peek size visible 13628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen ViewGroup.MarginLayoutParams params = 13638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen (ViewGroup.MarginLayoutParams) child.getLayoutParams(); 13648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // We add params.topMargin as an estimate because we don't actually know the top margin 13658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // of the next row. 13668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen bottomEdgeThatMustBeOnScreen = getDecoratedBottom(child) + 13678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen params.bottomMargin + params.topMargin + peekAmount; 13688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 13698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View nextChild = getChildAt(focusedIndex + 1); 13708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen bottomEdgeThatMustBeOnScreen = getDecoratedTop(nextChild) + peekAmount; 13718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (bottomEdgeThatMustBeOnScreen > getHeight()) { 13748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // We're going to have to scroll because the bottom edge that must be on screen is past 13758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // the bottom. 13768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int topEdgeToFindViewUnder = getPaddingTop() + 13778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen bottomEdgeThatMustBeOnScreen - getHeight(); 13788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View nextChild = null; 13808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen for (int i = 0; i < getChildCount(); i++) { 13818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View potentialNextChild = getChildAt(i); 13828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(potentialNextChild); 13838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen float top = getDecoratedTop(potentialNextChild) - params.topMargin; 13848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (top >= topEdgeToFindViewUnder) { 13858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen nextChild = potentialNextChild; 13868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen break; 13878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 13908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (nextChild == null) { 13918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.e(TAG, "There is no view under " + topEdgeToFindViewUnder); 13928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 13938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 13948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int nextChildPosition = getPosition(nextChild); 13958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen parent.smoothScrollToPosition(nextChildPosition); 13968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 13978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int firstFullyVisibleIndex = getFirstFullyVisibleChildIndex(); 13988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (focusedIndex <= firstFullyVisibleIndex) { 13998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen parent.smoothScrollToPosition(Math.max(focusedPosition - 1, 0)); 14008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return true; 14038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 14068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * We don't actually know the size of every single view, only what is currently laid out. 14078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * This makes it difficult to do accurate scrollbar calculations. However, lists in the car 14088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * often consist of views with identical heights. Because of that, we can use 14098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * a single sample view to do our calculations for. The main exceptions are in the first items 14108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * of a list (hero card, last call card, etc) so if the first view is at position 0, we pick 14118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * the next one. 14128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * 14138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The decorated measured height of the sample view plus its margins. 14148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 14158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int getSampleViewHeight() { 14168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (mSampleViewHeight != -1) { 14178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return mSampleViewHeight; 14188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int sampleViewIndex = getFirstFullyVisibleChildIndex(); 14208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen View sampleView = getChildAt(sampleViewIndex); 14218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getPosition(sampleView) == 0 && sampleViewIndex < getChildCount() - 1) { 14228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen sampleView = getChildAt(++sampleViewIndex); 14238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen RecyclerView.LayoutParams params = getParams(sampleView); 14258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int height = 14268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen getDecoratedMeasuredHeight(sampleView) + params.topMargin + params.bottomMargin; 14278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (height == 0) { 14288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // This can happen if the view isn't measured yet. 14298b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.w(TAG, "The sample view has a height of 0. Returning a dummy value for now " + 14308b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen "that won't be cached."); 14318b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen height = mContext.getResources().getDimensionPixelSize(R.dimen.car_sample_row_height); 14328b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } else { 14338b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mSampleViewHeight = height; 14348b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14358b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return height; 14368b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14378b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14388b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 14398b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return The height of the RecyclerView excluding padding. 14408b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 14418b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private int getAvailableHeight() { 14428b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return getHeight() - getPaddingTop() - getPaddingBottom(); 14438b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14448b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14458b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 14468b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * @return {@link RecyclerView.LayoutParams} for the given view or null if it isn't a child 14478b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * of {@link RecyclerView}. 14488b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 14498b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static RecyclerView.LayoutParams getParams(View view) { 14508b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return (RecyclerView.LayoutParams) view.getLayoutParams(); 14518b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14528b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14538b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** 14548b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * Custom {@link LinearSmoothScroller} that has: 14558b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * a) Custom control over the speed of scrolls. 14568b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * b) Scrolling snaps to start. All of our scrolling logic depends on that. 14578b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * c) Keeps track of some state of the current scroll so that can aid in things like 14588b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen * the scrollbar calculations. 14598b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen */ 14608b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private final class CarSmoothScroller extends LinearSmoothScroller { 14618b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** This value (150) was hand tuned by UX for what felt right. **/ 14628b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final float MILLISECONDS_PER_INCH = 150f; 14638b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** This value (0.45) was hand tuned by UX for what felt right. **/ 14648b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final float DECELERATION_TIME_DIVISOR = 0.45f; 14658b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private static final int NON_TOUCH_MAX_DECELERATION_MS = 1000; 14668b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14678b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen /** This value (1.8) was hand tuned by UX for what felt right. **/ 14688b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private final Interpolator mInterpolator = new DecelerateInterpolator(1.8f); 14698b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14708b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private final boolean mHasTouch; 14718b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen private final int mTargetPosition; 14728b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14738b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14748b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public CarSmoothScroller(Context context, int targetPosition) { 14758b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen super(context); 14768b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mTargetPosition = targetPosition; 14778b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen mHasTouch = mContext.getResources().getBoolean(R.bool.car_true_for_touch); 14788b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14798b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14808b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 14818b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public PointF computeScrollVectorForPosition(int i) { 14828b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (getChildCount() == 0) { 14838b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return null; 14848b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14858b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen final int firstChildPos = getPosition(getChildAt(getFirstFullyVisibleChildIndex())); 14868b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen final int direction = (mTargetPosition < firstChildPos) ? -1 : 1; 14878b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return new PointF(0, direction); 14888b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14898b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14908b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 14918b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen protected int getVerticalSnapPreference() { 14928b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // This is key for most of the scrolling logic that guarantees that scrolling 14938b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen // will settle with a view aligned to the top. 14948b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return LinearSmoothScroller.SNAP_TO_START; 14958b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 14968b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 14978b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 14988b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { 14998b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START); 15008b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (dy == 0) { 15018b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (DEBUG) { 15028b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen Log.d(TAG, "Scroll distance is 0"); 15038b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 15048b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return; 15058b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 15068b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 15078b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen final int time = calculateTimeForDeceleration(dy); 15088b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen if (time > 0) { 15098b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen action.update(0, -dy, time, mInterpolator); 15108b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 15118b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 15128b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 15138b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 15148b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { 15158b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; 15168b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 15178b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 15188b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen @Override 15198b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen protected int calculateTimeForDeceleration(int dx) { 15208b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen int time = (int) Math.ceil(calculateTimeForScrolling(dx) / DECELERATION_TIME_DIVISOR); 15218b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return mHasTouch ? time : Math.min(time, NON_TOUCH_MAX_DECELERATION_MS); 15228b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 15238b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen 15248b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen public int getTargetPosition() { 15258b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen return mTargetPosition; 15268b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 15278b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen } 15288b7af7188228ca88838e31e070f0fca0d1f90581Yao Chen} 1529