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
@@ -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);
}
}
}
}