Collapse private space container and animate header.
- Just opposite of how it will expand. - RecyclerView.SmoothScroller is needed to scroll the container. - Need to separate the lock button because this way I can use animateLayout changes and it itself was its own drawable. Separated into icon and textView in a viewGroup. - Give the background the 10padding on the left and right so that when in animation, the icon can adjust the padding/margins there. - Using propertySetter to set animation - Animates the alpha of the settings alpha - updated test to account for the nested child views the test needs to inspect bug: 299294792 test: manual: Expand + Collapse Video: https://drive.google.com/file/d/1Og66eqmXv3THn0wO4_x6Tfp2AbwFWUwZ/view?usp=sharing Flag: ACONFIG com.android.launcher3.Flags.private_space_animation TEAMFOOD Change-Id: I96f1d172a481522d23b4cee996ddec65961fce78
This commit is contained in:
@@ -16,7 +16,12 @@
|
||||
|
||||
package com.android.launcher3.allapps;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
|
||||
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
|
||||
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
|
||||
import static com.android.launcher3.allapps.PrivateProfileManager.STATE_DISABLED;
|
||||
import static com.android.launcher3.allapps.PrivateProfileManager.STATE_ENABLED;
|
||||
import static com.android.launcher3.allapps.PrivateProfileManager.STATE_TRANSITION;
|
||||
@@ -24,21 +29,37 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP;
|
||||
|
||||
import android.view.View;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.LayoutTransition;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.UserProfileManager.UserProfileState;
|
||||
import com.android.launcher3.anim.AnimatedPropertySetter;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Controller which returns views to be added to Private Space Header based upon
|
||||
* {@link UserProfileState}
|
||||
*/
|
||||
public class PrivateSpaceHeaderViewController {
|
||||
private static final int ANIMATION_DURATION = 2000;
|
||||
private static final int EXPAND_SCROLL_DURATION = 2000;
|
||||
private static final int EXPAND_COLLAPSE_DURATION = 800;
|
||||
private static final int SETTINGS_OPACITY_DURATION = 160;
|
||||
private final ActivityAllAppsContainerView mAllApps;
|
||||
private final PrivateProfileManager mPrivateProfileManager;
|
||||
|
||||
@@ -50,10 +71,17 @@ public class PrivateSpaceHeaderViewController {
|
||||
|
||||
/** Add Private Space Header view elements based upon {@link UserProfileState} */
|
||||
public void addPrivateSpaceHeaderViewElements(RelativeLayout parent) {
|
||||
// Set the transition duration for the settings and lock button to animate.
|
||||
ViewGroup settingsAndLockGroup = parent.findViewById(R.id.settingsAndLockGroup);
|
||||
LayoutTransition settingsAndLockTransition = settingsAndLockGroup.getLayoutTransition();
|
||||
settingsAndLockTransition.enableTransitionType(LayoutTransition.CHANGING);
|
||||
settingsAndLockTransition.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
|
||||
//Add quietMode image and action for lock/unlock button
|
||||
ImageButton quietModeButton = parent.findViewById(R.id.ps_lock_unlock_button);
|
||||
assert quietModeButton != null;
|
||||
addQuietModeButton(quietModeButton);
|
||||
ViewGroup lockButton =
|
||||
parent.findViewById(R.id.ps_lock_unlock_button);
|
||||
assert lockButton != null;
|
||||
addLockButton(parent, lockButton);
|
||||
|
||||
//Trigger lock/unlock action from header.
|
||||
addHeaderOnClickListener(parent);
|
||||
@@ -69,74 +97,181 @@ public class PrivateSpaceHeaderViewController {
|
||||
addTransitionImage(transitionView);
|
||||
}
|
||||
|
||||
private void addQuietModeButton(ImageButton quietModeButton) {
|
||||
/**
|
||||
* Adds the quietModeButton and attach onClickListener for the header to animate different
|
||||
* states when clicked.
|
||||
*/
|
||||
private void addLockButton(ViewGroup psHeader, ViewGroup lockButton) {
|
||||
TextView lockText = lockButton.findViewById(R.id.lock_text);
|
||||
switch (mPrivateProfileManager.getCurrentState()) {
|
||||
case STATE_ENABLED -> {
|
||||
quietModeButton.setVisibility(View.VISIBLE);
|
||||
quietModeButton.setImageResource(R.drawable.bg_ps_lock_button);
|
||||
quietModeButton.setOnClickListener(view -> lockAction());
|
||||
lockText.setVisibility(VISIBLE);
|
||||
lockButton.setVisibility(VISIBLE);
|
||||
lockButton.setOnClickListener(view -> lockAction(psHeader));
|
||||
}
|
||||
case STATE_DISABLED -> {
|
||||
quietModeButton.setVisibility(View.VISIBLE);
|
||||
quietModeButton.setImageResource(R.drawable.bg_ps_unlock_button);
|
||||
quietModeButton.setOnClickListener(view -> unLockAction());
|
||||
lockText.setVisibility(GONE);
|
||||
lockButton.setVisibility(VISIBLE);
|
||||
lockButton.setOnClickListener(view -> unlockAction(psHeader));
|
||||
}
|
||||
default -> quietModeButton.setVisibility(View.GONE);
|
||||
default -> lockButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void addHeaderOnClickListener(RelativeLayout header) {
|
||||
if (mPrivateProfileManager.getCurrentState() == STATE_DISABLED) {
|
||||
header.setOnClickListener(view -> unLockAction());
|
||||
header.setOnClickListener(view -> unlockAction(header));
|
||||
} else {
|
||||
header.setOnClickListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void unLockAction() {
|
||||
private void unlockAction(ViewGroup psHeader) {
|
||||
mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP);
|
||||
mPrivateProfileManager.unlockPrivateProfile((this::onPrivateProfileUnlocked));
|
||||
mPrivateProfileManager.unlockPrivateProfile((() -> onPrivateProfileUnlocked(psHeader)));
|
||||
}
|
||||
|
||||
private void lockAction() {
|
||||
private void lockAction(ViewGroup psHeader) {
|
||||
mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_LOCK_TAP);
|
||||
mPrivateProfileManager.lockPrivateProfile();
|
||||
if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()) {
|
||||
updatePrivateStateAnimator(false, psHeader);
|
||||
} else {
|
||||
mPrivateProfileManager.lockPrivateProfile();
|
||||
}
|
||||
}
|
||||
|
||||
private void addPrivateSpaceSettingsButton(ImageButton settingsButton) {
|
||||
if (mPrivateProfileManager.getCurrentState() == STATE_ENABLED
|
||||
&& mPrivateProfileManager.isPrivateSpaceSettingsAvailable()) {
|
||||
settingsButton.setVisibility(View.VISIBLE);
|
||||
settingsButton.setVisibility(VISIBLE);
|
||||
settingsButton.setAlpha(1f);
|
||||
settingsButton.setOnClickListener(
|
||||
view -> {
|
||||
mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP);
|
||||
mPrivateProfileManager.openPrivateSpaceSettings();
|
||||
});
|
||||
} else {
|
||||
settingsButton.setVisibility(View.GONE);
|
||||
settingsButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTransitionImage(ImageView transitionImage) {
|
||||
if (mPrivateProfileManager.getCurrentState() == STATE_TRANSITION) {
|
||||
transitionImage.setVisibility(View.VISIBLE);
|
||||
transitionImage.setVisibility(VISIBLE);
|
||||
} else {
|
||||
transitionImage.setVisibility(View.GONE);
|
||||
transitionImage.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPrivateProfileUnlocked() {
|
||||
private void onPrivateProfileUnlocked(ViewGroup header) {
|
||||
// If we are on main adapter view, we apply the PS Container expansion animation and
|
||||
// then scroll down to load the entire container, making animation visible.
|
||||
ActivityAllAppsContainerView<?>.AdapterHolder mainAdapterHolder =
|
||||
(ActivityAllAppsContainerView<?>.AdapterHolder) mAllApps.mAH.get(MAIN);
|
||||
if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()
|
||||
&& mAllApps.getActiveRecyclerView() == mainAdapterHolder.mRecyclerView) {
|
||||
mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(ANIMATION_DURATION);
|
||||
// Animate the text and settings icon.
|
||||
updatePrivateStateAnimator(true, header);
|
||||
mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(EXPAND_SCROLL_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
/** Finds the private space header to scroll to and set the private space icons to GONE. */
|
||||
private void collapse() {
|
||||
AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
|
||||
for (int i = allAppsRecyclerView.getChildCount() - 1; i > 0; i--) {
|
||||
int adapterPosition = allAppsRecyclerView.getChildAdapterPosition(
|
||||
allAppsRecyclerView.getChildAt(i));
|
||||
List<BaseAllAppsAdapter.AdapterItem> allAppsAdapters = allAppsRecyclerView.getApps()
|
||||
.getAdapterItems();
|
||||
if (adapterPosition < 0 || adapterPosition >= allAppsAdapters.size()) {
|
||||
continue;
|
||||
}
|
||||
// Scroll to the private space header.
|
||||
if (allAppsAdapters.get(adapterPosition).viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
|
||||
// Note: SmoothScroller is meant to be used once.
|
||||
RecyclerView.SmoothScroller smoothScroller =
|
||||
new LinearSmoothScroller(mAllApps.getContext()) {
|
||||
@Override protected int getVerticalSnapPreference() {
|
||||
return LinearSmoothScroller.SNAP_TO_END;
|
||||
}
|
||||
};
|
||||
smoothScroller.setTargetPosition(adapterPosition);
|
||||
RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
|
||||
if (layoutManager != null) {
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Make the private space apps gone to "collapse".
|
||||
if (allAppsAdapters.get(adapterPosition).decorationInfo != null) {
|
||||
allAppsRecyclerView.getChildAt(i).setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrivateProfileManager getPrivateProfileManager() {
|
||||
return mPrivateProfileManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls up to the private space header and animates the collapsing of the text.
|
||||
*/
|
||||
private ValueAnimator animateCollapseAnimation(ViewGroup lockButton) {
|
||||
float from = 1;
|
||||
float to = 0;
|
||||
ValueAnimator collapseAnim = ValueAnimator.ofFloat(from, to);
|
||||
collapseAnim.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
collapseAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
// scroll up
|
||||
collapse();
|
||||
// Animate the collapsing of the text.
|
||||
lockButton.findViewById(R.id.lock_text).setVisibility(GONE);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
mPrivateProfileManager.lockPrivateProfile();
|
||||
}
|
||||
});
|
||||
return collapseAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using PropertySetter{@link PropertySetter}, we can update the view's attributes within an
|
||||
* animation. At the moment, collapsing, setting alpha changes, and animating the text is done
|
||||
* here.
|
||||
*/
|
||||
private void updatePrivateStateAnimator(boolean expand, ViewGroup psHeader) {
|
||||
PropertySetter setter = new AnimatedPropertySetter();
|
||||
ViewGroup lockButton = psHeader.findViewById(R.id.ps_lock_unlock_button);
|
||||
ImageButton settingsButton = psHeader.findViewById(R.id.ps_settings_button);
|
||||
updateSettingsGearAlpha(settingsButton, expand, setter);
|
||||
AnimatorSet animatorSet = setter.buildAnim();
|
||||
animatorSet.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
// Animate the collapsing of the text at the same time while updating lock button.
|
||||
lockButton.findViewById(R.id.lock_text).setVisibility(expand ? VISIBLE : GONE);
|
||||
}
|
||||
});
|
||||
// Play the collapsing together of the stateAnimator to avoid being unable to scroll to the
|
||||
// header. Otherwise the smooth scrolling will scroll higher when played with the state
|
||||
// animator.
|
||||
if (!expand) {
|
||||
animatorSet.playTogether(animateCollapseAnimation(lockButton));
|
||||
}
|
||||
animatorSet.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
/** Change the settings gear alpha when expanded or collapsed. */
|
||||
private void updateSettingsGearAlpha(ImageButton settingsButton, boolean expand,
|
||||
PropertySetter setter) {
|
||||
float toAlpha = expand ? 1 : 0;
|
||||
setter.setFloat(settingsButton, VIEW_ALPHA, toAlpha, Interpolators.LINEAR)
|
||||
.setDuration(SETTINGS_OPACITY_DURATION).setStartDelay(0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user