Convert ClearAllButton to Kotlin

Bug: 389209338
Flag: EXEMPT refactor
Test: m
Change-Id: Iaafe69ae14744e418d3c30d4d9fc6f417f36f230
This commit is contained in:
minch
2025-02-25 06:02:19 +00:00
parent 45c1192fdf
commit b8aacf2d93
2 changed files with 269 additions and 333 deletions
@@ -1,333 +0,0 @@
/*
* Copyright (C) 2018 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.quickstep.views;
import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.BorderAnimator;
import kotlin.Unit;
public class ClearAllButton extends Button {
public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
new FloatProperty<ClearAllButton>("visibilityAlpha") {
@Override
public Float get(ClearAllButton view) {
return view.mVisibilityAlpha;
}
@Override
public void setValue(ClearAllButton view, float v) {
view.setVisibilityAlpha(v);
}
};
public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
new FloatProperty<ClearAllButton>("dismissAlpha") {
@Override
public Float get(ClearAllButton view) {
return view.mDismissAlpha;
}
@Override
public void setValue(ClearAllButton view, float v) {
view.setDismissAlpha(v);
}
};
private float mScrollAlpha = 1;
private float mContentAlpha = 1;
private float mVisibilityAlpha = 1;
private float mDismissAlpha = 1;
private float mFullscreenProgress = 1;
private float mGridProgress = 1;
private boolean mIsRtl;
private float mNormalTranslationPrimary;
private float mFullscreenTranslationPrimary;
private float mGridTranslationPrimary;
private float mTaskAlignmentTranslationY;
private float mGridScrollOffset;
private float mScrollOffsetPrimary;
private int mSidePadding;
private int mOutlinePadding;
private boolean mBorderEnabled;
@Nullable
private final BorderAnimator mFocusBorderAnimator;
public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
if (Flags.enableFocusOutline()) {
TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
R.styleable.ClearAllButton);
Resources resources = getResources();
mOutlinePadding = resources.getDimensionPixelSize(
R.dimen.recents_clear_all_outline_padding);
mFocusBorderAnimator =
BorderAnimator.createSimpleBorderAnimator(
/* borderRadiusPx= */ resources.getDimensionPixelSize(
R.dimen.recents_clear_all_outline_radius),
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
/* boundsBuilder= */ this::updateBorderBounds,
/* targetView= */ this,
/* borderColor= */ styledAttrs.getColor(
R.styleable.ClearAllButton_focusBorderColor,
DEFAULT_BORDER_COLOR));
styledAttrs.recycle();
} else {
mFocusBorderAnimator = null;
}
}
private Unit updateBorderBounds(@NonNull Rect bounds) {
bounds.set(0, 0, getWidth(), getHeight());
// Make the value negative to form a padding between button and outline
bounds.inset(-mOutlinePadding, -mOutlinePadding);
return Unit.INSTANCE;
}
@Override
public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (mFocusBorderAnimator != null && mBorderEnabled) {
mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
}
}
/**
* Enable or disable showing border on focus change
*/
public void setBorderEnabled(boolean enabled) {
if (mBorderEnabled == enabled) {
return;
}
mBorderEnabled = enabled;
if (mFocusBorderAnimator != null) {
mFocusBorderAnimator.setBorderVisibility(/* visible= */
enabled && isFocused(), /* animated= */true);
}
}
@Override
public void draw(Canvas canvas) {
if (mFocusBorderAnimator != null) {
mFocusBorderAnimator.drawBorder(canvas);
}
super.draw(canvas);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
RecentsPagedOrientationHandler orientationHandler =
getRecentsView().getPagedOrientationHandler();
mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
}
private RecentsView getRecentsView() {
return (RecentsView) getParent();
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
public float getScrollAlpha() {
return mScrollAlpha;
}
public void setContentAlpha(float alpha) {
if (mContentAlpha != alpha) {
mContentAlpha = alpha;
updateAlpha();
}
}
public void setVisibilityAlpha(float alpha) {
if (mVisibilityAlpha != alpha) {
mVisibilityAlpha = alpha;
updateAlpha();
}
}
public void setDismissAlpha(float alpha) {
if (mDismissAlpha != alpha) {
mDismissAlpha = alpha;
updateAlpha();
}
}
public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
RecentsView recentsView = getRecentsView();
if (recentsView == null) {
return;
}
RecentsPagedOrientationHandler orientationHandler =
recentsView.getPagedOrientationHandler();
float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
if (orientationSize == 0) {
return;
}
int clearAllScroll = recentsView.getClearAllScroll();
int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll);
float shift = Math.min(adjustedScrollFromEdge, orientationSize);
mNormalTranslationPrimary = mIsRtl ? -shift : shift;
if (!gridEnabled) {
mNormalTranslationPrimary += mSidePadding;
}
applyPrimaryTranslation();
applySecondaryTranslation();
float clearAllSpacing =
recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing();
clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing;
mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0);
updateAlpha();
}
private void updateAlpha() {
final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
setAlpha(alpha);
setClickable(Math.min(alpha, 1) == 1);
}
public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) {
mFullscreenTranslationPrimary = fullscreenTranslationPrimary;
applyPrimaryTranslation();
}
/**
* Sets `mTaskAlignmentTranslationY` to the given `value`. In order to put the button at the
* middle in the secondary coordinate.
*/
public void setTaskAlignmentTranslationY(float value) {
mTaskAlignmentTranslationY = value;
applySecondaryTranslation();
}
public void setGridTranslationPrimary(float gridTranslationPrimary) {
mGridTranslationPrimary = gridTranslationPrimary;
applyPrimaryTranslation();
}
public void setGridScrollOffset(float gridScrollOffset) {
mGridScrollOffset = gridScrollOffset;
}
public void setScrollOffsetPrimary(float scrollOffsetPrimary) {
mScrollOffsetPrimary = scrollOffsetPrimary;
}
public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
float scrollAdjustment = 0;
if (fullscreenEnabled) {
scrollAdjustment += mFullscreenTranslationPrimary;
}
if (gridEnabled) {
scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
}
scrollAdjustment += mScrollOffsetPrimary;
return scrollAdjustment;
}
public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
return getScrollAdjustment(fullscreenEnabled, gridEnabled);
}
/**
* Adjust translation when this TaskView is about to be shown fullscreen.
*
* @param progress: 0 = no translation; 1 = translate according to TaskVIew translations.
*/
public void setFullscreenProgress(float progress) {
mFullscreenProgress = progress;
applyPrimaryTranslation();
}
/**
* Moves ClearAllButton between carousel and 2 row grid.
*
* @param gridProgress 0 = carousel; 1 = 2 row grid.
*/
public void setGridProgress(float gridProgress) {
mGridProgress = gridProgress;
applyPrimaryTranslation();
}
private void applyPrimaryTranslation() {
RecentsView recentsView = getRecentsView();
if (recentsView == null) {
return;
}
RecentsPagedOrientationHandler orientationHandler =
recentsView.getPagedOrientationHandler();
orientationHandler.getPrimaryViewTranslate().set(this,
orientationHandler.getPrimaryValue(0f, mTaskAlignmentTranslationY)
+ mNormalTranslationPrimary + getFullscreenTrans(
mFullscreenTranslationPrimary) + getGridTrans(mGridTranslationPrimary));
}
private void applySecondaryTranslation() {
RecentsView recentsView = getRecentsView();
if (recentsView == null) {
return;
}
RecentsPagedOrientationHandler orientationHandler =
recentsView.getPagedOrientationHandler();
orientationHandler.getSecondaryViewTranslate().set(this,
orientationHandler.getSecondaryValue(0f, mTaskAlignmentTranslationY));
}
private float getFullscreenTrans(float endTranslation) {
return mFullscreenProgress > 0 ? endTranslation : 0;
}
private float getGridTrans(float endTranslation) {
return mGridProgress > 0 ? endTranslation : 0;
}
}
@@ -0,0 +1,269 @@
/*
* Copyright (C) 2025 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.quickstep.views
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
import android.util.FloatProperty
import android.widget.Button
import com.android.launcher3.Flags.enableFocusOutline
import com.android.launcher3.R
import com.android.quickstep.util.BorderAnimator
import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
import kotlin.math.abs
import kotlin.math.min
class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
Button(context, attrs) {
var scrollAlpha = 1f
set(value) {
field = value
updateAlpha()
}
var contentAlpha = 1f
set(value) {
field = value
updateAlpha()
}
var visibilityAlpha = 1f
set(value) {
field = value
updateAlpha()
}
var dismissAlpha = 1f
set(value) {
field = value
updateAlpha()
}
var fullscreenProgress = 1f
set(value) {
if (field == value) {
return
}
field = value
applyPrimaryTranslation()
}
/**
* Moves ClearAllButton between carousel and 2 row grid.
*
* 0 = carousel; 1 = 2 row grid.
*/
var gridProgress = 1f
set(value) {
if (field == value) {
return
}
field = value
applyPrimaryTranslation()
}
private var normalTranslationPrimary = 0f
var fullscreenTranslationPrimary = 0f
set(value) {
if (field == value) {
return
}
field = value
applyPrimaryTranslation()
}
var gridTranslationPrimary = 0f
set(value) {
if (field == value) {
return
}
field = value
applyPrimaryTranslation()
}
/** Used to put the button at the middle in the secondary coordinate. */
var taskAlignmentTranslationY = 0f
set(value) {
if (field == value) {
return
}
field = value
applySecondaryTranslation()
}
var gridScrollOffset = 0f
var scrollOffsetPrimary = 0f
private var sidePadding = 0
var borderEnabled = false
set(value) {
if (field == value) {
return
}
field = value
focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
}
private val focusBorderAnimator: BorderAnimator? =
if (enableFocusOutline())
createSimpleBorderAnimator(
context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_radius),
context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
this::getBorderBounds,
this,
context
.obtainStyledAttributes(attrs, R.styleable.ClearAllButton)
.getColor(
R.styleable.ClearAllButton_focusBorderColor,
BorderAnimator.DEFAULT_BORDER_COLOR,
),
)
else null
private fun getBorderBounds(bounds: Rect) {
bounds.set(0, 0, width, height)
val outlinePadding =
context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_padding)
// Make the value negative to form a padding between button and outline
bounds.inset(-outlinePadding, -outlinePadding)
}
public override fun onFocusChanged(
gainFocus: Boolean,
direction: Int,
previouslyFocusedRect: Rect?,
) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
if (borderEnabled) {
focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
}
}
override fun draw(canvas: Canvas) {
focusBorderAnimator?.drawBorder(canvas)
super.draw(canvas)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
sidePadding =
recentsView?.let { it.pagedOrientationHandler?.getClearAllSidePadding(it, isLayoutRtl) }
?: 0
}
private val recentsView: RecentsView<*, *>?
get() = parent as? RecentsView<*, *>?
override fun hasOverlappingRendering() = false
fun onRecentsViewScroll(scroll: Int, gridEnabled: Boolean) {
val recentsView = recentsView ?: return
val orientationSize =
recentsView.pagedOrientationHandler.getPrimaryValue(width, height).toFloat()
if (orientationSize == 0f) {
return
}
val clearAllScroll = recentsView.clearAllScroll
val adjustedScrollFromEdge = abs((scroll - clearAllScroll)).toFloat()
val shift = min(adjustedScrollFromEdge, orientationSize)
normalTranslationPrimary = if (isLayoutRtl) -shift else shift
if (!gridEnabled) {
normalTranslationPrimary += sidePadding.toFloat()
}
applyPrimaryTranslation()
applySecondaryTranslation()
var clearAllSpacing = recentsView.pageSpacing + recentsView.clearAllExtraPageSpacing
clearAllSpacing = if (isLayoutRtl) -clearAllSpacing else clearAllSpacing
scrollAlpha =
((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing.toFloat()).coerceAtLeast(
0f
)
}
private fun updateAlpha() {
val alpha = scrollAlpha * contentAlpha * visibilityAlpha * dismissAlpha
this.alpha = alpha
isClickable = alpha >= 1f
}
fun getScrollAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean): Float {
var scrollAdjustment = 0f
if (fullscreenEnabled) {
scrollAdjustment += fullscreenTranslationPrimary
}
if (gridEnabled) {
scrollAdjustment += gridTranslationPrimary + gridScrollOffset
}
scrollAdjustment += scrollOffsetPrimary
return scrollAdjustment
}
fun getOffsetAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean) =
getScrollAdjustment(fullscreenEnabled, gridEnabled)
private fun applyPrimaryTranslation() {
val recentsView = recentsView ?: return
val orientationHandler = recentsView.pagedOrientationHandler
orientationHandler.primaryViewTranslate.set(
this,
(orientationHandler.getPrimaryValue(0f, taskAlignmentTranslationY) +
normalTranslationPrimary +
getFullscreenTrans(fullscreenTranslationPrimary) +
getGridTrans(gridTranslationPrimary)),
)
}
private fun applySecondaryTranslation() {
val recentsView = recentsView ?: return
val orientationHandler = recentsView.pagedOrientationHandler
orientationHandler.secondaryViewTranslate.set(
this,
orientationHandler.getSecondaryValue(0f, taskAlignmentTranslationY),
)
}
private fun getFullscreenTrans(endTranslation: Float) =
if (fullscreenProgress > 0) endTranslation else 0f
private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f
companion object {
@JvmField
val VISIBILITY_ALPHA: FloatProperty<ClearAllButton> =
object : FloatProperty<ClearAllButton>("visibilityAlpha") {
override fun setValue(clearAllButton: ClearAllButton, value: Float) {
clearAllButton.visibilityAlpha = value
}
override fun get(clearAllButton: ClearAllButton) = clearAllButton.visibilityAlpha
}
@JvmField
val DISMISS_ALPHA: FloatProperty<ClearAllButton> =
object : FloatProperty<ClearAllButton>("dismissAlpha") {
override fun setValue(clearAllButton: ClearAllButton, value: Float) {
clearAllButton.dismissAlpha = value
}
override fun get(clearAllButton: ClearAllButton) = clearAllButton.dismissAlpha
}
}
}