mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-19 10:48:19 +00:00
1. setClipChildren(false) for WidgetFullSheet and content view during animation 2. setClipToOutline(true) for WidgetsRecyclerView and provide ViewOutlineProvider to expand bottom by 5% of height 3. Override calculateExtraLayoutSpace() for ScrollableLayoutManager 4. Manually modify AbstractSlideInView#mContent's background drawable during scale animation bug: b/260956481 Test: manual Change-Id: Ic391639de887cf4a70bc4965dc0b1fd9bc12dd2c
568 lines
24 KiB
Java
568 lines
24 KiB
Java
/*
|
|
* Copyright (C) 2015 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 static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
|
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
|
|
import static com.android.launcher3.LauncherState.ALL_APPS;
|
|
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
|
|
import static com.android.launcher3.LauncherState.NORMAL;
|
|
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
|
|
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
|
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
|
|
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
|
|
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
|
|
import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV;
|
|
import static com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV;
|
|
import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.Animator.AnimatorListener;
|
|
import android.animation.ObjectAnimator;
|
|
import android.animation.ValueAnimator;
|
|
import android.util.FloatProperty;
|
|
import android.view.HapticFeedbackConstants;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.ViewParent;
|
|
import android.view.animation.Interpolator;
|
|
|
|
import androidx.annotation.FloatRange;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import com.android.launcher3.DeviceProfile;
|
|
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
|
import com.android.launcher3.Launcher;
|
|
import com.android.launcher3.LauncherState;
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.Utilities;
|
|
import com.android.launcher3.anim.AnimatedFloat;
|
|
import com.android.launcher3.anim.AnimatorListeners;
|
|
import com.android.launcher3.anim.Interpolators;
|
|
import com.android.launcher3.anim.PendingAnimation;
|
|
import com.android.launcher3.anim.PropertySetter;
|
|
import com.android.launcher3.config.FeatureFlags;
|
|
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
|
import com.android.launcher3.states.StateAnimationConfig;
|
|
import com.android.launcher3.touch.AllAppsSwipeController;
|
|
import com.android.launcher3.util.MultiPropertyFactory;
|
|
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
|
import com.android.launcher3.util.MultiValueAlpha;
|
|
import com.android.launcher3.util.ScrollableLayoutManager;
|
|
import com.android.launcher3.util.Themes;
|
|
import com.android.launcher3.util.VibratorWrapper;
|
|
import com.android.launcher3.views.ScrimView;
|
|
|
|
/**
|
|
* Handles AllApps view transition.
|
|
* 1) Slides all apps view using direct manipulation
|
|
* 2) When finger is released, animate to either top or bottom accordingly.
|
|
* <p/>
|
|
* Algorithm:
|
|
* If release velocity > THRES1, snap according to the direction of movement.
|
|
* If release velocity < THRES1, snap according to either top or bottom depending on whether it's
|
|
* closer to top or closer to the page indicator.
|
|
*/
|
|
public class AllAppsTransitionController
|
|
implements StateHandler<LauncherState>, OnDeviceProfileChangeListener {
|
|
// This constant should match the second derivative of the animator interpolator.
|
|
public static final float INTERP_COEFF = 1.7f;
|
|
public static final int REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS = 200;
|
|
|
|
private static final float NAV_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.1f;
|
|
private static final float SWIPE_DRAG_COMMIT_THRESHOLD =
|
|
1 - AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL;
|
|
|
|
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
|
|
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
|
|
|
|
@Override
|
|
public Float get(AllAppsTransitionController controller) {
|
|
return controller.mProgress;
|
|
}
|
|
|
|
@Override
|
|
public void setValue(AllAppsTransitionController controller, float progress) {
|
|
controller.setProgress(progress);
|
|
}
|
|
};
|
|
|
|
private static final float ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT = 0f;
|
|
|
|
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_TRANSLATION =
|
|
new FloatProperty<AllAppsTransitionController>("allAppsPullBackTranslation") {
|
|
|
|
@Override
|
|
public Float get(AllAppsTransitionController controller) {
|
|
if (controller.mIsTablet) {
|
|
return controller.mAppsView.getActiveRecyclerView().getTranslationY();
|
|
} else {
|
|
return controller.getAppsViewPullbackTranslationY().getValue();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setValue(AllAppsTransitionController controller, float translation) {
|
|
if (controller.mIsTablet) {
|
|
controller.mAppsView.getActiveRecyclerView().setTranslationY(translation);
|
|
controller.getAppsViewPullbackTranslationY().setValue(
|
|
ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT);
|
|
} else {
|
|
controller.getAppsViewPullbackTranslationY().setValue(translation);
|
|
controller.mAppsView.getActiveRecyclerView().setTranslationY(
|
|
ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT);
|
|
}
|
|
}
|
|
};
|
|
|
|
private static final float ALL_APPS_PULL_BACK_ALPHA_DEFAULT = 1f;
|
|
|
|
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_ALPHA =
|
|
new FloatProperty<AllAppsTransitionController>("allAppsPullBackAlpha") {
|
|
|
|
@Override
|
|
public Float get(AllAppsTransitionController controller) {
|
|
if (controller.mIsTablet) {
|
|
return controller.mAppsView.getActiveRecyclerView().getAlpha();
|
|
} else {
|
|
return controller.getAppsViewPullbackAlpha().getValue();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setValue(AllAppsTransitionController controller, float alpha) {
|
|
if (controller.mIsTablet) {
|
|
controller.mAppsView.getActiveRecyclerView().setAlpha(alpha);
|
|
controller.getAppsViewPullbackAlpha().setValue(
|
|
ALL_APPS_PULL_BACK_ALPHA_DEFAULT);
|
|
} else {
|
|
controller.getAppsViewPullbackAlpha().setValue(alpha);
|
|
controller.mAppsView.getActiveRecyclerView().setAlpha(
|
|
ALL_APPS_PULL_BACK_ALPHA_DEFAULT);
|
|
}
|
|
}
|
|
};
|
|
|
|
private static final int INDEX_APPS_VIEW_PROGRESS = 0;
|
|
private static final int INDEX_APPS_VIEW_PULLBACK = 1;
|
|
private static final int APPS_VIEW_INDEX_COUNT = 2;
|
|
|
|
private ActivityAllAppsContainerView<Launcher> mAppsView;
|
|
|
|
private final Launcher mLauncher;
|
|
private final AnimatedFloat mAllAppScale = new AnimatedFloat(this::onScaleProgressChanged);
|
|
private final int mNavScrimFlag;
|
|
|
|
private boolean mIsVerticalLayout;
|
|
|
|
// Whether this class should take care of closing the keyboard.
|
|
private boolean mShouldControlKeyboard;
|
|
|
|
// Animation in this class is controlled by a single variable {@link mProgress}.
|
|
// Visually, it represents top y coordinate of the all apps container if multiplied with
|
|
// {@link mShiftRange}.
|
|
|
|
// When {@link mProgress} is 0, all apps container is pulled up.
|
|
// When {@link mProgress} is 1, all apps container is pulled down.
|
|
private float mShiftRange; // changes depending on the orientation
|
|
private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent
|
|
|
|
private ScrimView mScrimView;
|
|
|
|
private MultiValueAlpha mAppsViewAlpha;
|
|
private MultiPropertyFactory<View> mAppsViewTranslationY;
|
|
|
|
private boolean mIsTablet;
|
|
|
|
private boolean mHasScaleEffect;
|
|
private final VibratorWrapper mVibratorWrapper;
|
|
|
|
public AllAppsTransitionController(Launcher l) {
|
|
mLauncher = l;
|
|
DeviceProfile dp = mLauncher.getDeviceProfile();
|
|
mProgress = 1f;
|
|
mIsVerticalLayout = dp.isVerticalBarLayout();
|
|
mIsTablet = dp.isTablet;
|
|
mNavScrimFlag = Themes.getAttrBoolean(l, R.attr.isMainColorDark)
|
|
? FLAG_DARK_NAV : FLAG_LIGHT_NAV;
|
|
|
|
setShiftRange(dp.allAppsShiftRange);
|
|
mLauncher.addOnDeviceProfileChangeListener(this);
|
|
mVibratorWrapper = VibratorWrapper.INSTANCE.get(mLauncher.getApplicationContext());
|
|
}
|
|
|
|
public float getShiftRange() {
|
|
return mShiftRange;
|
|
}
|
|
|
|
@Override
|
|
public void onDeviceProfileChanged(DeviceProfile dp) {
|
|
mIsVerticalLayout = dp.isVerticalBarLayout();
|
|
setShiftRange(dp.allAppsShiftRange);
|
|
|
|
if (mIsVerticalLayout) {
|
|
mLauncher.getHotseat().setTranslationY(0);
|
|
mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
|
|
}
|
|
|
|
mIsTablet = dp.isTablet;
|
|
}
|
|
|
|
/**
|
|
* Note this method should not be called outside this class. This is public because it is used
|
|
* in xml-based animations which also handle updating the appropriate UI.
|
|
*
|
|
* @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
|
|
* @see #setState(LauncherState)
|
|
* @see #setStateWithAnimation(LauncherState, StateAnimationConfig, PendingAnimation)
|
|
*/
|
|
public void setProgress(float progress) {
|
|
mProgress = progress;
|
|
getAppsViewProgressTranslationY().setValue(mProgress * mShiftRange);
|
|
mLauncher.onAllAppsTransition(1 - progress);
|
|
|
|
boolean hasScrim = progress < NAV_BAR_COLOR_FORCE_UPDATE_THRESHOLD
|
|
&& mLauncher.getAppsView().getNavBarScrimHeight() > 0;
|
|
mLauncher.getSystemUiController().updateUiState(
|
|
UI_STATE_ALL_APPS, hasScrim ? mNavScrimFlag : 0);
|
|
}
|
|
|
|
public float getProgress() {
|
|
return mProgress;
|
|
}
|
|
|
|
private MultiProperty getAppsViewProgressTranslationY() {
|
|
return mAppsViewTranslationY.get(INDEX_APPS_VIEW_PROGRESS);
|
|
}
|
|
|
|
private MultiProperty getAppsViewPullbackTranslationY() {
|
|
return mAppsViewTranslationY.get(INDEX_APPS_VIEW_PULLBACK);
|
|
}
|
|
|
|
private MultiProperty getAppsViewProgressAlpha() {
|
|
return mAppsViewAlpha.get(INDEX_APPS_VIEW_PROGRESS);
|
|
}
|
|
|
|
private MultiProperty getAppsViewPullbackAlpha() {
|
|
return mAppsViewAlpha.get(INDEX_APPS_VIEW_PULLBACK);
|
|
}
|
|
|
|
/**
|
|
* Sets the vertical transition progress to {@param state} and updates all the dependent UI
|
|
* accordingly.
|
|
*/
|
|
@Override
|
|
public void setState(LauncherState state) {
|
|
setProgress(state.getVerticalProgress(mLauncher));
|
|
setAlphas(state, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
|
|
onProgressAnimationEnd();
|
|
}
|
|
|
|
@Override
|
|
public void onBackProgressed(
|
|
LauncherState toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
|
|
if (!mLauncher.isInState(ALL_APPS) || !NORMAL.equals(toState)) {
|
|
return;
|
|
}
|
|
|
|
float deceleratedProgress =
|
|
Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(backProgress);
|
|
float scaleProgress = ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE
|
|
+ (1 - ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE)
|
|
* (1 - deceleratedProgress);
|
|
|
|
mAllAppScale.updateValue(scaleProgress);
|
|
}
|
|
|
|
private void onScaleProgressChanged() {
|
|
final float scaleProgress = mAllAppScale.value;
|
|
SCALE_PROPERTY.set(mLauncher.getAppsView(), scaleProgress);
|
|
mLauncher.getScrimView().setScrimHeaderScale(scaleProgress);
|
|
|
|
AllAppsRecyclerView rv = mLauncher.getAppsView().getActiveRecyclerView();
|
|
if (rv != null && rv.getScrollbar() != null) {
|
|
rv.getScrollbar().setVisibility(scaleProgress < 1f ? View.INVISIBLE : View.VISIBLE);
|
|
}
|
|
|
|
// Disable view clipping from all apps' RecyclerView up to all apps view during scale
|
|
// animation, and vice versa. The goal is to display extra roll(s) app icons (rendered in
|
|
// {@link AppsGridLayoutManager#calculateExtraLayoutSpace}) during scale animation.
|
|
boolean hasScaleEffect = scaleProgress < 1f;
|
|
if (hasScaleEffect != mHasScaleEffect) {
|
|
mHasScaleEffect = hasScaleEffect;
|
|
if (mHasScaleEffect) {
|
|
setClipChildrenOnViewTree(rv, mLauncher.getAppsView(), false);
|
|
} else {
|
|
restoreClipChildrenOnViewTree(rv, mLauncher.getAppsView());
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Animate all apps view to 1f scale. */
|
|
public void animateAllAppsToNoScale() {
|
|
mAllAppScale.animateToValue(1f)
|
|
.setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
|
|
.start();
|
|
}
|
|
|
|
/**
|
|
* Creates an animation which updates the vertical transition progress and updates all the
|
|
* dependent UI using various animation events
|
|
*
|
|
* This method also dictates where along the progress the haptics should be played. As the user
|
|
* scrolls up from workspace or down from AllApps, a drag haptic is being played until the
|
|
* commit point where it plays a commit haptic. Where we play the haptics differs when going
|
|
* from workspace -> allApps and vice versa.
|
|
*/
|
|
@Override
|
|
public void setStateWithAnimation(LauncherState toState,
|
|
StateAnimationConfig config, PendingAnimation builder) {
|
|
if (mLauncher.isInState(ALL_APPS) && !ALL_APPS.equals(toState)) {
|
|
// For atomic animations, we close the keyboard immediately.
|
|
if (!config.userControlled && mShouldControlKeyboard) {
|
|
mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
|
|
}
|
|
|
|
builder.addEndListener(success -> {
|
|
// Reset pull back progress and alpha after switching states.
|
|
ALL_APPS_PULL_BACK_TRANSLATION.set(this, ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT);
|
|
ALL_APPS_PULL_BACK_ALPHA.set(this, ALL_APPS_PULL_BACK_ALPHA_DEFAULT);
|
|
|
|
// We only want to close the keyboard if the animation has completed successfully.
|
|
// The reason is that with keyboard sync, if the user swipes down from All Apps with
|
|
// the keyboard open and then changes their mind and swipes back up, we want the
|
|
// keyboard to remain open. However an onCancel signal is sent to the listeners
|
|
// (success = false), so we need to check for that.
|
|
if (config.userControlled && success && mShouldControlKeyboard) {
|
|
mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
|
|
}
|
|
|
|
mAllAppScale.updateValue(1f);
|
|
});
|
|
}
|
|
|
|
if(FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() && config.userControlled
|
|
&& Utilities.ATLEAST_S) {
|
|
if (toState == ALL_APPS) {
|
|
builder.addOnFrameListener(
|
|
new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
|
|
SWIPE_DRAG_COMMIT_THRESHOLD, 1));
|
|
} else {
|
|
builder.addOnFrameListener(
|
|
new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
|
|
0, SWIPE_DRAG_COMMIT_THRESHOLD));
|
|
}
|
|
builder.addEndListener(mVibratorWrapper::cancelVibrate);
|
|
}
|
|
|
|
float targetProgress = toState.getVerticalProgress(mLauncher);
|
|
if (Float.compare(mProgress, targetProgress) == 0) {
|
|
setAlphas(toState, config, builder);
|
|
// Fail fast
|
|
return;
|
|
}
|
|
|
|
// need to decide depending on the release velocity
|
|
Interpolator verticalProgressInterpolator = config.getInterpolator(ANIM_VERTICAL_PROGRESS,
|
|
config.userControlled ? LINEAR : DEACCEL_1_7);
|
|
Animator anim = createSpringAnimation(mProgress, targetProgress);
|
|
anim.setInterpolator(verticalProgressInterpolator);
|
|
anim.addListener(getProgressAnimatorListener());
|
|
builder.add(anim);
|
|
|
|
setAlphas(toState, config, builder);
|
|
|
|
if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) && !(Utilities.ATLEAST_S)) {
|
|
mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
|
|
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
|
}
|
|
}
|
|
|
|
public Animator createSpringAnimation(float... progressValues) {
|
|
return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues);
|
|
}
|
|
|
|
/**
|
|
* Updates the property for the provided state
|
|
*/
|
|
public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) {
|
|
int visibleElements = state.getVisibleElements(mLauncher);
|
|
boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
|
|
|
|
Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
|
|
setter.setFloat(getAppsViewProgressAlpha(), MultiPropertyFactory.MULTI_PROPERTY_VALUE,
|
|
hasAllAppsContent ? 1 : 0, allAppsFade);
|
|
|
|
boolean shouldProtectHeader =
|
|
ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS;
|
|
mScrimView.setDrawingController(shouldProtectHeader ? mAppsView : null);
|
|
}
|
|
|
|
public AnimatorListener getProgressAnimatorListener() {
|
|
return AnimatorListeners.forSuccessCallback(this::onProgressAnimationEnd);
|
|
}
|
|
|
|
/**
|
|
* see Launcher#setupViews
|
|
*/
|
|
public void setupViews(ScrimView scrimView, ActivityAllAppsContainerView<Launcher> appsView) {
|
|
mScrimView = scrimView;
|
|
mAppsView = appsView;
|
|
mAppsView.setScrimView(scrimView);
|
|
|
|
mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT);
|
|
mAppsViewAlpha.setUpdateVisibility(true);
|
|
mAppsViewTranslationY = new MultiPropertyFactory<>(
|
|
mAppsView, VIEW_TRANSLATE_Y, APPS_VIEW_INDEX_COUNT, Float::sum);
|
|
|
|
mShouldControlKeyboard = !mLauncher.getSearchConfig().isKeyboardSyncEnabled();
|
|
}
|
|
|
|
/**
|
|
* Recursively call {@link ViewGroup#setClipChildren(boolean)} from {@link View} to ts parent
|
|
* (direct or indirect) inclusive. This method will also save the old clipChildren value on each
|
|
* view with {@link View#setTag(int, Object)}, which can be restored in
|
|
* {@link #restoreClipChildrenOnViewTree(View, ViewParent)}.
|
|
*
|
|
* Note that if parent is null or not a parent of the view, this method will be applied all the
|
|
* way to root view.
|
|
*
|
|
* @param v child view
|
|
* @param parent direct or indirect parent of child view
|
|
* @param clipChildren whether we should clip children
|
|
*/
|
|
private static void setClipChildrenOnViewTree(
|
|
@Nullable View v,
|
|
@Nullable ViewParent parent,
|
|
boolean clipChildren) {
|
|
if (v == null) {
|
|
return;
|
|
}
|
|
|
|
if (v instanceof ViewGroup) {
|
|
ViewGroup viewGroup = (ViewGroup) v;
|
|
boolean oldClipChildren = viewGroup.getClipChildren();
|
|
if (oldClipChildren != clipChildren) {
|
|
v.setTag(R.id.saved_clip_children_tag_id, oldClipChildren);
|
|
viewGroup.setClipChildren(clipChildren);
|
|
}
|
|
}
|
|
|
|
if (v == parent) {
|
|
return;
|
|
}
|
|
|
|
if (v.getParent() instanceof View) {
|
|
setClipChildrenOnViewTree((View) v.getParent(), parent, clipChildren);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively call {@link ViewGroup#setClipChildren(boolean)} to restore clip children value
|
|
* set in {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} on view to its parent
|
|
* (direct or indirect) inclusive.
|
|
*
|
|
* Note that if parent is null or not a parent of the view, this method will be applied all the
|
|
* way to root view.
|
|
*
|
|
* @param v child view
|
|
* @param parent direct or indirect parent of child view
|
|
*/
|
|
private static void restoreClipChildrenOnViewTree(
|
|
@Nullable View v, @Nullable ViewParent parent) {
|
|
if (v == null) {
|
|
return;
|
|
}
|
|
if (v instanceof ViewGroup) {
|
|
ViewGroup viewGroup = (ViewGroup) v;
|
|
Object viewTag = viewGroup.getTag(R.id.saved_clip_children_tag_id);
|
|
if (viewTag instanceof Boolean) {
|
|
viewGroup.setClipChildren((boolean) viewTag);
|
|
viewGroup.setTag(R.id.saved_clip_children_tag_id, null);
|
|
}
|
|
}
|
|
|
|
if (v == parent) {
|
|
return;
|
|
}
|
|
|
|
if (v.getParent() instanceof View) {
|
|
restoreClipChildrenOnViewTree((View) v.getParent(), parent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the total scroll range but does not update the UI.
|
|
*/
|
|
public void setShiftRange(float shiftRange) {
|
|
mShiftRange = shiftRange;
|
|
}
|
|
|
|
/**
|
|
* Set the final view states based on the progress.
|
|
* TODO: This logic should go in {@link LauncherState}
|
|
*/
|
|
private void onProgressAnimationEnd() {
|
|
if (Float.compare(mProgress, 1f) == 0) {
|
|
mAppsView.reset(false /* animate */);
|
|
if (mShouldControlKeyboard) {
|
|
mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This VibrationAnimatorUpdateListener class takes in four parameters, a controller, start
|
|
* threshold, end threshold, and a Vibrator wrapper. We use the progress given by the controller
|
|
* as it gives an accurate progress that dictates where the vibrator should vibrate.
|
|
* Note: once the user begins a gesture and does the commit haptic, there should not be anymore
|
|
* haptics played for that gesture.
|
|
*/
|
|
private static class VibrationAnimatorUpdateListener implements
|
|
ValueAnimator.AnimatorUpdateListener {
|
|
private final VibratorWrapper mVibratorWrapper;
|
|
private final AllAppsTransitionController mController;
|
|
private final float mStartThreshold;
|
|
private final float mEndThreshold;
|
|
private boolean mHasCommitted;
|
|
|
|
VibrationAnimatorUpdateListener(AllAppsTransitionController controller,
|
|
VibratorWrapper vibratorWrapper, float startThreshold,
|
|
float endThreshold) {
|
|
mController = controller;
|
|
mVibratorWrapper = vibratorWrapper;
|
|
mStartThreshold = startThreshold;
|
|
mEndThreshold = endThreshold;
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationUpdate(ValueAnimator animation) {
|
|
if (mHasCommitted) {
|
|
return;
|
|
}
|
|
float currentProgress =
|
|
AllAppsTransitionController.ALL_APPS_PROGRESS.get(mController);
|
|
if (currentProgress > mStartThreshold && currentProgress < mEndThreshold) {
|
|
mVibratorWrapper.vibrateForDragTexture();
|
|
} else if (!(currentProgress == 0 || currentProgress == 1)) {
|
|
// This check guards against committing at the location of the start of the gesture
|
|
mVibratorWrapper.vibrateForDragCommit();
|
|
mHasCommitted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|