Add a header container view to the widgets full sheet

A header container view will contain
1. A horizontal bar: for indicating the popup view can be dragged
   to dismiss:
2. A title view: a title of the widgets full sheet popup view.
3. A search bar: for widgets / shortcut search. Since we will be
   making the fallback search algorithm available in AOSP, the
   search bar will now live in the Launcher3 codebase.
4. Recommended widgets (coming soon...)

This CL also added a scroll effect which gradually collapses the
title view when the user scrolls down the recycler view. The title
view will gradually restore its height when the user scroll to the
top of the recycler view.

Test: Manually test widgets pickers in work profile and non work
      profile setup. Verified the fast scroll bar works well in
      both setup.
      With searchbar: https://drive.google.com/file/d/19grUHL_LspLhMD_5p6-i0CiMW1FpflmD/view?usp=sharing
      Without searchbar: https://drive.google.com/file/d/1KRPgEAESHUhJDo1UJsQN80JO1c9Y8Nhl/view?usp=sharing

Bug: 179797520

Change-Id: If0016e3b3c693414897140e7912531ed9bd5deef
This commit is contained in:
Steven Ng
2021-02-23 10:48:46 +00:00
parent 036b585475
commit 167f81b931
11 changed files with 375 additions and 28 deletions
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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="#FFFFF7" />
<corners android:radius="24dp" />
</shape>
+1
View File
@@ -23,6 +23,7 @@
android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
android:orientation="horizontal"
android:elevation="2dp"
style="@style/TextHeadline">
<Button
+4 -2
View File
@@ -16,13 +16,15 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto">
<include layout="@layout/personal_work_tabs" />
<include layout="@layout/personal_work_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" />
<com.android.launcher3.workprofile.PersonalWorkPagedView
android:id="@+id/widgets_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/tabs"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
launcher:pageIndicator="@+id/tabs">
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_and_recommendations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<View
android:id="@+id/collapse_handle"
android:layout_width="48dp"
android:layout_height="2dp"
android:layout_gravity="center_horizontal"
android:background="@color/popup_color_primary_dark"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textSize="24sp"
android:layout_marginTop="16dp"
android:text="@string/widget_button_text"/>
<!-- Disable the search bar because it has not been implemented. -->
<EditText
android:id="@+id/widgets_search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginTop="16dp"
android:background="@drawable/bg_widgets_searchbox"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_allapps_search"
android:hint="@string/widgets_full_sheet_search_bar_hint"
android:padding="12dp" />
</LinearLayout>
+6 -2
View File
@@ -57,6 +57,12 @@
<item quantity="one"><xliff:g id="widget_count" example="1">%1$d</xliff:g> widget</item>
<item quantity="other"><xliff:g id="widget_count" example="2">%1$d</xliff:g> widgets</item>
</plurals>
<!-- Text for both the tile of a popup view, which shows all available widgets installed on
the device, and the text of a button, which opens this popup view. [CHAR LIMIT=30]-->
<string name="widget_button_text">Widgets</string>
<!-- Search bar text shown in the popup view showing all available widgets installed on the
device. [CHAR_LIMIT=50] -->
<string name="widgets_full_sheet_search_bar_hint">Search</string>
<!-- All Apps -->
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
@@ -185,8 +191,6 @@
<string name="folder_name_format_overflow">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> or more items</string>
<!-- Strings for the customization mode -->
<!-- Text for widget add button [CHAR LIMIT=30]-->
<string name="widget_button_text">Widgets</string>
<!-- Text for wallpaper change button [CHAR LIMIT=30]-->
<string name="wallpaper_button_text">Wallpapers</string>
<!-- Text for wallpaper change button [CHAR LIMIT=30]-->
@@ -23,6 +23,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -188,4 +189,21 @@ public abstract class BaseRecyclerView extends RecyclerView {
super.onInitializeAccessibilityNodeInfo(info);
if (isLayoutSuppressed()) info.setScrollable(false);
}
/**
* Scrolls this recycler view to the top.
*/
public void scrollToTop() {
if (mScrollbar != null) {
mScrollbar.reattachThumbToScroll();
}
if (getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
// We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
return;
}
}
scrollToPosition(0);
}
}
@@ -36,7 +36,6 @@ import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
import com.android.launcher3.views.RecyclerViewFastScroller;
import java.util.ArrayList;
@@ -109,23 +108,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
}
/**
* Scrolls this recycler view to the top.
*/
public void scrollToTop() {
// Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
if (mScrollbar != null) {
mScrollbar.reattachThumbToScroll();
}
if (getLayoutManager() instanceof AppsGridLayoutManager) {
AppsGridLayoutManager layoutManager = (AppsGridLayoutManager) getLayoutManager();
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
// We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
return;
}
}
scrollToPosition(0);
}
@Override
public void onDraw(Canvas c) {
@@ -34,6 +34,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BaseRecyclerView;
@@ -99,6 +100,7 @@ public class RecyclerViewFastScroller extends View {
private boolean mIsThumbDetached;
private final boolean mCanThumbDetach;
private boolean mIgnoreDragGesture;
private boolean mIsRecyclerViewFirstChildInParent = true;
// This is the offset from the top of the scrollbar when the user first starts touching. To
// prevent jumping, this offset is applied as the user scrolls.
@@ -112,6 +114,7 @@ public class RecyclerViewFastScroller extends View {
protected BaseRecyclerView mRv;
private RecyclerView.OnScrollListener mOnScrollListener;
@Nullable private OnFastScrollChangeListener mOnFastScrollChangeListener;
private int mDownX;
private int mDownY;
@@ -188,6 +191,9 @@ public class RecyclerViewFastScroller extends View {
updatePopupY(y);
mThumbOffsetY = y;
invalidate();
if (mOnFastScrollChangeListener != null) {
mOnFastScrollChangeListener.onThumbOffsetYChanged(mThumbOffsetY);
}
}
public int getThumbOffsetY() {
@@ -391,7 +397,9 @@ public class RecyclerViewFastScroller extends View {
return false;
}
getHitRect(sTempRect);
sTempRect.top += mRv.getScrollBarTop();
if (mIsRecyclerViewFirstChildInParent) {
sTempRect.top += mRv.getScrollBarTop();
}
if (outOffset != null) {
outOffset.set(sTempRect.left, sTempRect.top);
}
@@ -404,4 +412,23 @@ public class RecyclerViewFastScroller extends View {
// alpha is so low, it does not matter.
return false;
}
public void setIsRecyclerViewFirstChildInParent(boolean isRecyclerViewFirstChildInParent) {
mIsRecyclerViewFirstChildInParent = isRecyclerViewFirstChildInParent;
}
public void setOnFastScrollChangeListener(
@Nullable OnFastScrollChangeListener onFastScrollChangeListener) {
mOnFastScrollChangeListener = onFastScrollChangeListener;
}
/**
* A callback that is invoked when there is a scroll change in {@link RecyclerViewFastScroller}.
*/
public interface OnFastScrollChangeListener {
/**
* Called when the thumb offset vertical position, in pixels, has changed to {@code y}.
*/
void onThumbOffsetYChanged(int y);
}
}
@@ -0,0 +1,162 @@
/*
* Copyright (C) 2021 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.
*/
package com.android.launcher3.widget.picker;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.widget.picker.WidgetsFullSheet.SearchAndRecommendationViewHolder;
import com.android.launcher3.workprofile.PersonalWorkPagedView;
/**
* A controller which measures & updates {@link WidgetsFullSheet}'s views padding, margin and
* vertical displacement upon scrolling.
*/
final class SearchAndRecommendationsScrollController implements
RecyclerViewFastScroller.OnFastScrollChangeListener {
private final boolean mHasWorkProfile;
private final SearchAndRecommendationViewHolder mViewHolder;
private final RecyclerView mPrimaryRecyclerView;
// The following are only non null if mHasWorkProfile is true.
@Nullable private final RecyclerView mWorkRecyclerView;
@Nullable private final View mPrimaryWorkTabsView;
@Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
private int mMaxCollapsibleHeight = 0;
SearchAndRecommendationsScrollController(
boolean hasWorkProfile,
SearchAndRecommendationViewHolder viewHolder,
RecyclerView primaryRecyclerView,
@Nullable RecyclerView workRecyclerView,
@Nullable View personalWorkTabsView,
@Nullable PersonalWorkPagedView primaryWorkViewPager) {
mHasWorkProfile = hasWorkProfile;
mViewHolder = viewHolder;
mPrimaryRecyclerView = primaryRecyclerView;
mWorkRecyclerView = workRecyclerView;
mPrimaryWorkTabsView = personalWorkTabsView;
mPrimaryWorkViewPager = primaryWorkViewPager;
}
/**
* Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
*/
public void updateMarginAndPadding() {
// The maximum vertical distance, in pixels, until the last collapsible element is not
// visible from the screen when the user scrolls down the recycler view.
mMaxCollapsibleHeight = mViewHolder.mContainer.getPaddingTop()
+ mViewHolder.mCollapseHandle.getMeasuredHeight()
+ mViewHolder.mHeaderTitle.getMeasuredHeight();
int topContainerHeight = mViewHolder.mContainer.getMeasuredHeight();
if (mHasWorkProfile) {
// In a work profile setup, the full widget sheet contains the following views:
// ------- -|
// Widgets -|---> LinearLayout for search & recommendations
// Search bar -|
// Personal | Work
// View Pager
//
// Views after the search & recommendations are not bound by RelativelyLayout param.
// To position them on the expected location, padding & margin are added to these views
// Tabs should have a padding of the height of the search & recommendations container.
mPrimaryWorkTabsView.setPadding(
mPrimaryWorkTabsView.getPaddingLeft(),
topContainerHeight,
mPrimaryWorkTabsView.getPaddingRight(),
mPrimaryWorkTabsView.getPaddingBottom());
// Instead of setting the top offset directly, we split the top offset into two values:
// 1. topOffsetAfterAllViewsCollapsed: this is the top offset after all collapsible
// views are no longer visible on the screen.
// This value is set as the margin for the view pager.
// 2. mMaxCollapsibleDistance
// This value is set as the padding for the recycler views in order to work with
// clipToPadding="false", which is an attribute for not showing top / bottom padding
// when a recycler view has not reached the top or bottom of the list.
// e.g. a list of 10 entries, only 3 entries are visible at a time.
// case 1: recycler view is scrolled to the top. Top padding is visible/
// (top padding)
// item 1
// item 2
// item 3
//
// case 2: recycler view is scrolled to the middle. No padding is visible.
// item 4
// item 5
// item 6
//
// case 3: recycler view is scrolled to the end. bottom padding is visible.
// item 8
// item 9
// item 10
// (bottom padding): not set in this case.
//
// When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
// mMaxCollapsibleDistance should equal to the top container height.
int tabsViewActualHeight =
mPrimaryWorkTabsView.getMeasuredHeight() - mPrimaryWorkTabsView.getPaddingTop();
int topOffsetAfterAllViewsCollapsed =
topContainerHeight + tabsViewActualHeight - mMaxCollapsibleHeight;
RelativeLayout.LayoutParams layoutParams =
(RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
layoutParams.setMargins(0, topOffsetAfterAllViewsCollapsed, 0, 0);
mPrimaryWorkViewPager.setLayoutParams(layoutParams);
mPrimaryWorkViewPager.requestLayout();
mPrimaryRecyclerView.setPadding(
mPrimaryRecyclerView.getPaddingLeft(),
mMaxCollapsibleHeight,
mPrimaryRecyclerView.getPaddingRight(),
mPrimaryRecyclerView.getPaddingBottom());
mWorkRecyclerView.setPadding(
mWorkRecyclerView.getPaddingLeft(),
mMaxCollapsibleHeight,
mWorkRecyclerView.getPaddingRight(),
mWorkRecyclerView.getPaddingBottom());
} else {
mPrimaryRecyclerView.setPadding(
mPrimaryRecyclerView.getPaddingLeft(),
topContainerHeight,
mPrimaryRecyclerView.getPaddingRight(),
mPrimaryRecyclerView.getPaddingBottom());
}
}
/**
* Changes the displacement of collapsible views (e.g. title & widget recommendations) and fixed
* views (e.g. recycler views, tabs) upon scrolling.
*/
@Override
public void onThumbOffsetYChanged(int y) {
if (mMaxCollapsibleHeight > 0) {
int yDisplacement = Math.max(-y, -mMaxCollapsibleHeight);
mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
mViewHolder.mSearchBar.setTranslationY(yDisplacement);
if (mHasWorkProfile) {
mPrimaryWorkTabsView.setTranslationY(yDisplacement);
}
}
}
}
@@ -34,6 +34,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -61,7 +63,8 @@ import java.util.function.Predicate;
* Popup for showing the full list of available widgets
*/
public class WidgetsFullSheet extends BaseWidgetSheet
implements Insettable, ProviderChangedListener, OnActivePageChangedListener {
implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
WidgetsRecyclerView.HeaderViewDimensionsProvider {
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
@@ -77,6 +80,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mPrimaryWidgetsFilter.negate();
@Nullable private PersonalWorkPagedView mViewPager;
private int mInitialTabsHeight = 0;
private View mTabsView;
private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -98,8 +105,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
: R.layout.widgets_full_sheet_recyclerview;
layoutInflater.inflate(contentLayoutRes, springLayout, true);
layoutInflater.inflate(contentLayoutRes, springLayout, true);
RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
if (mHasWorkProfile) {
mViewPager = findViewById(R.id.widgets_view_pager);
// Temporarily disable swipe gesture until widgets list horizontal scrollviews per
@@ -108,10 +116,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mViewPager.initParentViews(this);
mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
mTabsView = findViewById(R.id.tabs);
findViewById(R.id.tab_personal)
.setOnClickListener((View view) -> mViewPager.snapToPage(0));
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> mViewPager.snapToPage(1));
fastScroller.setIsRecyclerViewFirstChildInParent(false);
springLayout.addSpringView(R.id.primary_widgets_list_view);
springLayout.addSpringView(R.id.work_widgets_list_view);
} else {
@@ -119,12 +129,36 @@ public class WidgetsFullSheet extends BaseWidgetSheet
springLayout.addSpringView(R.id.primary_widgets_list_view);
}
layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
true);
springLayout.addSpringView(R.id.search_and_recommendations_container);
mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
findViewById(R.id.search_and_recommendations_container));
mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
mHasWorkProfile,
mSearchAndRecommendationViewHolder,
findViewById(R.id.primary_widgets_list_view),
mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
mTabsView,
mViewPager);
fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
onWidgetsBound();
}
@Override
public void onActivePageChanged(int currentActivePage) {
mAdapters.get(currentActivePage).mWidgetsRecyclerView.bindFastScrollbar();
reset();
}
private void reset() {
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
if (mHasWorkProfile) {
mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
}
}
@VisibleForTesting
@@ -220,6 +254,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
contentLeft + contentWidth, height);
setTranslationShift(mTranslationShift);
if (mInitialTabsHeight == 0 && mTabsView != null) {
mInitialTabsHeight = mTabsView.getMeasuredHeight();
}
mSearchAndRecommendationsScrollController.updateMarginAndPadding();
}
@Override
@@ -325,6 +365,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet
AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
}
@Override
public int getHeaderViewHeight() {
// No need to check work profile here because mInitialTabHeight is always 0 if there is no
// work profile.
return mInitialTabsHeight
+ mSearchAndRecommendationViewHolder.mContainer.getMeasuredHeight();
}
/** A holder class for holding adapters & their corresponding recycler view. */
private final class AdapterHolder {
static final int PRIMARY = 0;
@@ -354,9 +402,24 @@ public class WidgetsFullSheet extends BaseWidgetSheet
void setup(WidgetsRecyclerView recyclerView) {
mWidgetsRecyclerView = recyclerView;
mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
mWidgetsRecyclerView.setEdgeEffectFactory(
((TopRoundedCornerView) mContent).createEdgeEffectFactory());
mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
}
}
final class SearchAndRecommendationViewHolder {
final View mContainer;
final View mCollapseHandle;
final EditText mSearchBar;
final TextView mHeaderTitle;
SearchAndRecommendationViewHolder(View searchAndRecommendationContainer) {
mContainer = searchAndRecommendationContainer;
mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
mHeaderTitle = mContainer.findViewById(R.id.title);
}
}
}
@@ -40,6 +40,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
private final Point mFastScrollerOffset = new Point();
private boolean mTouchDownOnScroller;
private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -135,8 +136,8 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
@Override
protected int getAvailableScrollHeight() {
View child = getChildAt(0);
return child.getMeasuredHeight() * mAdapter.getItemCount() - getScrollbarTrackHeight()
- mScrollbarTop;
return child.getMeasuredHeight() * mAdapter.getItemCount() + getScrollBarTop()
+ getPaddingBottom() - mScrollbar.getHeight();
}
private boolean isModelNotReady() {
@@ -145,7 +146,9 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
@Override
public int getScrollBarTop() {
return mScrollbarTop;
return mHeaderViewDimensionsProvider == null
? mScrollbarTop
: mHeaderViewDimensionsProvider.getHeaderViewHeight() + mScrollbarTop;
}
@Override
@@ -171,4 +174,21 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
public void setHeaderViewDimensionsProvider(
HeaderViewDimensionsProvider headerViewDimensionsProvider) {
mHeaderViewDimensionsProvider = headerViewDimensionsProvider;
}
/**
* Provides dimensions of the header view that is shown at the top of a
* {@link WidgetsRecyclerView}.
*/
public interface HeaderViewDimensionsProvider {
/**
* Returns the height, in pixels, of the header view that is shown at the top of a
* {@link WidgetsRecyclerView}.
*/
int getHeaderViewHeight();
}
}