Files
Lawnchair/src/com/android/launcher3/Hotseat.java
T
Sebastian Franco 2986e0b4d1 Removing the Worskapce as a dependency of CellLayout
This will make it easier to test CellLayout, also we should avoid
circular dependencies as much as poisble.

This also allows the CellLayout to be created in other containers
that are not workspace.

Bug: 318417510
Test: creating HotseatReorderUnitTest in follow up cl
Test: TaplReorderWidgets
Test: ReorderAlgorithmUnitTest
Flag: NA
Change-Id: Ic45029a244cb11f8d6775c50b90af9c56f01eaa3
2024-01-26 09:28:57 -08:00

298 lines
11 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 com.android.launcher3.util.HorizontalInsettableView;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.views.ActivityContext;
/**
* View class that represents the bottom row of the home screen.
*/
public class Hotseat extends CellLayout implements Insettable {
// 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 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);
}
/**
* 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;
}
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) {
float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
if (hasBubbles && Float.compare(adjustedBorderSpace, 0f) != 0) {
getShortcutsAndWidgets().setTranslationProvider(child -> {
int index = getShortcutsAndWidgets().indexOfChild(child);
float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
return dp.iconSizePx + index * borderSpaceDelta;
});
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();
float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
if (Float.compare(adjustedBorderSpace, 0f) == 0) {
return;
}
ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
AnimatorSet animatorSet = new AnimatorSet();
float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
// update the translation provider for future layout passes of hotseat icons.
if (isBubbleBarVisible) {
icons.setTranslationProvider(child -> {
int index = icons.indexOfChild(child);
return dp.iconSizePx + index * borderSpaceDelta;
});
} else {
icons.setTranslationProvider(null);
}
for (int i = 0; i < icons.getChildCount(); i++) {
View child = icons.getChildAt(i);
float tx = isBubbleBarVisible ? dp.iconSizePx + i * borderSpaceDelta : 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));
}
}
if (mQsb instanceof HorizontalInsettableView) {
HorizontalInsettableView horizontalInsettableQsb = (HorizontalInsettableView) mQsb;
ValueAnimator qsbAnimator = ValueAnimator.ofFloat(0f, 1f);
qsbAnimator.addUpdateListener(animation -> {
float fraction = qsbAnimator.getAnimatedFraction();
float insetFraction = isBubbleBarVisible
? (float) dp.iconSizePx * fraction / dp.hotseatQsbWidth
: (float) dp.iconSizePx * (1 - fraction) / dp.hotseatQsbWidth;
horizontalInsettableQsb.setHorizontalInsets(insetFraction);
});
animatorSet.play(qsbAnimator);
}
animatorSet.setDuration(BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS).start();
}
@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 just our ShortcutAndWidgetContainer.
*/
public void setIconsAlpha(float alpha) {
getShortcutsAndWidgets().setAlpha(alpha);
}
/**
* Sets the alpha value of just our QSB.
*/
public void setQsbAlpha(float alpha) {
mQsb.setAlpha(alpha);
}
public float getIconsAlpha() {
return getShortcutsAndWidgets().getAlpha();
}
/**
* Returns the QSB inside hotseat
*/
public View getQsb() {
return mQsb;
}
}