mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
Add floatingMaskView when animating to mimic bottom container.
- On expand, we add the floating mask view and translate it out at the end. - On collapse, we translate off the mask view in the beginning once the floating mask view is added so that we can translate it in before the actual collapsing part of the animation bug:339850589 Test manually: https://drive.google.com/file/d/1YNc3vq9Cb5BcbcPOHp8H3lhe6KmYBdLI/view?usp=sharing Flag:ACONFIG com.android.launcher3.private_space_floating_mask_view STAGING Change-Id: I7c303e6629d83408bd314886fe10113246e44dcb
This commit is contained in:
30
res/drawable/bg_ps_mask_left_corner.xml
Normal file
30
res/drawable/bg_ps_mask_left_corner.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2024 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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<vector
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28"
|
||||
android:width="@dimen/ps_floating_mask_corner_radius"
|
||||
android:height="@dimen/ps_floating_mask_corner_radius">
|
||||
<path
|
||||
android:pathData="M0 28H28C24.3228 28 20.6821 27.2759 17.2847 25.8687C13.8877 24.4614 10.8013 22.3989 8.20117 19.7988C5.60107 17.1987 3.53857 14.1123 2.13135 10.7153C0.724121 7.31787 0 3.67725 0 0V28Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="?attr/allAppsScrimColor" />
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
30
res/drawable/bg_ps_mask_right_corner.xml
Normal file
30
res/drawable/bg_ps_mask_right_corner.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2024 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.
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<vector
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28"
|
||||
android:width="@dimen/ps_floating_mask_corner_radius"
|
||||
android:height="@dimen/ps_floating_mask_corner_radius">
|
||||
<path
|
||||
android:pathData="M28 28V0C28 3.67725 27.2759 7.31787 25.8687 10.7153C24.4614 14.1123 22.3989 17.1987 19.7988 19.7988C17.1987 22.3989 14.1123 24.4614 10.7153 25.8687C7.31787 27.2759 3.67725 28 0 28H28Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="?attr/allAppsScrimColor" />
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
55
res/layout/private_space_mask_view.xml
Normal file
55
res/layout/private_space_mask_view.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2024 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.
|
||||
-->
|
||||
|
||||
<com.android.launcher3.allapps.FloatingMaskView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginLeft="@dimen/ps_floating_mask_end_padding"
|
||||
android:layout_marginRight="@dimen/ps_floating_mask_end_padding"
|
||||
android:importantForAccessibility="noHideDescendants"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/left_corner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:importantForAccessibility="no"
|
||||
android:background="@drawable/bg_ps_mask_left_corner"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/right_corner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:importantForAccessibility="no"
|
||||
android:background="@drawable/bg_ps_mask_right_corner"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bottom_box"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="@id/left_corner"
|
||||
app:layout_constraintEnd_toEndOf="@id/right_corner"
|
||||
app:layout_constraintTop_toBottomOf="@id/left_corner"
|
||||
android:importantForAccessibility="no"
|
||||
android:background="?attr/allAppsScrimColor"/>
|
||||
|
||||
</com.android.launcher3.allapps.FloatingMaskView>
|
||||
@@ -536,6 +536,8 @@
|
||||
<dimen name="ps_lock_icon_text_margin_start_expanded">8dp</dimen>
|
||||
<dimen name="ps_lock_icon_text_margin_end_expanded">6dp</dimen>
|
||||
<dimen name="ps_lock_button_background_padding">10dp</dimen>
|
||||
<dimen name="ps_floating_mask_corner_radius">28dp</dimen>
|
||||
<dimen name="ps_floating_mask_end_padding">16dp</dimen>
|
||||
|
||||
<!-- WindowManagerProxy -->
|
||||
<dimen name="max_width_and_height_of_small_display_cutout">136px</dimen>
|
||||
|
||||
65
src/com/android/launcher3/allapps/FloatingMaskView.java
Normal file
65
src/com/android/launcher3/allapps/FloatingMaskView.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.allapps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
public class FloatingMaskView extends ConstraintLayout {
|
||||
|
||||
private final ActivityContext mActivityContext;
|
||||
private ImageView mBottomBox;
|
||||
|
||||
public FloatingMaskView(Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
|
||||
public FloatingMaskView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public FloatingMaskView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mActivityContext = ActivityContext.lookupContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mBottomBox = findViewById(R.id.bottom_box);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
|
||||
AllAppsRecyclerView allAppsContainerView =
|
||||
mActivityContext.getAppsView().getActiveRecyclerView();
|
||||
if (lp != null) {
|
||||
lp.rightMargin = allAppsContainerView.getPaddingRight();
|
||||
lp.leftMargin = allAppsContainerView.getPaddingLeft();
|
||||
mBottomBox.setMinimumHeight(allAppsContainerView.getPaddingBottom());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
@@ -93,14 +94,17 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
private static final int TEXT_UNLOCK_OPACITY_DURATION = 300;
|
||||
private static final int TEXT_LOCK_OPACITY_DURATION = 50;
|
||||
private static final int APP_OPACITY_DURATION = 400;
|
||||
private static final int MASK_VIEW_DURATION = 200;
|
||||
private static final int APP_OPACITY_DELAY = 400;
|
||||
private static final int SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY = 400;
|
||||
private static final int SETTINGS_OPACITY_DELAY = 400;
|
||||
private static final int LOCK_TEXT_OPACITY_DELAY = 500;
|
||||
private static final int MASK_VIEW_DELAY = 400;
|
||||
private static final int NO_DELAY = 0;
|
||||
private final ActivityAllAppsContainerView<?> mAllApps;
|
||||
private final Predicate<UserHandle> mPrivateProfileMatcher;
|
||||
private final int mPsHeaderHeight;
|
||||
private final int mFloatingMaskViewCornerRadius;
|
||||
private final RecyclerView.OnScrollListener mOnIdleScrollListener =
|
||||
new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
@@ -120,6 +124,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
private Runnable mOnPSHeaderAdded;
|
||||
@Nullable
|
||||
private RelativeLayout mPSHeader;
|
||||
private ConstraintLayout mFloatingMaskView;
|
||||
private final String mLockedStateContentDesc;
|
||||
private final String mUnLockedStateContentDesc;
|
||||
|
||||
@@ -139,6 +144,8 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
.getString(R.string.ps_container_lock_button_content_description);
|
||||
mUnLockedStateContentDesc = mAllApps.getContext()
|
||||
.getString(R.string.ps_container_unlock_button_content_description);
|
||||
mFloatingMaskViewCornerRadius = mAllApps.getContext().getResources().getDimensionPixelSize(
|
||||
R.dimen.ps_floating_mask_corner_radius);
|
||||
}
|
||||
|
||||
/** Adds Private Space Header to the layout. */
|
||||
@@ -216,6 +223,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED);
|
||||
int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED;
|
||||
setCurrentState(updatedState);
|
||||
mFloatingMaskView = null;
|
||||
if (mPSHeader != null) {
|
||||
mPSHeader.setAlpha(1);
|
||||
}
|
||||
@@ -491,12 +499,15 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
|
||||
if (layoutManager != null) {
|
||||
startAnimationScroll(allAppsRecyclerView, layoutManager, smoothScroller);
|
||||
currentItem.decorationInfo = null;
|
||||
// Preserve decorator if floating mask view exists.
|
||||
if (mFloatingMaskView == null) {
|
||||
currentItem.decorationInfo = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Make the private space apps gone to "collapse".
|
||||
if (isPrivateSpaceItem(currentItem)) {
|
||||
if (mFloatingMaskView == null && isPrivateSpaceItem(currentItem)) {
|
||||
RecyclerView.ViewHolder viewHolder =
|
||||
allAppsRecyclerView.findViewHolderForAdapterPosition(i);
|
||||
if (viewHolder != null) {
|
||||
@@ -634,6 +645,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
setAnimationRunning(false);
|
||||
return;
|
||||
}
|
||||
attachFloatingMaskView(expand);
|
||||
ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
|
||||
ViewGroup lockButton = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
|
||||
TextView lockText = lockButton.findViewById(R.id.lock_text);
|
||||
@@ -657,6 +669,11 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
lockText.setVisibility(expand ? VISIBLE : GONE);
|
||||
setAnimationRunning(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
detachFloatingMaskView();
|
||||
}
|
||||
});
|
||||
animatorSet.addListener(forEndCallback(() -> {
|
||||
setAnimationRunning(false);
|
||||
@@ -671,13 +688,17 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
}
|
||||
}));
|
||||
if (expand) {
|
||||
animatorSet.playTogether(animateAlphaOfIcons(true));
|
||||
animatorSet.playTogether(animateAlphaOfIcons(true),
|
||||
translateFloatingMaskView(false));
|
||||
} else {
|
||||
if (isPrivateSpaceHidden()) {
|
||||
animatorSet.playSequentially(animateAlphaOfIcons(false),
|
||||
animateCollapseAnimation(), fadeOutHeaderAlpha());
|
||||
animatorSet.playSequentially(translateFloatingMaskView(false),
|
||||
animateAlphaOfIcons(false),
|
||||
animateCollapseAnimation(),
|
||||
fadeOutHeaderAlpha());
|
||||
} else {
|
||||
animatorSet.playSequentially(animateAlphaOfIcons(false),
|
||||
animatorSet.playSequentially(translateFloatingMaskView(true),
|
||||
animateAlphaOfIcons(false),
|
||||
animateCollapseAnimation());
|
||||
}
|
||||
}
|
||||
@@ -705,6 +726,27 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
return alphaAnim;
|
||||
}
|
||||
|
||||
/** Fades out the private space container. */
|
||||
private ValueAnimator translateFloatingMaskView(boolean animateIn) {
|
||||
if (!Flags.privateSpaceFloatingMaskView() || mFloatingMaskView == null) {
|
||||
return new ValueAnimator();
|
||||
}
|
||||
// Translate base on the height amount. Translates out on expand and in on collapse.
|
||||
float floatingMaskViewHeight = getFloatingMaskViewHeight();
|
||||
float from = animateIn ? floatingMaskViewHeight : 0;
|
||||
float to = animateIn ? 0 : floatingMaskViewHeight;
|
||||
ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
|
||||
alphaAnim.setDuration(MASK_VIEW_DURATION);
|
||||
alphaAnim.setStartDelay(MASK_VIEW_DELAY);
|
||||
alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
mFloatingMaskView.setTranslationY((float) valueAnimator.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
return alphaAnim;
|
||||
}
|
||||
|
||||
/** Animates the layout changes when the text of the button becomes visible/gone. */
|
||||
private void enableLayoutTransition(ViewGroup settingsAndLockGroup) {
|
||||
LayoutTransition settingsAndLockTransition = new LayoutTransition();
|
||||
@@ -771,6 +813,28 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
});
|
||||
}
|
||||
|
||||
private void attachFloatingMaskView(boolean expand) {
|
||||
if (!Flags.privateSpaceFloatingMaskView()) {
|
||||
return;
|
||||
}
|
||||
mFloatingMaskView = (FloatingMaskView) mAllApps.getLayoutInflater().inflate(
|
||||
R.layout.private_space_mask_view, mAllApps, false);
|
||||
mAllApps.addView(mFloatingMaskView);
|
||||
// Translate off the screen first if its collapsing so this header view isn't visible to
|
||||
// user when animation starts.
|
||||
if (!expand) {
|
||||
mFloatingMaskView.setTranslationY(getFloatingMaskViewHeight());
|
||||
}
|
||||
mFloatingMaskView.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void detachFloatingMaskView() {
|
||||
if (mFloatingMaskView != null) {
|
||||
mAllApps.removeView(mFloatingMaskView);
|
||||
}
|
||||
mFloatingMaskView = null;
|
||||
}
|
||||
|
||||
/** Starts the smooth scroll with the provided smoothScroller and add idle listener. */
|
||||
private void startAnimationScroll(AllAppsRecyclerView allAppsRecyclerView,
|
||||
RecyclerView.LayoutManager layoutManager, RecyclerView.SmoothScroller smoothScroller) {
|
||||
@@ -780,6 +844,10 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
allAppsRecyclerView.addOnScrollListener(mOnIdleScrollListener);
|
||||
}
|
||||
|
||||
private float getFloatingMaskViewHeight() {
|
||||
return mFloatingMaskViewCornerRadius + getMainRecyclerView().getPaddingBottom();
|
||||
}
|
||||
|
||||
AllAppsRecyclerView getMainRecyclerView() {
|
||||
return mAllApps.mAH.get(ActivityAllAppsContainerView.AdapterHolder.MAIN).mRecyclerView;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user