blob: 3b12b86bf773323dbb7722acb2c36494e3566354 [file] [log] [blame]
/*
* 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;
}
@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;
}
}