4235fc129c
Update the tapl test logic to scroll a smaller distance to avoid the
search bar blocking the target touch area.
Test: Open full widgets sheet and observe the widget recommendations
shown at the top.
Run AddConfigWidgetTest
Bug: 179797520
Change-Id: I6d53bbb46e2cb928ed7d015aaac604be17d33178
258 lines
12 KiB
Java
258 lines
12 KiB
Java
/*
|
|
* 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.view.ViewGroup.MarginLayoutParams;
|
|
import android.widget.RelativeLayout;
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
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 WidgetsRecyclerView mPrimaryRecyclerView;
|
|
private final WidgetsRecyclerView mSearchRecyclerView;
|
|
private final int mTabsHeight;
|
|
|
|
// The following are only non null if mHasWorkProfile is true.
|
|
@Nullable private final WidgetsRecyclerView mWorkRecyclerView;
|
|
@Nullable private final View mPrimaryWorkTabsView;
|
|
@Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
|
|
|
|
private WidgetsRecyclerView mCurrentRecyclerView;
|
|
|
|
/**
|
|
* The vertical distance, in pixels, until the search is pinned at the top of the screen when
|
|
* the user scrolls down the recycler view.
|
|
*/
|
|
private int mCollapsibleHeightForSearch = 0;
|
|
/**
|
|
* The vertical distance, in pixels, until the recommendation table disappears from the top of
|
|
* the screen when the user scrolls down the recycler view.
|
|
*/
|
|
private int mCollapsibleHeightForRecommendation = 0;
|
|
/**
|
|
* The vertical distance, in pixels, until the tabs is pinned at the top of the screen when the
|
|
* user scrolls down the recycler view.
|
|
*
|
|
* <p>Always 0 if there is no work profile.
|
|
*/
|
|
private int mCollapsibleHeightForTabs = 0;
|
|
|
|
SearchAndRecommendationsScrollController(
|
|
boolean hasWorkProfile,
|
|
int tabsHeight,
|
|
SearchAndRecommendationViewHolder viewHolder,
|
|
WidgetsRecyclerView primaryRecyclerView,
|
|
@Nullable WidgetsRecyclerView workRecyclerView,
|
|
WidgetsRecyclerView searchRecyclerView,
|
|
@Nullable View personalWorkTabsView,
|
|
@Nullable PersonalWorkPagedView primaryWorkViewPager) {
|
|
mHasWorkProfile = hasWorkProfile;
|
|
mViewHolder = viewHolder;
|
|
mPrimaryRecyclerView = primaryRecyclerView;
|
|
mCurrentRecyclerView = mPrimaryRecyclerView;
|
|
mWorkRecyclerView = workRecyclerView;
|
|
mSearchRecyclerView = searchRecyclerView;
|
|
mPrimaryWorkTabsView = personalWorkTabsView;
|
|
mPrimaryWorkViewPager = primaryWorkViewPager;
|
|
mCurrentRecyclerView = mPrimaryRecyclerView;
|
|
mTabsHeight = tabsHeight;
|
|
}
|
|
|
|
/** Sets the current active {@link WidgetsRecyclerView}. */
|
|
public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
|
|
mCurrentRecyclerView = currentRecyclerView;
|
|
mCurrentRecyclerView = currentRecyclerView;
|
|
mViewHolder.mHeaderTitle.setTranslationY(0);
|
|
mViewHolder.mRecommendedWidgetsTable.setTranslationY(0);
|
|
mViewHolder.mSearchBar.setTranslationY(0);
|
|
|
|
if (mHasWorkProfile) {
|
|
mPrimaryWorkTabsView.setTranslationY(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
|
|
*
|
|
* @return {@code true} if margins or/and padding of views in the search and recommendations
|
|
* container have been updated.
|
|
*/
|
|
public boolean updateMarginAndPadding() {
|
|
boolean hasMarginOrPaddingUpdated = false;
|
|
mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
|
|
mCollapsibleHeightForRecommendation =
|
|
measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
|
|
+ measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
|
|
+ measureHeightWithVerticalMargins((View) mViewHolder.mSearchBar)
|
|
+ measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
|
|
|
|
int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
|
|
if (mHasWorkProfile) {
|
|
mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
|
|
+ measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
|
|
// In a work profile setup, the full widget sheet contains the following views:
|
|
// ------- (pinned) -|
|
|
// Widgets (collapsible) -|---> LinearLayout for search & recommendations
|
|
// Search bar (pinned) -|
|
|
// Widgets recommendation (collapsible)-|
|
|
// Personal | Work (pinned)
|
|
// 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.
|
|
RelativeLayout.LayoutParams tabsLayoutParams =
|
|
(RelativeLayout.LayoutParams) mPrimaryWorkTabsView.getLayoutParams();
|
|
tabsLayoutParams.topMargin = topContainerHeight;
|
|
mPrimaryWorkTabsView.setLayoutParams(tabsLayoutParams);
|
|
|
|
// 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 topOffsetAfterAllViewsCollapsed =
|
|
topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
|
|
|
|
RelativeLayout.LayoutParams viewPagerLayoutParams =
|
|
(RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
|
|
if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
|
|
viewPagerLayoutParams.topMargin = topOffsetAfterAllViewsCollapsed;
|
|
mPrimaryWorkViewPager.setLayoutParams(viewPagerLayoutParams);
|
|
hasMarginOrPaddingUpdated = true;
|
|
}
|
|
|
|
if (mPrimaryRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
|
|
mPrimaryRecyclerView.setPadding(
|
|
mPrimaryRecyclerView.getPaddingLeft(),
|
|
mCollapsibleHeightForTabs,
|
|
mPrimaryRecyclerView.getPaddingRight(),
|
|
mPrimaryRecyclerView.getPaddingBottom());
|
|
hasMarginOrPaddingUpdated = true;
|
|
}
|
|
if (mWorkRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
|
|
mWorkRecyclerView.setPadding(
|
|
mWorkRecyclerView.getPaddingLeft(),
|
|
mCollapsibleHeightForTabs,
|
|
mWorkRecyclerView.getPaddingRight(),
|
|
mWorkRecyclerView.getPaddingBottom());
|
|
hasMarginOrPaddingUpdated = true;
|
|
}
|
|
} else {
|
|
if (mPrimaryRecyclerView.getPaddingTop() != topContainerHeight) {
|
|
mPrimaryRecyclerView.setPadding(
|
|
mPrimaryRecyclerView.getPaddingLeft(),
|
|
topContainerHeight,
|
|
mPrimaryRecyclerView.getPaddingRight(),
|
|
mPrimaryRecyclerView.getPaddingBottom());
|
|
hasMarginOrPaddingUpdated = true;
|
|
}
|
|
}
|
|
if (mSearchRecyclerView.getPaddingTop() != topContainerHeight) {
|
|
mSearchRecyclerView.setPadding(
|
|
mSearchRecyclerView.getPaddingLeft(),
|
|
topContainerHeight,
|
|
mSearchRecyclerView.getPaddingRight(),
|
|
mSearchRecyclerView.getPaddingBottom());
|
|
hasMarginOrPaddingUpdated = true;
|
|
}
|
|
return hasMarginOrPaddingUpdated;
|
|
}
|
|
|
|
/**
|
|
* 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 unused) {
|
|
// Always use the recycler view offset because fast scroller offset has a different scale.
|
|
int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
|
|
if (recyclerViewYOffset < 0) return;
|
|
|
|
if (mCollapsibleHeightForRecommendation > 0) {
|
|
int yDisplacement = Math.max(-recyclerViewYOffset,
|
|
-mCollapsibleHeightForRecommendation);
|
|
mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
|
|
mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
|
|
}
|
|
|
|
if (mCollapsibleHeightForSearch > 0) {
|
|
int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch);
|
|
mViewHolder.mSearchBar.setTranslationY(searchYDisplacement);
|
|
}
|
|
|
|
if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
|
|
int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs);
|
|
mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
|
|
}
|
|
}
|
|
|
|
/** Resets any previous view translation. */
|
|
public void reset() {
|
|
mViewHolder.mHeaderTitle.setTranslationY(0);
|
|
mViewHolder.mSearchBar.setTranslationY(0);
|
|
if (mHasWorkProfile) {
|
|
mPrimaryWorkTabsView.setTranslationY(0);
|
|
}
|
|
}
|
|
|
|
/** private the height, in pixel, + the vertical margins of a given view. */
|
|
private static int measureHeightWithVerticalMargins(View view) {
|
|
if (view.getVisibility() != View.VISIBLE) {
|
|
return 0;
|
|
}
|
|
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
|
|
return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
|
|
+ marginLayoutParams.topMargin;
|
|
}
|
|
}
|