Merge "Support for animating A-Z <-> Search." into tm-qpr-dev

This commit is contained in:
TreeHugger Robot
2022-09-10 01:11:36 +00:00
committed by Android (Google) Code Review
11 changed files with 417 additions and 58 deletions
+2 -2
View File
@@ -20,6 +20,8 @@
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_header_pill_height"
android:layout_gravity="center_horizontal"
android:paddingTop="@dimen/all_apps_tabs_vertical_padding"
android:paddingBottom="@dimen/all_apps_tabs_vertical_padding"
android:orientation="horizontal"
style="@style/TextHeadline">
@@ -28,7 +30,6 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/all_apps_tabs_button_horizontal_padding"
android:layout_marginVertical="@dimen/all_apps_tabs_vertical_padding"
android:layout_weight="1"
android:background="@drawable/all_apps_tabs_background"
android:text="@string/all_apps_personal_tab"
@@ -41,7 +42,6 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/all_apps_tabs_button_horizontal_padding"
android:layout_marginVertical="@dimen/all_apps_tabs_vertical_padding"
android:layout_weight="1"
android:background="@drawable/all_apps_tabs_background"
android:text="@string/all_apps_work_tab"
+1 -1
View File
@@ -18,7 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_results_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:focusable="true" />
+1
View File
@@ -26,6 +26,7 @@
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp"
android:background="@drawable/work_apps_toggle_background"
android:forceHasOverlappingRendering="false"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_corp_off"
android:layout_marginBottom="@dimen/work_fab_margin_bottom"
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps;
import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.SEARCH;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
@@ -34,7 +36,6 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.views.AppLauncher;
import java.util.ArrayList;
import java.util.Objects;
/**
* All apps container view with search support for use in a dragging activity.
@@ -44,6 +45,11 @@ import java.util.Objects;
public class ActivityAllAppsContainerView<T extends Context & AppLauncher
& DeviceProfileListenable> extends BaseAllAppsContainerView<T> {
private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300;
// Used to animate Search results out and A-Z apps in, or vice-versa.
private final SearchTransitionController mSearchTransitionController;
protected SearchUiManager mSearchUiManager;
/**
* View that defines the search box. Result is rendered inside the recycler view defined in the
@@ -52,6 +58,7 @@ public class ActivityAllAppsContainerView<T extends Context & AppLauncher
private View mSearchContainer;
/** {@code true} when rendered view is in search state instead of the scroll state. */
private boolean mIsSearching;
private boolean mRebindAdaptersAfterSearchAnimation;
public ActivityAllAppsContainerView(Context context) {
this(context, null);
@@ -63,6 +70,8 @@ public class ActivityAllAppsContainerView<T extends Context & AppLauncher
public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mSearchTransitionController = new SearchTransitionController(this);
}
public SearchUiManager getSearchUiManager() {
@@ -73,19 +82,10 @@ public class ActivityAllAppsContainerView<T extends Context & AppLauncher
return mSearchContainer;
}
/** Updates all apps container with the latest search query. */
public void setLastSearchQuery(String query) {
mIsSearching = true;
rebindAdapters();
mHeader.setCollapsed(true);
}
/** Invoke when the current search session is finished. */
public void onClearSearchResult() {
mIsSearching = false;
mHeader.setCollapsed(false);
animateToSearchState(false);
rebindAdapters();
mHeader.reset(false);
}
/**
@@ -93,12 +93,42 @@ public class ActivityAllAppsContainerView<T extends Context & AppLauncher
*/
public void setSearchResults(ArrayList<AdapterItem> results) {
if (getSearchResultList().setSearchResults(results)) {
for (int i = 0; i < mAH.size(); i++) {
if (mAH.get(i).mRecyclerView != null) {
mAH.get(i).mRecyclerView.onSearchResultsChanged();
}
}
getSearchRecyclerView().onSearchResultsChanged();
}
if (results != null) {
animateToSearchState(true);
}
}
private void animateToSearchState(boolean goingToSearch) {
animateToSearchState(goingToSearch, DEFAULT_SEARCH_TRANSITION_DURATION_MS);
}
private void animateToSearchState(boolean goingToSearch, long durationMs) {
if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) {
return;
}
if (goingToSearch) {
// Fade out the button to pause work apps.
mWorkManager.onActivePageChanged(SEARCH);
}
mSearchTransitionController.animateToSearchState(goingToSearch, durationMs,
/* onEndRunnable = */ () -> {
mIsSearching = goingToSearch;
updateSearchResultsVisibility();
int previousPage = getCurrentPage();
if (mRebindAdaptersAfterSearchAnimation) {
rebindAdapters(false);
mRebindAdaptersAfterSearchAnimation = false;
}
if (!goingToSearch) {
setSearchResults(null);
if (mViewPager != null) {
mViewPager.setCurrentPage(previousPage);
}
onActivePageChanged(previousPage);
}
});
}
@Override
@@ -121,6 +151,8 @@ public class ActivityAllAppsContainerView<T extends Context & AppLauncher
super.reset(animate);
// Reset the search bar after transitioning home.
mSearchUiManager.resetSearch();
// Animate to A-Z with 0 time to reset the animation with proper state management.
animateToSearchState(false, 0);
}
@Override
@@ -156,22 +188,31 @@ public class ActivityAllAppsContainerView<T extends Context & AppLauncher
return mIsSearching;
}
@Override
public void onActivePageChanged(int currentActivePage) {
if (mSearchTransitionController.isRunning()) {
// Will be called at the end of the animation.
return;
}
super.onActivePageChanged(currentActivePage);
}
@Override
protected void rebindAdapters(boolean force) {
if (mSearchTransitionController.isRunning()) {
mRebindAdaptersAfterSearchAnimation = true;
return;
}
super.rebindAdapters(force);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()
|| getMainAdapterProvider().getDecorator() == null) {
|| getMainAdapterProvider().getDecorator() == null
|| getSearchRecyclerView() == null) {
return;
}
RecyclerView.ItemDecoration decoration = getMainAdapterProvider().getDecorator();
mAH.stream()
.map(adapterHolder -> adapterHolder.mRecyclerView)
.filter(Objects::nonNull)
.forEach(v -> {
v.removeItemDecoration(decoration); // Remove in case it is already added.
v.addItemDecoration(decoration);
});
getSearchRecyclerView().removeItemDecoration(decoration); // In case it is already added.
getSearchRecyclerView().addItemDecoration(decoration);
}
@Override
@@ -127,6 +127,11 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
public boolean isContentSame(AdapterItem other) {
return itemInfo == null && other.itemInfo == null;
}
/** Sets the alpha of the decorator for this item. Returns true if successful. */
public boolean setDecorationFillAlpha(int alpha) {
return false;
}
}
protected final T mActivityContext;
@@ -107,12 +107,13 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY());
}
};
private final WorkProfileManager mWorkManager;
protected final WorkProfileManager mWorkManager;
private final Paint mNavBarScrimPaint;
private int mNavBarScrimHeight = 0;
private AllAppsPagedView mViewPager;
protected AllAppsPagedView mViewPager;
private SearchRecyclerView mSearchRecyclerView;
protected FloatingHeaderView mHeader;
@@ -349,7 +350,7 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
* The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently
* hidden while searching.
**/
private View getAppsRecyclerViewContainer() {
protected View getAppsRecyclerViewContainer() {
return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
}
@@ -527,7 +528,7 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
}
private void updateSearchResultsVisibility() {
protected void updateSearchResultsVisibility() {
if (isSearching()) {
getSearchRecyclerView().setVisibility(VISIBLE);
getAppsRecyclerViewContainer().setVisibility(GONE);
@@ -42,6 +42,7 @@ import com.android.systemui.plugins.AllAppsRow.OnHeightUpdatedListener;
import com.android.systemui.plugins.PluginListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
public class FloatingHeaderView extends LinearLayout implements
@@ -82,8 +83,8 @@ public class FloatingHeaderView extends LinearLayout implements
protected final Map<AllAppsRow, PluginHeaderRow> mPluginRows = new ArrayMap<>();
// These two values are necessary to ensure that the header protection is drawn correctly.
private final int mHeaderTopAdjustment;
private final int mHeaderBottomAdjustment;
private final int mTabsAdditionalPaddingTop;
private final int mTabsAdditionalPaddingBottom;
private boolean mHeaderProtectionSupported;
protected ViewGroup mTabLayout;
@@ -91,7 +92,6 @@ public class FloatingHeaderView extends LinearLayout implements
private AllAppsRecyclerView mWorkRV;
private SearchRecyclerView mSearchRV;
private AllAppsRecyclerView mCurrentRV;
public boolean mHeaderCollapsed;
protected int mSnappedScrolledY;
private int mTranslationY;
@@ -100,7 +100,12 @@ public class FloatingHeaderView extends LinearLayout implements
protected boolean mTabsHidden;
protected int mMaxTranslation;
private boolean mCollapsed = false;
// Whether the header has been scrolled off-screen.
private boolean mHeaderCollapsed;
// Whether floating rows like predicted apps are hidden.
private boolean mFloatingRowsCollapsed;
// Total height of all current floating rows. Collapsed rows == 0 height.
private int mFloatingRowsHeight;
// This is initialized once during inflation and stays constant after that. Fixed views
// cannot be added or removed dynamically.
@@ -117,9 +122,9 @@ public class FloatingHeaderView extends LinearLayout implements
public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mHeaderTopAdjustment = context.getResources()
mTabsAdditionalPaddingTop = context.getResources()
.getDimensionPixelSize(R.dimen.all_apps_header_top_adjustment);
mHeaderBottomAdjustment = context.getResources()
mTabsAdditionalPaddingBottom = context.getResources()
.getDimensionPixelSize(R.dimen.all_apps_header_bottom_adjustment);
mHeaderProtectionSupported = context.getResources().getBoolean(
R.bool.config_header_protection_supported);
@@ -148,6 +153,7 @@ public class FloatingHeaderView extends LinearLayout implements
}
mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
mAllRows = mFixedRows;
updateFloatingRowsHeight();
}
@Override
@@ -179,6 +185,7 @@ public class FloatingHeaderView extends LinearLayout implements
count++;
}
}
updateFloatingRowsHeight();
}
@Override
@@ -195,7 +202,7 @@ public class FloatingHeaderView extends LinearLayout implements
int oldMaxHeight = mMaxTranslation;
updateExpectedHeight();
if (mMaxTranslation != oldMaxHeight || mCollapsed) {
if (mMaxTranslation != oldMaxHeight || mFloatingRowsCollapsed) {
BaseAllAppsContainerView<?> parent = (BaseAllAppsContainerView<?>) getParent();
if (parent != null) {
parent.setupHeader();
@@ -258,20 +265,19 @@ public class FloatingHeaderView extends LinearLayout implements
}
private void updateExpectedHeight() {
updateFloatingRowsHeight();
mMaxTranslation = 0;
if (mCollapsed) {
if (mFloatingRowsCollapsed) {
return;
}
for (FloatingHeaderRow row : mAllRows) {
mMaxTranslation += row.getExpectedHeight();
}
mMaxTranslation += mFloatingRowsHeight;
if (!mTabsHidden) {
mMaxTranslation += mHeaderBottomAdjustment;
mMaxTranslation += mTabsAdditionalPaddingBottom;
}
}
public int getMaxTranslation() {
if (mMaxTranslation == 0 && mTabsHidden) {
if (mMaxTranslation == 0 && (mTabsHidden || mFloatingRowsCollapsed)) {
return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
} else if (mMaxTranslation > 0 && mTabsHidden) {
return mMaxTranslation + getPaddingTop();
@@ -312,7 +318,7 @@ public class FloatingHeaderView extends LinearLayout implements
int uncappedTranslationY = mTranslationY;
mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
if (mCollapsed || uncappedTranslationY < mTranslationY - getPaddingTop()) {
if (mFloatingRowsCollapsed || uncappedTranslationY < mTranslationY - getPaddingTop()) {
// we hide it completely if already capped (for opening search anim)
for (FloatingHeaderRow row : mAllRows) {
row.setVerticalScroll(0, true /* isScrolledOut */);
@@ -325,11 +331,11 @@ public class FloatingHeaderView extends LinearLayout implements
mTabLayout.setTranslationY(mTranslationY);
int clipTop = getPaddingTop() - mHeaderTopAdjustment;
int clipTop = getPaddingTop() - mTabsAdditionalPaddingTop;
if (mTabsHidden) {
clipTop += getPaddingBottom() - mHeaderBottomAdjustment;
clipTop += getPaddingBottom() - mTabsAdditionalPaddingBottom;
}
mRVClip.top = mTabsHidden ? clipTop : 0;
mRVClip.top = mTabsHidden || mFloatingRowsCollapsed ? clipTop : 0;
mHeaderClip.top = clipTop;
// clipping on a draw might cause additional redraw
setClipBounds(mHeaderClip);
@@ -347,10 +353,12 @@ public class FloatingHeaderView extends LinearLayout implements
/**
* Hides all the floating rows
*/
public void setCollapsed(boolean collapse) {
if (mCollapsed == collapse) return;
public void setFloatingRowsCollapsed(boolean collapsed) {
if (mFloatingRowsCollapsed == collapsed) {
return;
}
mCollapsed = collapse;
mFloatingRowsCollapsed = collapsed;
onHeightUpdated();
}
@@ -376,6 +384,30 @@ public class FloatingHeaderView extends LinearLayout implements
return !mHeaderCollapsed;
}
/** Returns true if personal/work tabs are currently in use. */
public boolean usingTabs() {
return !mTabsHidden;
}
ViewGroup getTabLayout() {
return mTabLayout;
}
/** Calculates the combined height of any floating rows (e.g. predicted apps, app divider). */
private void updateFloatingRowsHeight() {
mFloatingRowsHeight =
Arrays.stream(mAllRows).mapToInt(FloatingHeaderRow::getExpectedHeight).sum();
}
/** Gets the combined height of any floating rows (e.g. predicted apps, app divider). */
int getFloatingRowsHeight() {
return mFloatingRowsHeight;
}
int getTabsAdditionalPaddingBottom() {
return mTabsAdditionalPaddingBottom;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTranslationY = (Integer) animation.getAnimatedValue();
@@ -447,9 +479,10 @@ public class FloatingHeaderView extends LinearLayout implements
// we only want to show protection when work tab is available and header is either
// collapsed or animating to/from collapsed state
if (mTabsHidden || !mHeaderCollapsed) {
if (mTabsHidden || mFloatingRowsCollapsed || !mHeaderCollapsed) {
return 0;
}
return Math.max(getHeight() - getPaddingTop() + mTranslationY, 0);
return Math.max(0,
getTabLayout().getBottom() - getPaddingTop() + getPaddingBottom() + mTranslationY);
}
}
@@ -17,12 +17,17 @@ package com.android.launcher3.allapps;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import com.android.launcher3.views.RecyclerViewFastScroller;
/** A RecyclerView for AllApps Search results. */
public class SearchRecyclerView extends AllAppsRecyclerView {
private static final String TAG = "SearchRecyclerView";
private Consumer<View> mChildAttachedConsumer;
public SearchRecyclerView(Context context) {
this(context, null);
@@ -41,6 +46,11 @@ public class SearchRecyclerView extends AllAppsRecyclerView {
super(context, attrs, defStyleAttr, defStyleRes);
}
/** This will be called just before a new child is attached to the window. */
public void setChildAttachedConsumer(Consumer<View> childAttachedConsumer) {
mChildAttachedConsumer = childAttachedConsumer;
}
@Override
protected void updatePoolSize() {
RecycledViewPool pool = getRecycledViewPool();
@@ -57,4 +67,12 @@ public class SearchRecyclerView extends AllAppsRecyclerView {
public RecyclerViewFastScroller getScrollbar() {
return null;
}
@Override
public void onChildAttachedToWindow(@NonNull View child) {
if (mChildAttachedConsumer != null) {
mChildAttachedConsumer.accept(child);
}
super.onChildAttachedToWindow(child);
}
}
@@ -0,0 +1,258 @@
/*
* Copyright (C) 2022 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.allapps;
import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import android.animation.ObjectAnimator;
import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
/** Coordinates the transition between Search and A-Z in All Apps. */
public class SearchTransitionController {
// Interpolator when the user taps the QSB while already in All Apps.
private static final Interpolator DEFAULT_INTERPOLATOR_WITHIN_ALL_APPS = DEACCEL_1_7;
// Interpolator when the user taps the QSB from home screen, so transition to all apps is
// happening simultaneously.
private static final Interpolator DEFAULT_INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = LINEAR;
/**
* These values represent points on the [0, 1] animation progress spectrum. They are used to
* animate items in the {@link SearchRecyclerView}.
*/
private static final float TOP_CONTENT_FADE_PROGRESS_START = 0.133f;
private static final float CONTENT_FADE_PROGRESS_DURATION = 0.083f;
private static final float TOP_BACKGROUND_FADE_PROGRESS_START = 0.633f;
private static final float BACKGROUND_FADE_PROGRESS_DURATION = 0.15f;
private static final float CONTENT_STAGGER = 0.01f; // Progress before next item starts fading.
private static final FloatProperty<SearchTransitionController> SEARCH_TO_AZ_PROGRESS =
new FloatProperty<SearchTransitionController>("searchToAzProgress") {
@Override
public Float get(SearchTransitionController controller) {
return controller.getSearchToAzProgress();
}
@Override
public void setValue(SearchTransitionController controller, float progress) {
controller.setSearchToAzProgress(progress);
}
};
private final ActivityAllAppsContainerView<?> mAllAppsContainerView;
private ObjectAnimator mSearchToAzAnimator = null;
private float mSearchToAzProgress = 1f;
public SearchTransitionController(ActivityAllAppsContainerView<?> allAppsContainerView) {
mAllAppsContainerView = allAppsContainerView;
}
/** Returns true if a transition animation is currently in progress. */
public boolean isRunning() {
return mSearchToAzAnimator != null;
}
/**
* Starts the transition to or from search state. If a transition is already in progress, the
* animation will start from that point with the new duration, and the previous onEndRunnable
* will not be called.
*
* @param goingToSearch true if will be showing search results, otherwise will be showing a-z
* @param duration time in ms for the animation to run
* @param onEndRunnable will be called when the animation finishes, unless another animation is
* scheduled in the meantime
*/
public void animateToSearchState(boolean goingToSearch, long duration, Runnable onEndRunnable) {
float targetProgress = goingToSearch ? 0 : 1;
if (mSearchToAzAnimator != null) {
mSearchToAzAnimator.cancel();
}
mSearchToAzAnimator = ObjectAnimator.ofFloat(this, SEARCH_TO_AZ_PROGRESS, targetProgress);
boolean inAllApps = Launcher.getLauncher(
mAllAppsContainerView.getContext()).getStateManager().isInStableState(
LauncherState.ALL_APPS);
mSearchToAzAnimator.setDuration(duration).setInterpolator(
inAllApps ? DEFAULT_INTERPOLATOR_WITHIN_ALL_APPS
: DEFAULT_INTERPOLATOR_TRANSITIONING_TO_ALL_APPS);
mSearchToAzAnimator.addListener(forEndCallback(() -> mSearchToAzAnimator = null));
if (!goingToSearch) {
mSearchToAzAnimator.addListener(forSuccessCallback(() -> {
mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(false);
mAllAppsContainerView.getFloatingHeaderView().reset(false /* animate */);
mAllAppsContainerView.getAppsRecyclerViewContainer().setTranslationY(0);
}));
}
mSearchToAzAnimator.addListener(forSuccessCallback(onEndRunnable));
mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true);
mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE);
getSearchRecyclerView().setVisibility(VISIBLE);
getSearchRecyclerView().setChildAttachedConsumer(this::onSearchChildAttached);
mSearchToAzAnimator.start();
}
private SearchRecyclerView getSearchRecyclerView() {
return mAllAppsContainerView.getSearchRecyclerView();
}
private void setSearchToAzProgress(float searchToAzProgress) {
mSearchToAzProgress = searchToAzProgress;
int searchHeight = updateSearchRecyclerViewProgress();
FloatingHeaderView headerView = mAllAppsContainerView.getFloatingHeaderView();
// Add predictions + app divider height to account for predicted apps which will now be in
// the Search RV instead of the floating header view. Note `getFloatingRowsHeight` returns 0
// when predictions are not shown.
int appsTranslationY = searchHeight + headerView.getFloatingRowsHeight();
if (headerView.usingTabs()) {
// Move tabs below the search results, and fade them out in 20% of the animation.
headerView.setTranslationY(searchHeight);
headerView.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
// Account for the additional padding added for the tabs.
appsTranslationY -=
headerView.getPaddingTop() - headerView.getTabsAdditionalPaddingBottom();
}
View appsContainer = mAllAppsContainerView.getAppsRecyclerViewContainer();
appsContainer.setTranslationY(appsTranslationY);
// Fade apps out with tabs (in 20% of the total animation).
appsContainer.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
}
/**
* Updates the children views of SearchRecyclerView based on the current animation progress.
*
* @return the total height of animating views (excluding any app icons).
*/
private int updateSearchRecyclerViewProgress() {
int numSearchResultsAnimated = 0;
int totalHeight = 0;
int appRowHeight = 0;
Integer top = null;
SearchRecyclerView searchRecyclerView = getSearchRecyclerView();
for (int i = 0; i < searchRecyclerView.getChildCount(); i++) {
View searchResultView = searchRecyclerView.getChildAt(i);
if (searchResultView == null) {
continue;
}
if (top == null) {
top = searchResultView.getTop();
}
if (searchResultView instanceof BubbleTextView) {
// The first app icon will set appRowHeight, which will also contribute to
// totalHeight. Additional app icons should remove the appRowHeight to remain in
// the same row as the first app.
searchResultView.setY(top + totalHeight - appRowHeight);
if (appRowHeight == 0) {
appRowHeight = searchResultView.getHeight();
totalHeight += appRowHeight;
}
// Don't scale/fade app row.
continue;
}
// Adjust content alpha based on start progress and stagger.
float startContentFadeProgress = Math.max(0,
TOP_CONTENT_FADE_PROGRESS_START - CONTENT_STAGGER * numSearchResultsAnimated);
float endContentFadeProgress = Math.min(1,
startContentFadeProgress + CONTENT_FADE_PROGRESS_DURATION);
searchResultView.setAlpha(1 - clampToProgress(mSearchToAzProgress,
startContentFadeProgress, endContentFadeProgress));
// Adjust background (or decorator) alpha based on start progress and stagger.
float startBackgroundFadeProgress = Math.max(0,
TOP_BACKGROUND_FADE_PROGRESS_START
- CONTENT_STAGGER * numSearchResultsAnimated);
float endBackgroundFadeProgress = Math.min(1,
startBackgroundFadeProgress + BACKGROUND_FADE_PROGRESS_DURATION);
float backgroundAlpha = 1 - clampToProgress(mSearchToAzProgress,
startBackgroundFadeProgress, endBackgroundFadeProgress);
int adapterPosition = searchRecyclerView.getChildAdapterPosition(searchResultView);
boolean decoratorFilled =
adapterPosition != NO_POSITION
&& searchRecyclerView.getApps().getAdapterItems().get(adapterPosition)
.setDecorationFillAlpha((int) (255 * backgroundAlpha));
if (!decoratorFilled) {
// Try to adjust background alpha instead (e.g. for Search Edu card).
Drawable background = searchResultView.getBackground();
if (background != null) {
background.setAlpha((int) (255 * backgroundAlpha));
}
}
float scaleY = 1 - mSearchToAzProgress;
int scaledHeight = (int) (searchResultView.getHeight() * scaleY);
searchResultView.setScaleY(scaleY);
searchResultView.setY(top + totalHeight);
numSearchResultsAnimated++;
totalHeight += scaledHeight;
}
return totalHeight - appRowHeight;
}
/** Called just before a child is attached to the SearchRecyclerView. */
private void onSearchChildAttached(View child) {
// Avoid allocating hardware layers for alpha changes.
child.forceHasOverlappingRendering(false);
if (mSearchToAzProgress > 0) {
// Before the child is rendered, apply the animation including it to avoid flicker.
updateSearchRecyclerViewProgress();
} else {
// Apply default states without processing the full layout.
child.setAlpha(1);
child.setScaleY(1);
child.setTranslationY(0);
int adapterPosition = getSearchRecyclerView().getChildAdapterPosition(child);
if (adapterPosition != NO_POSITION) {
getSearchRecyclerView().getApps().getAdapterItems().get(adapterPosition)
.setDecorationFillAlpha(255);
}
if (child.getBackground() != null) {
child.getBackground().setAlpha(255);
}
}
}
private float getSearchToAzProgress() {
return mSearchToAzProgress;
}
}
@@ -168,14 +168,11 @@ public class AppsSearchContainerLayout extends ExtendedEditText
public void onSearchResult(String query, ArrayList<AdapterItem> items) {
if (items != null) {
mAppsView.setSearchResults(items);
mAppsView.setLastSearchQuery(query);
}
}
@Override
public void clearSearchResult() {
mAppsView.setSearchResults(null);
// Clear the search query
mSearchQueryBuilder.clear();
mSearchQueryBuilder.clearSpans();
@@ -22,6 +22,7 @@ import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.util.Log;
import android.view.View;
@@ -135,7 +136,11 @@ public class WorkProfileTest extends AbstractLauncherUiTest {
LauncherInstrumentation.WAIT_TIME_MS);
//start work profile toggle OFF test
executeOnLauncher(l -> l.getAppsView().getWorkManager().getWorkModeSwitch().performClick());
executeOnLauncher(l -> {
// Ensure updates are not deferred so notification happens when apps pause.
l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
l.getAppsView().getWorkManager().getWorkModeSwitch().performClick();
});
waitForLauncherCondition("Work profile toggle OFF failed", launcher -> {
manager.reset(); // pulls current state from system