Merge "Cleaning up scrollbar logic to properly calculate stable extents." into ub-launcher3-calgary

This commit is contained in:
Winson Chung 2016-07-13 01:04:39 +00:00 committed by Android (Google) Code Review
commit b029e9fd66
11 changed files with 214 additions and 181 deletions

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/all_apps_divider_color" />
<size android:height="1dp" />
</shape>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?android:attr/colorAccent" />
<size android:height="1dp" />
</shape>

View File

@ -13,13 +13,14 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<View xmlns:android="http://schemas.android.com/apk/res/android"
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:importantForAccessibility="no"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_divider_height"
android:layout_marginBottom="@dimen/all_apps_divider_margin_vertical"
android:layout_marginLeft="@dimen/container_fastscroll_thumb_max_width"
android:layout_marginRight="@dimen/container_fastscroll_thumb_max_width"
android:layout_marginTop="@dimen/all_apps_divider_margin_vertical"
android:background="@color/all_apps_divider_color"
android:layout_height="wrap_content"
android:paddingTop="@dimen/all_apps_divider_margin_vertical"
android:paddingBottom="@dimen/all_apps_divider_margin_vertical"
android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
android:paddingRight="@dimen/container_fastscroll_thumb_max_width"
android:src="@drawable/all_apps_divider"
android:scaleType="fitXY"
android:focusable="false" />

View File

@ -13,16 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.BubbleTextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/Icon.AllApps"
android:id="@+id/icon"
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:importantForAccessibility="no"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingTop="@dimen/all_apps_icon_top_bottom_padding"
android:paddingBottom="@dimen/all_apps_icon_top_bottom_padding"
android:focusable="true"
launcher:iconDisplay="all_apps" />
android:paddingBottom="@dimen/all_apps_divider_margin_vertical"
android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
android:paddingRight="@dimen/container_fastscroll_thumb_max_width"
android:src="@drawable/all_apps_search_divider"
android:scaleType="fitXY"
android:focusable="false" />

View File

@ -80,7 +80,6 @@
<dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
<dimen name="all_apps_header_shadow_height">6dp</dimen>
<dimen name="all_apps_divider_height">1dp</dimen>
<dimen name="all_apps_divider_margin_vertical">8dp</dimen>
<dimen name="all_apps_bezel_swipe_height">24dp</dimen>

View File

@ -41,21 +41,6 @@ public abstract class BaseRecyclerView extends RecyclerView
@Thunk int mDy = 0;
private float mDeltaThreshold;
/**
* The current scroll state of the recycler view. We use this in onUpdateScrollbar()
* and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
* that we can calculate what the scroll bar looks like, and where to jump to from the fast
* scroller.
*/
public static class ScrollPositionState {
// The index of the first visible row
public int rowIndex;
// The offset of the first visible row
public int rowTopOffset;
// The adapter position of the first visible item
public int itemPos;
}
protected BaseRecyclerViewFastScrollBar mScrollbar;
private int mDownX;
@ -199,11 +184,7 @@ public abstract class BaseRecyclerView extends RecyclerView
* Returns the available scroll height:
* AvailableScrollHeight = Total height of the all items - last page height
*/
protected int getAvailableScrollHeight(int rowCount) {
int totalHeight = getPaddingTop() + getTop(rowCount) + getPaddingBottom();
int availableScrollHeight = totalHeight - getVisibleHeight();
return availableScrollHeight;
}
protected abstract int getAvailableScrollHeight();
/**
* Returns the available scroll bar height:
@ -247,15 +228,12 @@ public abstract class BaseRecyclerView extends RecyclerView
* this by mapping the available scroll area of the recycler view to the available space for the
* scroll bar.
*
* @param scrollPosState the current scroll position
* @param rowCount the number of rows, used to calculate the total scroll height (assumes that
* all rows are the same height)
* @param scrollY the current scroll y
*/
protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
int rowCount) {
protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
int availableScrollHeight) {
// Only show the scrollbar if there is height to be scrolled
int availableScrollBarHeight = getAvailableScrollBarHeight();
int availableScrollHeight = getAvailableScrollHeight(rowCount);
if (availableScrollHeight <= 0) {
mScrollbar.setThumbOffset(-1, -1);
return;
@ -264,7 +242,6 @@ public abstract class BaseRecyclerView extends RecyclerView
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
// padding)
int scrollY = Math.max(0, getScrollTop(scrollPosState));
int scrollBarY = mBackgroundPadding.top +
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@ -291,20 +268,7 @@ public abstract class BaseRecyclerView extends RecyclerView
*
* @return the scroll top of this recycler view.
*/
protected int getScrollTop(ScrollPositionState scrollPosState) {
return getPaddingTop() + getTop(scrollPosState.rowIndex) -
scrollPosState.rowTopOffset;
}
/**
* Returns information about the item that the recycler view is currently scrolled to.
*/
protected abstract void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask);
/**
* Returns the top (or y position) of the row at the specified index.
*/
protected abstract int getTop(int rowIndex);
protected abstract int getCurrentScrollY();
/**
* Maps the touch (from 0..1) to the adapter position that should be visible.

View File

@ -366,26 +366,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
mAppsRecyclerView.preMeasureViews(mAdapter);
mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
// Precalculate the prediction icon and normal icon sizes
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().widthPixels, MeasureSpec.AT_MOST);
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().heightPixels, MeasureSpec.AT_MOST);
BubbleTextView icon = (BubbleTextView) layoutInflater.inflate(
R.layout.all_apps_icon, this, false);
icon.applyDummyInfo();
icon.measure(widthMeasureSpec, heightMeasureSpec);
BubbleTextView predIcon = (BubbleTextView) layoutInflater.inflate(
R.layout.all_apps_prediction_bar_icon, this, false);
predIcon.applyDummyInfo();
predIcon.measure(widthMeasureSpec, heightMeasureSpec);
mAppsRecyclerView.setPremeasuredIconHeights(predIcon.getMeasuredHeight(),
icon.getMeasuredHeight());
// TODO(hyunyoungs): clean up setting the content and the reveal view.
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
getContentView().setBackground(null);

View File

@ -18,7 +18,6 @@ package com.android.launcher3.allapps;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.BaseRecyclerViewFastScrollBar;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.util.Thunk;
@ -144,8 +143,8 @@ public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallb
// Calculate the full animation from the current scroll position to the final scroll
// position, and then run the animation for the duration.
int newScrollY = Math.min(availableScrollHeight,
mRv.getPaddingTop() + mRv.getTop(info.fastScrollToItem.rowIndex));
int newPosition = info.fastScrollToItem.position;
int newScrollY = Math.min(availableScrollHeight, mRv.getCurrentScrollY(newPosition, 0));
int numFrames = mFastScrollFrames.length;
for (int i = 0; i < numFrames; i++) {
// TODO(winsonc): We can interpolate this as well.

View File

@ -334,7 +334,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
private final int mSectionNamesMargin;
private final int mSectionHeaderOffset;
private final Paint mSectionTextPaint;
private int mAccentColor;
private int mAppsPerRow;
@ -364,12 +363,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
mIsRtl = Utilities.isRtl(res);
mAccentColor = Utilities.getColorAccent(launcher);
mSectionTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
R.dimen.all_apps_grid_section_text_size));
mSectionTextPaint.setColor(mAccentColor);
mSectionTextPaint.setColor(Utilities.getColorAccent(launcher));
}
public static boolean isDividerViewType(int viewType) {
@ -380,6 +377,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
return isViewType(viewType, VIEW_TYPE_MASK_ICON);
}
public static boolean isPredictionIconViewType(int viewType) {
return isViewType(viewType, VIEW_TYPE_PREDICTION_ICON);
}
public static boolean isViewType(int viewType, int viewTypeMask) {
return (viewType & viewTypeMask) != 0;
}
@ -449,8 +450,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
/* falls through */
case VIEW_TYPE_PREDICTION_ICON: {
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
viewType == VIEW_TYPE_ICON ? R.layout.all_apps_icon :
R.layout.all_apps_prediction_bar_icon, parent, false);
R.layout.all_apps_icon, parent, false);
icon.setOnClickListener(mIconClickListener);
icon.setOnLongClickListener(mIconLongClickListener);
icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext())
@ -472,14 +472,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
});
return new ViewHolder(searchMarketView);
case VIEW_TYPE_SEARCH_DIVIDER:
final View searchDivider =
mLayoutInflater.inflate(R.layout.all_apps_divider, parent, false);
searchDivider.setBackgroundColor(mAccentColor);
final GridLayoutManager.LayoutParams searchDividerParams =
(GridLayoutManager.LayoutParams) searchDivider.getLayoutParams();
searchDividerParams.topMargin = 0;
searchDivider.setLayoutParams(searchDividerParams);
return new ViewHolder(searchDivider);
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_search_divider, parent, false));
case VIEW_TYPE_PREDICTION_DIVIDER:
/* falls through */
case VIEW_TYPE_SEARCH_MARKET_DIVIDER:

View File

@ -21,6 +21,7 @@ import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.view.View;
import com.android.launcher3.BaseRecyclerView;
@ -42,13 +43,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView
private AlphabeticalAppsList mApps;
private AllAppsFastScrollHelper mFastScrollHelper;
private BaseRecyclerView.ScrollPositionState mScrollPosState =
new BaseRecyclerView.ScrollPositionState();
private int mNumAppsPerRow;
// The specific icon heights that we use to calculate scroll
private int mPredictionIconHeight;
private int mIconHeight;
// The specific view heights that we use to calculate scroll
private SparseIntArray mViewHeights = new SparseIntArray();
private SparseIntArray mCachedScrollPositions = new SparseIntArray();
// The empty-search result background
private AllAppsBackgroundDrawable mEmptySearchBackground;
@ -109,11 +108,52 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
/**
* Sets the heights of the icons in this view (for scroll calculations).
* Ensures that we can present a stable scrollbar for views of varying types by pre-measuring
* all the different view types.
*/
public void setPremeasuredIconHeights(int predictionIconHeight, int iconHeight) {
mPredictionIconHeight = predictionIconHeight;
mIconHeight = iconHeight;
public void preMeasureViews(AllAppsGridAdapter adapter) {
final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().widthPixels, View.MeasureSpec.AT_MOST);
final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().heightPixels, View.MeasureSpec.AT_MOST);
// Icons
BubbleTextView icon = (BubbleTextView) adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_ICON).mContent;
icon.applyDummyInfo();
icon.measure(widthMeasureSpec, heightMeasureSpec);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, icon.getMeasuredHeight());
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, icon.getMeasuredHeight());
// Search divider
View searchDivider = adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).mContent;
searchDivider.measure(widthMeasureSpec, heightMeasureSpec);
int searchDividerHeight = searchDivider.getMeasuredHeight();
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, searchDividerHeight);
// Generic dividers
View divider = adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).mContent;
divider.measure(widthMeasureSpec, heightMeasureSpec);
int dividerHeight = divider.getMeasuredHeight();
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, dividerHeight);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER, dividerHeight);
// Search views
View emptySearch = adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).mContent;
emptySearch.measure(widthMeasureSpec, heightMeasureSpec);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH,
emptySearch.getMeasuredHeight());
View searchMarket = adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).mContent;
searchMarket.measure(widthMeasureSpec, heightMeasureSpec);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET,
searchMarket.getMeasuredHeight());
// Section breaks
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK, 0);
}
/**
@ -234,8 +274,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
// Update the fast scroll
int scrollY = getScrollTop(mScrollPosState);
int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows());
int scrollY = getCurrentScrollY();
int availableScrollHeight = getAvailableScrollHeight();
mFastScrollHelper.smoothScrollToSection(scrollY, availableScrollHeight, lastInfo);
return lastInfo.sectionName;
}
@ -249,6 +289,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
public void onChanged() {
mCachedScrollPositions.clear();
}
});
mFastScrollHelper.onSetAdapter((AllAppsGridAdapter) adapter);
}
@ -265,17 +310,16 @@ public class AllAppsRecyclerView extends BaseRecyclerView
return;
}
// Find the index and height of the first visible row (all rows have the same height)
int rowCount = mApps.getNumAppRows();
getCurScrollState(mScrollPosState, AllAppsGridAdapter.VIEW_TYPE_MASK_ICON);
if (mScrollPosState.rowIndex < 0) {
// Skip early if, there no child laid out in the container.
int scrollY = getCurrentScrollY();
if (scrollY < 0) {
mScrollbar.setThumbOffset(-1, -1);
return;
}
// Only show the scrollbar if there is height to be scrolled
int availableScrollBarHeight = getAvailableScrollBarHeight();
int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows());
int availableScrollHeight = getAvailableScrollHeight();
if (availableScrollHeight <= 0) {
mScrollbar.setThumbOffset(-1, -1);
return;
@ -284,7 +328,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
// padding)
int scrollY = getScrollTop(mScrollPosState);
int scrollBarY = mBackgroundPadding.top +
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@ -330,41 +373,10 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
}
} else {
synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
synchronizeScrollBarThumbOffsetToViewScroll(scrollY, availableScrollHeight);
}
}
/**
* Returns the current scroll state of the apps rows.
*/
protected void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask) {
stateOut.rowIndex = -1;
stateOut.rowTopOffset = -1;
stateOut.itemPos = -1;
// Return early if there are no items or we haven't been measured
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
if (items.isEmpty() || mNumAppsPerRow == 0) {
return;
}
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int position = getChildPosition(child);
if (position != NO_POSITION) {
AlphabeticalAppsList.AdapterItem item = items.get(position);
if (AllAppsGridAdapter.isViewType(item.viewType, viewTypeMask)) {
stateOut.rowIndex = item.rowIndex;
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
stateOut.itemPos = position;
return;
}
}
}
return;
}
@Override
protected boolean supportsFastScrolling() {
// Only allow fast scrolling when the user is not searching, since the results are not
@ -372,13 +384,63 @@ public class AllAppsRecyclerView extends BaseRecyclerView
return !mApps.hasFilter();
}
protected int getTop(int rowIndex) {
if (getChildCount() == 0 || rowIndex <= 0) {
return 0;
@Override
protected int getCurrentScrollY() {
// Return early if there are no items or we haven't been measured
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
return -1;
}
// The prediction bar icons have more padding, so account for that in the row offset
return mPredictionIconHeight + (rowIndex - 1) * mIconHeight;
// Calculate the y and offset for the item
View child = getChildAt(0);
int position = getChildPosition(child);
if (position == NO_POSITION) {
return -1;
}
return getCurrentScrollY(position, getLayoutManager().getDecoratedTop(child));
}
public int getCurrentScrollY(int position, int offset) {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
AlphabeticalAppsList.AdapterItem posItem = position < items.size() ?
items.get(position) : null;
int y = mCachedScrollPositions.get(position, -1);
if (y < 0) {
y = 0;
for (int i = 0; i < position; i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i);
if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
// Break once we reach the desired row
if (posItem != null && posItem.viewType == item.viewType &&
posItem.rowIndex == item.rowIndex) {
break;
}
// Otherwise, only account for the first icon in the row since they are the same
// size within a row
if (item.rowAppIndex == 0) {
y += mViewHeights.get(item.viewType, 0);
}
} else {
// Rest of the views span the full width
y += mViewHeights.get(item.viewType, 0);
}
}
mCachedScrollPositions.put(position, y);
}
return getPaddingTop() + y - offset;
}
/**
* Returns the available scroll height:
* AvailableScrollHeight = Total height of the all items - last page height
*/
@Override
protected int getAvailableScrollHeight() {
int paddedHeight = getCurrentScrollY(mApps.getAdapterItems().size(), 0);
int totalHeight = paddedHeight + getPaddingBottom();
return totalHeight - getVisibleHeight();
}
/**

View File

@ -33,7 +33,6 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
private static final String TAG = "WidgetsRecyclerView";
private WidgetsModel mWidgets;
private ScrollPositionState mScrollPosState = new ScrollPositionState();
public WidgetsRecyclerView(Context context) {
this(context, null);
@ -99,9 +98,8 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
stopScroll();
int rowCount = mWidgets.getPackageSize();
getCurScrollState(mScrollPosState, -1);
float pos = rowCount * touchFraction;
int availableScrollHeight = getAvailableScrollHeight(rowCount);
int availableScrollHeight = getAvailableScrollHeight();
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@ -121,45 +119,41 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
}
// Skip early if, there no child laid out in the container.
getCurScrollState(mScrollPosState, -1);
if (mScrollPosState.rowIndex < 0) {
int scrollY = getCurrentScrollY();
if (scrollY < 0) {
mScrollbar.setThumbOffset(-1, -1);
return;
}
synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, mWidgets.getPackageSize());
}
/**
* Returns the current scroll state.
*/
protected void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask) {
stateOut.rowIndex = -1;
stateOut.rowTopOffset = -1;
stateOut.itemPos = -1;
// Skip early if widgets are not bound.
if (isModelNotReady()) {
return;
}
View child = getChildAt(0);
int position = getChildPosition(child);
stateOut.rowIndex = position;
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
stateOut.itemPos = position;
synchronizeScrollBarThumbOffsetToViewScroll(scrollY, getAvailableScrollHeight());
}
@Override
protected int getTop(int rowIndex) {
if (getChildCount() == 0) {
return 0;
protected int getCurrentScrollY() {
// Skip early if widgets are not bound.
if (isModelNotReady() || getChildCount() == 0) {
return -1;
}
// All the rows are the same height, return any child height
View child = getChildAt(0);
return child.getMeasuredHeight() * rowIndex;
int rowIndex = getChildPosition(child);
int y = (child.getMeasuredHeight() * rowIndex);
int offset = getLayoutManager().getDecoratedTop(child);
return getPaddingTop() + y - offset;
}
/**
* Returns the available scroll height:
* AvailableScrollHeight = Total height of the all items - last page height
*/
@Override
protected int getAvailableScrollHeight() {
View child = getChildAt(0);
int height = child.getMeasuredHeight() * mWidgets.getPackageSize();
int totalHeight = getPaddingTop() + height + getPaddingBottom();
int availableScrollHeight = totalHeight - getVisibleHeight();
return availableScrollHeight;
}
private boolean isModelNotReady() {