-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Predictive Back][Search] Update SearchView to support predictive bac…
…k when set up with SearchBar PiperOrigin-RevId: 520613990
- Loading branch information
Showing
6 changed files
with
446 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
225 changes: 225 additions & 0 deletions
225
lib/java/com/google/android/material/motion/MaterialMainContainerBackHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
/* | ||
* Copyright 2023 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.google.android.material.motion; | ||
|
||
import com.google.android.material.R; | ||
|
||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; | ||
import static com.google.android.material.animation.AnimationUtils.lerp; | ||
import static java.lang.Math.max; | ||
import static java.lang.Math.min; | ||
|
||
import android.animation.Animator; | ||
import android.animation.AnimatorListenerAdapter; | ||
import android.animation.AnimatorSet; | ||
import android.animation.ObjectAnimator; | ||
import android.animation.ValueAnimator; | ||
import android.content.res.Resources; | ||
import android.graphics.Rect; | ||
import android.os.Build.VERSION; | ||
import android.os.Build.VERSION_CODES; | ||
import android.view.RoundedCorner; | ||
import android.view.View; | ||
import android.view.WindowInsets; | ||
import android.window.BackEvent; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
import androidx.annotation.RequiresApi; | ||
import androidx.annotation.RestrictTo; | ||
import androidx.annotation.VisibleForTesting; | ||
import com.google.android.material.animation.AnimationUtils; | ||
import com.google.android.material.internal.ClippableRoundedCornerLayout; | ||
import com.google.android.material.internal.ViewUtils; | ||
|
||
/** | ||
* Utility class for main container views usually filling the entire screen (e.g., search view) that | ||
* support back progress animations. | ||
* | ||
* @hide | ||
*/ | ||
@RestrictTo(LIBRARY_GROUP) | ||
public class MaterialMainContainerBackHelper extends MaterialBackAnimationHelper { | ||
|
||
private static final float MIN_SCALE = 0.9f; | ||
|
||
private final float minEdgeGap; | ||
private final float maxTranslationY; | ||
|
||
private float initialTouchY; | ||
@Nullable private Rect initialHideToClipBounds; | ||
@Nullable private Rect initialHideFromClipBounds; | ||
@Nullable private Integer deviceCornerRadius; | ||
|
||
public MaterialMainContainerBackHelper(@NonNull View view) { | ||
super(view); | ||
|
||
Resources resources = view.getResources(); | ||
minEdgeGap = resources.getDimension(R.dimen.m3_back_progress_main_container_min_edge_gap); | ||
maxTranslationY = | ||
resources.getDimension(R.dimen.m3_back_progress_main_container_max_translation_y); | ||
} | ||
|
||
@Nullable | ||
public Rect getInitialHideToClipBounds() { | ||
return initialHideToClipBounds; | ||
} | ||
|
||
@Nullable | ||
public Rect getInitialHideFromClipBounds() { | ||
return initialHideFromClipBounds; | ||
} | ||
|
||
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) | ||
public void startBackProgress(@NonNull BackEvent backEvent, @NonNull View collapsedView) { | ||
super.onStartBackProgress(backEvent); | ||
|
||
startBackProgress(backEvent.getTouchY(), collapsedView); | ||
} | ||
|
||
@VisibleForTesting | ||
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) | ||
public void startBackProgress(float touchY, @NonNull View collapsedView) { | ||
collapsedView.setVisibility(View.INVISIBLE); | ||
|
||
initialHideToClipBounds = ViewUtils.calculateRectFromBounds(view); | ||
initialHideFromClipBounds = ViewUtils.calculateOffsetRectFromBounds(view, collapsedView); | ||
initialTouchY = touchY; | ||
} | ||
|
||
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) | ||
public void updateBackProgress(@NonNull BackEvent backEvent, float collapsedCornerSize) { | ||
super.onUpdateBackProgress(backEvent); | ||
|
||
boolean leftSwipeEdge = backEvent.getSwipeEdge() == BackEvent.EDGE_LEFT; | ||
updateBackProgress( | ||
backEvent.getProgress(), leftSwipeEdge, backEvent.getTouchY(), collapsedCornerSize); | ||
} | ||
|
||
@VisibleForTesting | ||
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) | ||
public void updateBackProgress( | ||
float progress, boolean leftSwipeEdge, float touchY, float collapsedCornerSize) { | ||
float width = view.getWidth(); | ||
float height = view.getHeight(); | ||
float scale = lerp(1, MIN_SCALE, progress); | ||
|
||
float availableHorizontalSpace = max(0, (width - MIN_SCALE * width) / 2 - minEdgeGap); | ||
float translationX = lerp(0, availableHorizontalSpace, progress) * (leftSwipeEdge ? 1 : -1); | ||
|
||
float availableVerticalSpace = max(0, (height - scale * height) / 2 - minEdgeGap); | ||
float maxTranslationY = min(availableVerticalSpace, this.maxTranslationY); | ||
float yDelta = touchY - initialTouchY; | ||
float yProgress = Math.abs(yDelta) / height; | ||
float translationYDirection = Math.signum(yDelta); | ||
float translationY = AnimationUtils.lerp(0, maxTranslationY, yProgress) * translationYDirection; | ||
|
||
view.setScaleX(scale); | ||
view.setScaleY(scale); | ||
view.setTranslationX(translationX); | ||
view.setTranslationY(translationY); | ||
if (view instanceof ClippableRoundedCornerLayout) { | ||
((ClippableRoundedCornerLayout) view) | ||
.updateCornerRadius(lerp(getDeviceCornerRadius(), collapsedCornerSize, progress)); | ||
} | ||
} | ||
|
||
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) | ||
public void finishBackProgress(long duration, @NonNull View collapsedView) { | ||
AnimatorSet resetAnimator = createResetScaleAndTranslationAnimator(collapsedView); | ||
resetAnimator.setDuration(duration); | ||
resetAnimator.start(); | ||
|
||
resetInitialValues(); | ||
} | ||
|
||
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) | ||
public void cancelBackProgress(@NonNull View collapsedView) { | ||
super.onCancelBackProgress(); | ||
|
||
AnimatorSet cancelAnimatorSet = createResetScaleAndTranslationAnimator(collapsedView); | ||
if (view instanceof ClippableRoundedCornerLayout) { | ||
cancelAnimatorSet.playTogether(createCornerAnimator((ClippableRoundedCornerLayout) view)); | ||
} | ||
cancelAnimatorSet.setDuration(cancelDuration); | ||
cancelAnimatorSet.start(); | ||
|
||
resetInitialValues(); | ||
} | ||
|
||
private void resetInitialValues() { | ||
initialTouchY = 0f; | ||
initialHideToClipBounds = null; | ||
initialHideFromClipBounds = null; | ||
} | ||
|
||
@NonNull | ||
private AnimatorSet createResetScaleAndTranslationAnimator(@NonNull View collapsedView) { | ||
AnimatorSet animatorSet = new AnimatorSet(); | ||
animatorSet.playTogether( | ||
ObjectAnimator.ofFloat(view, View.SCALE_X, 1), | ||
ObjectAnimator.ofFloat(view, View.SCALE_Y, 1), | ||
ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0), | ||
ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0)); | ||
animatorSet.addListener(new AnimatorListenerAdapter() { | ||
@Override | ||
public void onAnimationEnd(Animator animation) { | ||
collapsedView.setVisibility(View.VISIBLE); | ||
} | ||
}); | ||
return animatorSet; | ||
} | ||
|
||
@NonNull | ||
private ValueAnimator createCornerAnimator( | ||
ClippableRoundedCornerLayout clippableRoundedCornerLayout) { | ||
ValueAnimator cornerAnimator = | ||
ValueAnimator.ofFloat( | ||
clippableRoundedCornerLayout.getCornerRadius(), getDeviceCornerRadius()); | ||
cornerAnimator.addUpdateListener( | ||
animation -> | ||
clippableRoundedCornerLayout.updateCornerRadius((Float) animation.getAnimatedValue())); | ||
return cornerAnimator; | ||
} | ||
|
||
public int getDeviceCornerRadius() { | ||
if (deviceCornerRadius == null) { | ||
deviceCornerRadius = getMaxDeviceCornerRadius(); | ||
} | ||
return deviceCornerRadius; | ||
} | ||
|
||
private int getMaxDeviceCornerRadius() { | ||
if (VERSION.SDK_INT >= VERSION_CODES.S) { | ||
final WindowInsets insets = view.getRootWindowInsets(); | ||
if (insets != null) { | ||
return max( | ||
max( | ||
getRoundedCornerRadius(insets, RoundedCorner.POSITION_TOP_LEFT), | ||
getRoundedCornerRadius(insets, RoundedCorner.POSITION_TOP_RIGHT)), | ||
max( | ||
getRoundedCornerRadius(insets, RoundedCorner.POSITION_BOTTOM_LEFT), | ||
getRoundedCornerRadius(insets, RoundedCorner.POSITION_BOTTOM_RIGHT))); | ||
} | ||
} | ||
return 0; | ||
} | ||
|
||
@RequiresApi(VERSION_CODES.S) | ||
private int getRoundedCornerRadius(WindowInsets insets, int position) { | ||
final RoundedCorner roundedCorner = insets.getRoundedCorner(position); | ||
return roundedCorner != null ? roundedCorner.getRadius() : 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.