mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-04 09:56:49 +00:00
Arrows to scroll recent tasks in KQS
Bug: 397878339
Bug: 397975686
Flag: com.android.launcher3.taskbar_overflow
Test: Enter taskbar overflow, and open the overflow view.
Verify that arrow buttons are present; tapping them scrolls in the
correct direction; when fully scrolled in a direction, the
associated arrow button is disabled; verify arrow buttons have hover
effects.
Open Alt+Tab view, verify no buttons are present if the view cannot
be scrolled (and present if the view can be scrolled).
Change-Id: Id1c37a2967a629aeb4b3cc2dc5ff7efb21c90cbe
This commit is contained in:
@@ -30,10 +30,12 @@ import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
import android.window.WindowOnBackInvokedDispatcher;
|
||||
@@ -45,6 +47,7 @@ import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.internal.jank.Cuj;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
@@ -102,8 +105,12 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
private HorizontalScrollView mScrollView;
|
||||
private ConstraintLayout mContent;
|
||||
|
||||
private int mTaskViewWidth;
|
||||
private int mTaskViewHeight;
|
||||
private boolean mSupportsScrollArrows = false;
|
||||
private ImageButton mStartScrollArrow;
|
||||
private ImageButton mEndScrollArrow;
|
||||
|
||||
private int mTaskViewBorderWidth;
|
||||
private int mTaskViewRadius;
|
||||
private int mSpacing;
|
||||
private int mSmallSpacing;
|
||||
private int mOutlineRadius;
|
||||
@@ -112,11 +119,13 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
private int mOverviewTaskIndex = -1;
|
||||
private int mDesktopTaskIndex = -1;
|
||||
|
||||
@Nullable private AnimatorSet mOpenAnimation;
|
||||
@Nullable
|
||||
private AnimatorSet mOpenAnimation;
|
||||
|
||||
private boolean mIsBackCallbackRegistered = false;
|
||||
|
||||
@Nullable private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
|
||||
@Nullable
|
||||
private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
|
||||
|
||||
public KeyboardQuickSwitchView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
@@ -152,18 +161,35 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
mNoRecentItemsPane = findViewById(R.id.no_recent_items_pane);
|
||||
mScrollView = findViewById(R.id.scroll_view);
|
||||
mContent = findViewById(R.id.content);
|
||||
mStartScrollArrow = findViewById(R.id.scroll_button_start);
|
||||
mEndScrollArrow = findViewById(R.id.scroll_button_end);
|
||||
|
||||
setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||||
|
||||
Resources resources = getResources();
|
||||
mTaskViewWidth = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_taskview_width);
|
||||
mTaskViewHeight = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_taskview_height);
|
||||
mSpacing = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_spacing);
|
||||
mSmallSpacing = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_view_small_spacing);
|
||||
mOutlineRadius = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_radius);
|
||||
mTaskViewBorderWidth = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_border_width);
|
||||
mTaskViewRadius = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_task_view_radius);
|
||||
|
||||
mIsRtl = Utilities.isRtl(resources);
|
||||
|
||||
if (Flags.taskbarOverflow()) {
|
||||
initializeScrollArrows();
|
||||
|
||||
if (mIsRtl) {
|
||||
mStartScrollArrow.setContentDescription(
|
||||
resources.getString(R.string.quick_switch_scroll_arrow_right));
|
||||
mEndScrollArrow.setContentDescription(
|
||||
resources.getString(R.string.quick_switch_scroll_arrow_left));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TypefaceUtils.setTypeface(
|
||||
mNoRecentItemsPane.findViewById(R.id.no_recent_items_text),
|
||||
TypefaceUtils.FONT_FAMILY_LABEL_LARGE_BASELINE);
|
||||
@@ -331,6 +357,78 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeScrollArrows() {
|
||||
mSupportsScrollArrows = true;
|
||||
|
||||
mStartScrollArrow.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mIsRtl) {
|
||||
runScrollCommand(false, () -> {
|
||||
mScrollView.smoothScrollBy(mScrollView.getWidth(), 0);
|
||||
});
|
||||
} else {
|
||||
runScrollCommand(false, () -> {
|
||||
mScrollView.smoothScrollBy(-mScrollView.getWidth(), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mEndScrollArrow.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mIsRtl) {
|
||||
runScrollCommand(false, () -> {
|
||||
mScrollView.smoothScrollBy(-mScrollView.getWidth(), 0);
|
||||
});
|
||||
} else {
|
||||
runScrollCommand(false, () -> {
|
||||
mScrollView.smoothScrollBy(mScrollView.getWidth(), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add listeners to disable arrow buttons when the scroll view cannot be further scrolled in
|
||||
// the associated direction.
|
||||
mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() {
|
||||
@Override
|
||||
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX,
|
||||
int oldScrollY) {
|
||||
updateArrowButtonsEnabledState();
|
||||
}
|
||||
});
|
||||
|
||||
// Update scroll view outline to clip its contents with rounded corners.
|
||||
mScrollView.setClipToOutline(true);
|
||||
mScrollView.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
int spacingWithoutBorder = mSpacing - mTaskViewBorderWidth;
|
||||
outline.setRoundRect(spacingWithoutBorder,
|
||||
spacingWithoutBorder, view.getWidth() - spacingWithoutBorder,
|
||||
view.getHeight() - spacingWithoutBorder,
|
||||
mTaskViewRadius);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateArrowButtonsEnabledState() {
|
||||
if (!mDisplayingRecentTasks) {
|
||||
return;
|
||||
}
|
||||
|
||||
int scrollX = mScrollView.getScrollX();
|
||||
if (mIsRtl) {
|
||||
mEndScrollArrow.setEnabled(scrollX > 0);
|
||||
mStartScrollArrow.setEnabled(scrollX < mContent.getWidth() - mScrollView.getWidth());
|
||||
} else {
|
||||
mStartScrollArrow.setEnabled(scrollX > 0);
|
||||
mEndScrollArrow.setEnabled(scrollX < mContent.getWidth() - mScrollView.getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
int getOverviewTaskIndex() {
|
||||
return mOverviewTaskIndex;
|
||||
}
|
||||
@@ -346,6 +444,21 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
mViewCallbacks = null;
|
||||
}
|
||||
|
||||
private void animateDisplayedContentForClose(View view, AnimatorSet animator) {
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
view,
|
||||
TRANSLATION_Y,
|
||||
0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
|
||||
animator.play(translationYAnimation);
|
||||
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(view, ALPHA, 1f, 0f);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
animator.play(contentAlphaAnimation);
|
||||
|
||||
}
|
||||
|
||||
protected Animator getCloseAnimation() {
|
||||
AnimatorSet closeAnimation = new AnimatorSet();
|
||||
|
||||
@@ -360,17 +473,11 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
closeAnimation.play(alphaAnimation);
|
||||
|
||||
View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
displayedContent,
|
||||
TRANSLATION_Y,
|
||||
0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
|
||||
closeAnimation.play(translationYAnimation);
|
||||
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 1f, 0f);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
closeAnimation.play(contentAlphaAnimation);
|
||||
animateDisplayedContentForClose(displayedContent, closeAnimation);
|
||||
if (mSupportsScrollArrows) {
|
||||
animateDisplayedContentForClose(mStartScrollArrow, closeAnimation);
|
||||
animateDisplayedContentForClose(mEndScrollArrow, closeAnimation);
|
||||
}
|
||||
|
||||
closeAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
@@ -385,6 +492,31 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
return closeAnimation;
|
||||
}
|
||||
|
||||
private void animateDisplayedContentForOpen(View view, AnimatorSet animator) {
|
||||
Animator translationXAnimation = ObjectAnimator.ofFloat(
|
||||
view,
|
||||
TRANSLATION_X,
|
||||
-Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
|
||||
translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
|
||||
translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
|
||||
animator.play(translationXAnimation);
|
||||
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
view,
|
||||
TRANSLATION_Y,
|
||||
-Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
|
||||
animator.play(translationYAnimation);
|
||||
|
||||
view.setAlpha(0.0f);
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(view, ALPHA, 0f,
|
||||
1f);
|
||||
contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
animator.play(contentAlphaAnimation);
|
||||
}
|
||||
|
||||
protected void animateOpen(int currentFocusIndexOverride) {
|
||||
if (mOpenAnimation != null) {
|
||||
// Restart animation since currentFocusIndexOverride can change the initial scroll.
|
||||
@@ -407,26 +539,12 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
mOpenAnimation.play(alphaAnimation);
|
||||
|
||||
View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
|
||||
Animator translationXAnimation = ObjectAnimator.ofFloat(
|
||||
displayedContent,
|
||||
TRANSLATION_X,
|
||||
-Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
|
||||
translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
|
||||
translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
|
||||
mOpenAnimation.play(translationXAnimation);
|
||||
animateDisplayedContentForOpen(displayedContent, mOpenAnimation);
|
||||
if (mSupportsScrollArrows) {
|
||||
animateDisplayedContentForOpen(mStartScrollArrow, mOpenAnimation);
|
||||
animateDisplayedContentForOpen(mEndScrollArrow, mOpenAnimation);
|
||||
}
|
||||
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
displayedContent,
|
||||
TRANSLATION_Y,
|
||||
-Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
|
||||
mOpenAnimation.play(translationYAnimation);
|
||||
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 0f, 1f);
|
||||
contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
mOpenAnimation.play(contentAlphaAnimation);
|
||||
|
||||
ViewOutlineProvider outlineProvider = getOutlineProvider();
|
||||
mOpenAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@@ -461,6 +579,27 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
OPEN_OUTLINE_INTERPOLATOR));
|
||||
}
|
||||
});
|
||||
|
||||
if (mSupportsScrollArrows) {
|
||||
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
if (mScrollView.getWidth() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mContent.getWidth() > mScrollView.getWidth()) {
|
||||
mStartScrollArrow.setVisibility(VISIBLE);
|
||||
mEndScrollArrow.setVisibility(VISIBLE);
|
||||
updateArrowButtonsEnabledState();
|
||||
}
|
||||
mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(
|
||||
this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
animateFocusMove(-1, Math.min(
|
||||
getTaskCount() - 1,
|
||||
currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride));
|
||||
|
||||
Reference in New Issue
Block a user