Replace taskbar hotseat with real hotseat when folder is open

- Seamlessly show real hotseat and hide taskbar hotseat, while
  keeping rest of taskbar visible
- Update MultiValueAlpha to allow for taking max alpha instead
  of blending, and use that for Hotseat
- Fix folder open bounds on home screen when taskbar is present

Test: Open folder from taskbar on home, can drag out items
Bug: 182079330
Bug: 171917176
Change-Id: I7c1983e3219b1341cf233260f0ccac9051c4dc14
This commit is contained in:
Tony Wickham 2021-03-10 16:18:40 -08:00
parent 462384dbed
commit ae72b46b2c
9 changed files with 158 additions and 27 deletions

View File

@ -25,7 +25,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/taskbar_background"
android:gravity="center"
android:animateLayoutChanges="true"/>
android:gravity="center"/>
</com.android.launcher3.taskbar.TaskbarContainerView>

View File

@ -315,6 +315,10 @@ public abstract class BaseQuickstepLauncher extends Launcher
@Override
public void onDragLayerHierarchyChanged() {
onLauncherStateOrFocusChanged();
if (mTaskbarController != null) {
mTaskbarController.onLauncherDragLayerHierarchyChanged();
}
}
@Override

View File

@ -19,6 +19,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_REPLACE_TASKBAR_WITH_HOTSEAT;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@ -40,6 +42,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.R;
@ -144,22 +147,13 @@ public class TaskbarController {
ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
ActivityOptions.makeBasic());
} else if (tag instanceof FolderInfo) {
FolderIcon folderIcon = (FolderIcon) view;
Folder folder = folderIcon.getFolder();
setTaskbarWindowFullscreen(true);
mTaskbarContainerView.post(() -> {
folder.animateOpen();
folder.iterateOverItems((itemInfo, itemView) -> {
itemView.setOnClickListener(getItemOnClickListener());
itemView.setOnLongClickListener(getItemOnLongClickListener());
// To play haptic when dragging, like other Taskbar items do.
itemView.setHapticFeedbackEnabled(true);
return false;
});
});
if (mLauncher.hasBeenResumed()) {
FolderInfo folderInfo = (FolderInfo) tag;
onClickedOnFolderFromHome(folderInfo);
} else {
FolderIcon folderIcon = (FolderIcon) view;
onClickedOnFolderInApp(folderIcon);
}
} else {
ItemClickHandler.INSTANCE.onClick(view);
}
@ -169,6 +163,34 @@ public class TaskbarController {
};
}
// Open the real folder in Launcher.
private void onClickedOnFolderFromHome(FolderInfo folderInfo) {
alignRealHotseatWithTaskbar();
FolderIcon folderIcon = (FolderIcon) mLauncher.getHotseat()
.getFirstItemMatch((info, v) -> info == folderInfo);
folderIcon.post(folderIcon::performClick);
}
// Open the Taskbar folder, and handle clicks on folder items.
private void onClickedOnFolderInApp(FolderIcon folderIcon) {
Folder folder = folderIcon.getFolder();
setTaskbarWindowFullscreen(true);
mTaskbarContainerView.post(() -> {
folder.animateOpen();
folder.iterateOverItems((itemInfo, itemView) -> {
itemView.setOnClickListener(getItemOnClickListener());
itemView.setOnLongClickListener(getItemOnLongClickListener());
// To play haptic when dragging, like other Taskbar items do.
itemView.setHapticFeedbackEnabled(true);
return false;
});
});
}
@Override
public View.OnLongClickListener getItemOnLongClickListener() {
return view -> {
@ -306,6 +328,7 @@ public class TaskbarController {
mAnimator = createAnimToLauncher(null, duration);
} else {
mAnimator = createAnimToApp(duration);
replaceTaskbarWithHotseatOrViceVersa();
}
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@ -355,6 +378,7 @@ public class TaskbarController {
@Override
public void onAnimationStart(Animator animation) {
mTaskbarView.updateHotseatItemsVisibility();
setReplaceTaskbarWithHotseat(false);
}
});
return anim.buildAnim();
@ -451,6 +475,35 @@ public class TaskbarController {
mTaskbarView.getHeight() - hotseatBounds.bottom);
}
/**
* A view was added or removed from DragLayer, check if we need to hide our hotseat copy and
* show the real one instead.
*/
public void onLauncherDragLayerHierarchyChanged() {
replaceTaskbarWithHotseatOrViceVersa();
}
private void replaceTaskbarWithHotseatOrViceVersa() {
boolean replaceTaskbarWithHotseat = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
TYPE_ALL & TYPE_REPLACE_TASKBAR_WITH_HOTSEAT) != null;
if (!mLauncher.hasBeenResumed()) {
replaceTaskbarWithHotseat = false;
}
setReplaceTaskbarWithHotseat(replaceTaskbarWithHotseat);
}
private void setReplaceTaskbarWithHotseat(boolean replaceTaskbarWithHotseat) {
Hotseat hotseat = mLauncher.getHotseat();
if (replaceTaskbarWithHotseat) {
alignRealHotseatWithTaskbar();
hotseat.getReplaceTaskbarAlpha().setValue(1f);
mTaskbarView.setHotseatViewsHidden(true);
} else {
hotseat.getReplaceTaskbarAlpha().setValue(0f);
mTaskbarView.setHotseatViewsHidden(false);
}
}
private float getTaskbarScaleOnHome() {
return 1f / mTaskbarContainerView.getTaskbarActivityContext().getTaskbarIconScale();
}

View File

@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar;
import android.animation.LayoutTransition;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@ -63,6 +64,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
// Initialized in init().
private LayoutTransition mLayoutTransition;
private int mHotseatStartIndex;
private int mHotseatEndIndex;
private View mHotseatRecentsDivider;
@ -76,6 +78,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
private boolean mIsDraggingItem;
// Only non-null when the corresponding Folder is open.
private @Nullable FolderIcon mLeaveBehindFolderIcon;
private boolean mIsHotseatHidden;
public TaskbarView(@NonNull Context context) {
this(context, null);
@ -107,6 +110,9 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
}
protected void init(int numHotseatIcons, int numRecentIcons) {
mLayoutTransition = new LayoutTransition();
setLayoutTransitionsEnabled(true);
mHotseatStartIndex = 0;
mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
updateHotseatItems(new ItemInfo[numHotseatIcons]);
@ -119,6 +125,10 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
updateRecentTasks(new Task[numRecentIcons]);
}
private void setLayoutTransitionsEnabled(boolean enabled) {
setLayoutTransition(enabled ? mLayoutTransition : null);
}
protected void cleanup() {
removeAllViews();
}
@ -206,9 +216,19 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
}
}
/**
* Hides or shows the hotseat items immediately (without layout transitions).
*/
protected void setHotseatViewsHidden(boolean hidden) {
mIsHotseatHidden = hidden;
setLayoutTransitionsEnabled(false);
updateHotseatItemsVisibility();
setLayoutTransitionsEnabled(true);
}
private void updateHotseatItemVisibility(View hotseatView) {
if (hotseatView.getTag() != null) {
hotseatView.setVisibility(VISIBLE);
hotseatView.setVisibility(mIsHotseatHidden ? INVISIBLE : VISIBLE);
} else {
int oldVisibility = hotseatView.getVisibility();
int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility();

View File

@ -98,6 +98,9 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
| TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER;
// When these types of floating views are open, hide the taskbar hotseat and show the real one.
public static final int TYPE_REPLACE_TASKBAR_WITH_HOTSEAT = TYPE_FOLDER | TYPE_ACTION_POPUP;
public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
& ~TYPE_ALL_APPS_EDU;

View File

@ -708,10 +708,11 @@ public class DeviceProfile {
mInsets.top + availableHeightPx);
} else {
// Folders should only appear below the drop target bar and above the hotseat
int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
return new Rect(mInsets.left + edgeMarginPx,
mInsets.top + dropTargetBarSizePx + edgeMarginPx,
mInsets.left + availableWidthPx - edgeMarginPx,
mInsets.top + availableHeightPx - hotseatBarSizePx
mInsets.top + availableHeightPx - hotseatTop
- workspacePageIndicatorHeight - edgeMarginPx);
}
}

View File

@ -29,6 +29,7 @@ import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.MultiValueAlpha;
import java.util.function.Consumer;
@ -37,6 +38,10 @@ import java.util.function.Consumer;
*/
public class Hotseat extends CellLayout implements Insettable {
private static final int ALPHA_INDEX_STATE = 0;
private static final int ALPHA_INDEX_REPLACE_TASKBAR = 1;
private static final int NUM_ALPHA_CHANNELS = 2;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mHasVerticalHotseat;
private Workspace mWorkspace;
@ -44,6 +49,8 @@ public class Hotseat extends CellLayout implements Insettable {
@Nullable
private Consumer<Boolean> mOnVisibilityAggregatedCallback;
private final MultiValueAlpha mMultiValueAlpha;
public Hotseat(Context context) {
this(context, null);
}
@ -54,6 +61,8 @@ public class Hotseat extends CellLayout implements Insettable {
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS, MultiValueAlpha.Mode.MAX);
mMultiValueAlpha.setUpdateVisibility(true);
}
/**
@ -174,4 +183,12 @@ public class Hotseat extends CellLayout implements Insettable {
public View getFirstItemMatch(Workspace.ItemOperator itemOperator) {
return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
}
public MultiValueAlpha.AlphaProperty getStateAlpha() {
return mMultiValueAlpha.getProperty(ALPHA_INDEX_STATE);
}
public MultiValueAlpha.AlphaProperty getReplaceTaskbarAlpha() {
return mMultiValueAlpha.getProperty(ALPHA_INDEX_REPLACE_TASKBAR);
}
}

View File

@ -55,6 +55,7 @@ import com.android.launcher3.graphics.SysUiScrim;
import com.android.launcher3.graphics.WorkspaceDragScrim;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.systemui.plugins.ResourceProvider;
/**
@ -143,8 +144,8 @@ public class WorkspaceStateTransitionAnimation {
}
float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha,
config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
propertySetter.setFloat(hotseat.getStateAlpha(), MultiValueAlpha.VALUE,
hotseatIconsAlpha, config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
workspacePageIndicatorAlpha, fadeInterpolator);

View File

@ -42,16 +42,49 @@ public class MultiValueAlpha {
}
};
/**
* Determines how each alpha should factor into the final alpha.
*/
public enum Mode {
BLEND(1f) {
@Override
public float calculateNewAlpha(float currentAlpha, float otherAlpha) {
return currentAlpha * otherAlpha;
}
},
MAX(0f) {
@Override
public float calculateNewAlpha(float currentAlpha, float otherAlpha) {
return Math.max(currentAlpha, otherAlpha);
}
};
Mode(float startAlpha) {
mStartAlpha = startAlpha;
}
protected final float mStartAlpha;
protected abstract float calculateNewAlpha(float currentAlpha, float otherAlpha);
}
private final View mView;
private final AlphaProperty[] mMyProperties;
private final Mode mMode;
private int mValidMask;
// Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
private boolean mUpdateVisibility;
public MultiValueAlpha(View view, int size) {
this(view, size, Mode.BLEND);
}
public MultiValueAlpha(View view, int size, Mode mode) {
mView = view;
mMyProperties = new AlphaProperty[size];
mMode = mode;
mView.setAlpha(mMode.mStartAlpha);
mValidMask = 0;
for (int i = 0; i < size; i++) {
@ -79,9 +112,9 @@ public class MultiValueAlpha {
private final int mMyMask;
private float mValue = 1;
private float mValue = mMode.mStartAlpha;
// Factor of all other alpha channels, only valid if mMyMask is present in mValidMask.
private float mOthers = 1;
private float mOthers = mMode.mStartAlpha;
AlphaProperty(int myMask) {
mMyMask = myMask;
@ -94,10 +127,10 @@ public class MultiValueAlpha {
if ((mValidMask & mMyMask) == 0) {
// Our cache value is not correct, recompute it.
mOthers = 1;
mOthers = mMode.mStartAlpha;
for (AlphaProperty prop : mMyProperties) {
if (prop != this) {
mOthers *= prop.mValue;
mOthers = mMode.calculateNewAlpha(mOthers, prop.mValue);
}
}
}
@ -107,7 +140,7 @@ public class MultiValueAlpha {
mValidMask = mMyMask;
mValue = value;
mView.setAlpha(mOthers * mValue);
mView.setAlpha(mMode.calculateNewAlpha(mOthers, mValue));
if (mUpdateVisibility) {
AlphaUpdateListener.updateVisibility(mView);
}