Files
Lawnchair/src/com/android/launcher3/Hotseat.java
T
mpodolian 06fb33ceb3 Align hotseat drop target with the adjusted hotseat
Added logic to adjust the drop target to account for changes in the
hotseat position when accommodating the bubble bar.

Fixes: 382348730
Test: all tests in package src.com.android.launcher3.celllayout
Test: Manual. Ensure bubbles are present, and the hotseat width is
adjusted. Long-press any icon in the hotseat, begin dragging it over
other hotseat icons, and observe that the drop target is drawn
appropriately.
Flag: com.android.wm.shell.enable_bubble_bar

Change-Id: Ice961f40ace03077ab8c9312a93eda1a16d7da34
2024-12-06 14:57:31 -08:00

387 lines
15 KiB
Java

/*
* Copyright (C) 2011 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;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_BUBBLE_ADJUSTMENT_ANIM;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.util.HorizontalInsettableView;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.views.ActivityContext;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* View class that represents the bottom row of the home screen.
*/
public class Hotseat extends CellLayout implements Insettable {
public static final int ALPHA_CHANNEL_TASKBAR_ALIGNMENT = 0;
public static final int ALPHA_CHANNEL_PREVIEW_RENDERER = 1;
public static final int ALPHA_CHANNEL_TASKBAR_STASH = 2;
public static final int ALPHA_CHANNEL_CHANNELS_COUNT = 3;
@Retention(RetentionPolicy.RUNTIME)
@IntDef({ALPHA_CHANNEL_TASKBAR_ALIGNMENT, ALPHA_CHANNEL_PREVIEW_RENDERER,
ALPHA_CHANNEL_TASKBAR_STASH})
public @interface HotseatQsbAlphaId {
}
public static final int ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT = 0;
public static final int ICONS_TRANSLATION_X_CHANNELS_COUNT = 1;
@Retention(RetentionPolicy.RUNTIME)
@IntDef({ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT})
public @interface IconsTranslationX {
}
// Ratio of empty space, qsb should take up to appear visually centered.
public static final float QSB_CENTER_FACTOR = .325f;
private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mHasVerticalHotseat;
private Workspace<?> mWorkspace;
private boolean mSendTouchToWorkspace;
private final MultiValueAlpha mIconsAlphaChannels;
private final MultiValueAlpha mQsbAlphaChannels;
private @Nullable MultiProperty mQsbTranslationX;
private final MultiPropertyFactory mIconsTranslationXFactory;
private final View mQsb;
public Hotseat(Context context) {
this(context, null);
}
public Hotseat(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
addView(mQsb);
mIconsAlphaChannels = new MultiValueAlpha(getShortcutsAndWidgets(),
ALPHA_CHANNEL_CHANNELS_COUNT);
if (mQsb instanceof Reorderable qsbReorderable) {
mQsbTranslationX = qsbReorderable.getTranslateDelegate()
.getTranslationX(MultiTranslateDelegate.INDEX_NAV_BAR_ANIM);
}
mIconsTranslationXFactory = new MultiPropertyFactory<>(getShortcutsAndWidgets(),
VIEW_TRANSLATE_X, ICONS_TRANSLATION_X_CHANNELS_COUNT, Float::sum);
mQsbAlphaChannels = new MultiValueAlpha(mQsb, ALPHA_CHANNEL_CHANNELS_COUNT);
}
/** Provides translation X for hotseat icons for the channel. */
public MultiProperty getIconsTranslationX(@IconsTranslationX int channelId) {
return mIconsTranslationXFactory.get(channelId);
}
/** Provides translation X for hotseat Qsb. */
@Nullable
public MultiProperty getQsbTranslationX() {
return mQsbTranslationX;
}
/**
* Returns orientation specific cell X given invariant order in the hotseat
*/
public int getCellXFromOrder(int rank) {
return mHasVerticalHotseat ? 0 : rank;
}
/**
* Returns orientation specific cell Y given invariant order in the hotseat
*/
public int getCellYFromOrder(int rank) {
return mHasVerticalHotseat ? (getCountY() - (rank + 1)) : 0;
}
boolean isHasVerticalHotseat() {
return mHasVerticalHotseat;
}
public void resetLayout(boolean hasVerticalHotseat) {
ActivityContext activityContext = ActivityContext.lookupContext(getContext());
boolean bubbleBarEnabled = activityContext.isBubbleBarEnabled();
boolean hasBubbles = activityContext.hasBubbles();
removeAllViewsInLayout();
mHasVerticalHotseat = hasVerticalHotseat;
DeviceProfile dp = mActivity.getDeviceProfile();
if (bubbleBarEnabled) {
if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
getShortcutsAndWidgets().setTranslationProvider(
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
if (mQsb instanceof HorizontalInsettableView) {
HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth;
// post this to the looper so that QSB has a chance to redraw itself, e.g.
// after device rotation
mQsb.post(() -> insettableQsb.setHorizontalInsets(insetFraction));
}
} else {
getShortcutsAndWidgets().setTranslationProvider(null);
if (mQsb instanceof HorizontalInsettableView) {
((HorizontalInsettableView) mQsb).setHorizontalInsets(0);
}
}
}
resetCellSize(dp);
if (hasVerticalHotseat) {
setGridSize(1, dp.numShownHotseatIcons);
} else {
setGridSize(dp.numShownHotseatIcons, 1);
}
}
/**
* Adjust the hotseat icons for the bubble bar.
*
* <p>When the bubble bar becomes visible, if needed, this method animates the hotseat icons
* to reduce the spacing between them and make room for the bubble bar. The QSB width is
* animated as well to align with the hotseat icons.
*
* <p>When the bubble bar goes away, any adjustments that were previously made are reversed.
*/
public void adjustForBubbleBar(boolean isBubbleBarVisible) {
DeviceProfile dp = mActivity.getDeviceProfile();
boolean shouldAdjust = isBubbleBarVisible
&& dp.shouldAdjustHotseatOrQsbForBubbleBar(getContext());
boolean shouldAdjustHotseat = shouldAdjust
&& dp.shouldAlignBubbleBarWithHotseat();
ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
// update the translation provider for future layout passes of hotseat icons.
if (shouldAdjustHotseat) {
icons.setTranslationProvider(
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
} else {
icons.setTranslationProvider(null);
}
AnimatorSet animatorSet = new AnimatorSet();
for (int i = 0; i < icons.getChildCount(); i++) {
View child = icons.getChildAt(i);
if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) {
float tx = shouldAdjustHotseat
? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0;
if (child instanceof Reorderable) {
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
animatorSet.play(
mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
} else {
animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
}
}
}
//TODO(b/381109832) refactor & simplify adjustment logic
boolean shouldAdjustQsb =
shouldAdjustHotseat || (shouldAdjust && dp.shouldAlignBubbleBarWithQSB());
if (mQsb instanceof HorizontalInsettableView horizontalInsettableQsb) {
final float currentInsetFraction = horizontalInsettableQsb.getHorizontalInsets();
final float targetInsetFraction = shouldAdjustQsb
? (float) dp.iconSizePx / dp.hotseatQsbWidth : 0;
ValueAnimator qsbAnimator =
ValueAnimator.ofFloat(currentInsetFraction, targetInsetFraction);
qsbAnimator.addUpdateListener(animation -> {
float insetFraction = (float) animation.getAnimatedValue();
horizontalInsettableQsb.setHorizontalInsets(insetFraction);
});
animatorSet.play(qsbAnimator);
}
animatorSet.setDuration(BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS).start();
}
@Override
protected int getTranslationXForCell(int cellX, int cellY) {
TranslationProvider translationProvider = getShortcutsAndWidgets().getTranslationProvider();
if (translationProvider == null) return 0;
return (int) translationProvider.getTranslationX(cellX);
}
@Override
public void setInsets(Rect insets) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
DeviceProfile grid = mActivity.getDeviceProfile();
if (grid.isVerticalBarLayout()) {
mQsb.setVisibility(View.GONE);
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
if (grid.isSeascape()) {
lp.gravity = Gravity.LEFT;
lp.width = grid.hotseatBarSizePx + insets.left;
} else {
lp.gravity = Gravity.RIGHT;
lp.width = grid.hotseatBarSizePx + insets.right;
}
} else {
mQsb.setVisibility(View.VISIBLE);
lp.gravity = Gravity.BOTTOM;
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = grid.hotseatBarSizePx;
}
Rect padding = grid.getHotseatLayoutPadding(getContext());
setPadding(padding.left, padding.top, padding.right, padding.bottom);
setLayoutParams(lp);
InsettableFrameLayout.dispatchInsets(this, insets);
}
public void setWorkspace(Workspace<?> w) {
mWorkspace = w;
setCellLayoutContainer(w);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// We allow horizontal workspace scrolling from within the Hotseat. We do this by delegating
// touch intercept the Workspace, and if it intercepts, delegating touch to the Workspace
// for the remainder of the this input stream.
int yThreshold = getMeasuredHeight() - getPaddingBottom();
if (mWorkspace != null && ev.getY() <= yThreshold) {
mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
return mSendTouchToWorkspace;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// See comment in #onInterceptTouchEvent
if (mSendTouchToWorkspace) {
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mSendTouchToWorkspace = false;
}
return mWorkspace.onTouchEvent(event);
}
// Always let touch follow through to Workspace.
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
DeviceProfile dp = mActivity.getDeviceProfile();
mQsb.measure(MeasureSpec.makeMeasureSpec(dp.hotseatQsbWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(dp.hotseatQsbHeight, MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int qsbMeasuredWidth = mQsb.getMeasuredWidth();
int left;
DeviceProfile dp = mActivity.getDeviceProfile();
if (dp.isQsbInline) {
int qsbSpace = dp.hotseatBorderSpace;
left = Utilities.isRtl(getResources()) ? r - getPaddingRight() + qsbSpace
: l + getPaddingLeft() - qsbMeasuredWidth - qsbSpace;
} else {
left = (r - l - qsbMeasuredWidth) / 2;
}
int right = left + qsbMeasuredWidth;
int bottom = b - t - dp.getQsbOffsetY();
int top = bottom - dp.hotseatQsbHeight;
mQsb.layout(left, top, right, bottom);
}
/**
* Sets the alpha value of the specified alpha channel of just our ShortcutAndWidgetContainer.
*/
public void setIconsAlpha(float alpha, @HotseatQsbAlphaId int channelId) {
getIconsAlpha(channelId).setValue(alpha);
}
/**
* Sets the alpha value of just our QSB.
*/
public void setQsbAlpha(float alpha, @HotseatQsbAlphaId int channelId) {
getQsbAlpha(channelId).setValue(alpha);
}
/** Returns the alpha channel for ShortcutAndWidgetContainer */
public MultiProperty getIconsAlpha(@HotseatQsbAlphaId int channelId) {
return mIconsAlphaChannels.get(channelId);
}
/** Returns the alpha channel for Qsb */
public MultiProperty getQsbAlpha(@HotseatQsbAlphaId int channelId) {
return mQsbAlphaChannels.get(channelId);
}
/**
* Returns the QSB inside hotseat
*/
public View getQsb() {
return mQsb;
}
/** Dumps the Hotseat internal state */
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "Hotseat:");
mIconsAlphaChannels.dump(
prefix + "\t",
writer,
"mIconsAlphaChannels",
"ALPHA_CHANNEL_TASKBAR_ALIGNMENT",
"ALPHA_CHANNEL_PREVIEW_RENDERER",
"ALPHA_CHANNEL_TASKBAR_STASH");
mQsbAlphaChannels.dump(
prefix + "\t",
writer,
"mQsbAlphaChannels",
"ALPHA_CHANNEL_TASKBAR_ALIGNMENT",
"ALPHA_CHANNEL_PREVIEW_RENDERER",
"ALPHA_CHANNEL_TASKBAR_STASH"
);
}
}