Skip to content

Commit c9e2ba0

Browse files
committed
[Motion] Improved MaterialContainerTransform approximation of native elevation shadows by using location of bounds on screen
Resolves #1543 Follow-up to #1126 PiperOrigin-RevId: 324807509 (cherry picked from commit 23d6100)
1 parent 775d286 commit c9e2ba0

File tree

2 files changed

+114
-16
lines changed

2 files changed

+114
-16
lines changed

lib/java/com/google/android/material/transition/MaterialContainerTransform.java

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@
5252
import android.os.Build.VERSION;
5353
import android.os.Build.VERSION_CODES;
5454
import androidx.core.view.ViewCompat;
55+
import android.util.DisplayMetrics;
5556
import android.util.Log;
5657
import android.view.View;
5758
import android.view.ViewGroup;
59+
import android.view.WindowManager;
5860
import androidx.annotation.ColorInt;
5961
import androidx.annotation.FloatRange;
6062
import androidx.annotation.IdRes;
@@ -1032,10 +1034,13 @@ private ProgressThresholdsGroup getThresholdsOrDefault(
10321034
*/
10331035
private static final class TransitionDrawable extends Drawable {
10341036

1035-
// Elevation shadow
1037+
// Elevation shadow colors
10361038
private static final int SHADOW_COLOR = 0x2D000000;
10371039
private static final int COMPAT_SHADOW_COLOR = 0xFF888888;
1038-
private static final float COMPAT_SHADOW_OFFSET_MULTIPLIER = 0.75f;
1040+
1041+
// Elevation shadow offset multiplier adjustments which help approximate native shadows
1042+
private static final float SHADOW_DX_MULTIPLIER_ADJUSTMENT = 0.3f;
1043+
private static final float SHADOW_DY_MULTIPLIER_ADJUSTMENT = 1.5f;
10391044

10401045
// Start container
10411046
private final View startView;
@@ -1064,6 +1069,8 @@ private static final class TransitionDrawable extends Drawable {
10641069

10651070
// Drawing
10661071
private final boolean entering;
1072+
private final float displayWidth;
1073+
private final float displayHeight;
10671074
private final boolean elevationShadowEnabled;
10681075
private final MaterialShapeDrawable compatShadowDrawable = new MaterialShapeDrawable();
10691076
private final RectF currentStartBounds;
@@ -1084,6 +1091,7 @@ private static final class TransitionDrawable extends Drawable {
10841091
private FitModeResult fitModeResult;
10851092
private RectF currentMaskBounds;
10861093
private float currentElevation;
1094+
private float currentElevationDy;
10871095
private float progress;
10881096

10891097
private TransitionDrawable(
@@ -1121,6 +1129,13 @@ private TransitionDrawable(
11211129
this.progressThresholds = progressThresholds;
11221130
this.drawDebugEnabled = drawDebugEnabled;
11231131

1132+
WindowManager windowManager =
1133+
(WindowManager) startView.getContext().getSystemService(Context.WINDOW_SERVICE);
1134+
DisplayMetrics displayMetrics = new DisplayMetrics();
1135+
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
1136+
displayWidth = displayMetrics.widthPixels;
1137+
displayHeight = displayMetrics.heightPixels;
1138+
11241139
containerPaint.setColor(containerColor);
11251140
startContainerPaint.setColor(startContainerColor);
11261141
endContainerPaint.setColor(endContainerColor);
@@ -1230,8 +1245,7 @@ private void drawElevationShadowWithMaterialShapeDrawable(Canvas canvas) {
12301245
(int) currentMaskBounds.right,
12311246
(int) currentMaskBounds.bottom);
12321247
compatShadowDrawable.setElevation(currentElevation);
1233-
compatShadowDrawable.setShadowVerticalOffset(
1234-
(int) (currentElevation * COMPAT_SHADOW_OFFSET_MULTIPLIER));
1248+
compatShadowDrawable.setShadowVerticalOffset((int) currentElevationDy);
12351249
compatShadowDrawable.setShapeAppearanceModel(maskEvaluator.getCurrentShapeAppearanceModel());
12361250
compatShadowDrawable.draw(canvas);
12371251
}
@@ -1307,10 +1321,6 @@ private void updateProgress(float progress) {
13071321
// Fade in/out scrim over non-shared elements
13081322
scrimPaint.setAlpha((int) (entering ? lerp(0, 255, progress) : lerp(255, 0, progress)));
13091323

1310-
// Calculate current elevation and set up shadow layer
1311-
currentElevation = lerp(startElevation, endElevation, progress);
1312-
shadowPaint.setShadowLayer(currentElevation, 0, currentElevation, SHADOW_COLOR);
1313-
13141324
// Calculate position based on motion path
13151325
motionPathMeasure.getPosTan(motionPathLength * progress, motionPathPosition, null);
13161326
float motionPathX = motionPathPosition[0];
@@ -1367,6 +1377,15 @@ private void updateProgress(float progress) {
13671377
currentEndBoundsMasked,
13681378
progressThresholds.shapeMask);
13691379

1380+
// Calculate current elevation and set up shadow layer
1381+
currentElevation = lerp(startElevation, endElevation, progress);
1382+
float dxMultiplier = calculateElevationDxMultiplier(currentMaskBounds, displayWidth);
1383+
float dyMultiplier = calculateElevationDyMultiplier(currentMaskBounds, displayHeight);
1384+
float currentElevationDx = (int) (currentElevation * dxMultiplier);
1385+
currentElevationDy = (int) (currentElevation * dyMultiplier);
1386+
shadowPaint.setShadowLayer(
1387+
currentElevation, currentElevationDx, currentElevationDy, SHADOW_COLOR);
1388+
13701389
// Cross-fade images of the start/end states over range of `progress`
13711390
float fadeStartFraction = checkNotNull(progressThresholds.fade.start);
13721391
float fadeEndFraction = checkNotNull(progressThresholds.fade.end);
@@ -1388,6 +1407,36 @@ private static PointF getMotionPathPoint(RectF bounds) {
13881407
return new PointF(bounds.centerX(), bounds.top);
13891408
}
13901409

1410+
/**
1411+
* The dx value for the elevation shadow's horizontal offset should be based on where the
1412+
* current bounds are located in relation to the horizontal mid-point of the screen, since
1413+
* that's where the native light source is located.
1414+
*
1415+
* <p>If the bounds are at the mid-point, the offset should be 0, which results in even shadows
1416+
* to the left and right of the view.
1417+
*
1418+
* <p>If the bounds are to the left of the mid-point, the offset should be negative, which
1419+
* results in more shadow to the left of the view.
1420+
*
1421+
* <p>If the bounds are to the right of the mid-point, the offset should be positive, which
1422+
* results in more shadow to the right of the view.
1423+
*/
1424+
private static float calculateElevationDxMultiplier(RectF bounds, float displayWidth) {
1425+
return (bounds.centerX() / (displayWidth / 2) - 1) * SHADOW_DX_MULTIPLIER_ADJUSTMENT;
1426+
}
1427+
1428+
/**
1429+
* The dy value for the elevation shadow's vertical offset should be based on where the current
1430+
* bounds are located in relation to the top of the screen, since that's where the native light
1431+
* source is located.
1432+
*
1433+
* <p>The offset should be 0 at the top of the screen and increase as the bounds get further
1434+
* away from the top of the screen.
1435+
*/
1436+
private static float calculateElevationDyMultiplier(RectF bounds, float displayHeight) {
1437+
return bounds.centerY() / displayHeight * SHADOW_DY_MULTIPLIER_ADJUSTMENT;
1438+
}
1439+
13911440
private void drawDebugCumulativePath(
13921441
Canvas canvas, RectF bounds, Path path, @ColorInt int color) {
13931442
PointF point = getMotionPathPoint(bounds);

lib/java/com/google/android/material/transition/platform/MaterialContainerTransform.java

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@
5656
import android.os.Build.VERSION;
5757
import android.os.Build.VERSION_CODES;
5858
import androidx.core.view.ViewCompat;
59+
import android.util.DisplayMetrics;
5960
import android.util.Log;
6061
import android.view.View;
6162
import android.view.ViewGroup;
63+
import android.view.WindowManager;
6264
import androidx.annotation.ColorInt;
6365
import androidx.annotation.FloatRange;
6466
import androidx.annotation.IdRes;
@@ -1037,10 +1039,13 @@ private ProgressThresholdsGroup getThresholdsOrDefault(
10371039
*/
10381040
private static final class TransitionDrawable extends Drawable {
10391041

1040-
// Elevation shadow
1042+
// Elevation shadow colors
10411043
private static final int SHADOW_COLOR = 0x2D000000;
10421044
private static final int COMPAT_SHADOW_COLOR = 0xFF888888;
1043-
private static final float COMPAT_SHADOW_OFFSET_MULTIPLIER = 0.75f;
1045+
1046+
// Elevation shadow offset multiplier adjustments which help approximate native shadows
1047+
private static final float SHADOW_DX_MULTIPLIER_ADJUSTMENT = 0.3f;
1048+
private static final float SHADOW_DY_MULTIPLIER_ADJUSTMENT = 1.5f;
10441049

10451050
// Start container
10461051
private final View startView;
@@ -1069,6 +1074,8 @@ private static final class TransitionDrawable extends Drawable {
10691074

10701075
// Drawing
10711076
private final boolean entering;
1077+
private final float displayWidth;
1078+
private final float displayHeight;
10721079
private final boolean elevationShadowEnabled;
10731080
private final MaterialShapeDrawable compatShadowDrawable = new MaterialShapeDrawable();
10741081
private final RectF currentStartBounds;
@@ -1089,6 +1096,7 @@ private static final class TransitionDrawable extends Drawable {
10891096
private FitModeResult fitModeResult;
10901097
private RectF currentMaskBounds;
10911098
private float currentElevation;
1099+
private float currentElevationDy;
10921100
private float progress;
10931101

10941102
private TransitionDrawable(
@@ -1126,6 +1134,13 @@ private TransitionDrawable(
11261134
this.progressThresholds = progressThresholds;
11271135
this.drawDebugEnabled = drawDebugEnabled;
11281136

1137+
WindowManager windowManager =
1138+
(WindowManager) startView.getContext().getSystemService(Context.WINDOW_SERVICE);
1139+
DisplayMetrics displayMetrics = new DisplayMetrics();
1140+
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
1141+
displayWidth = displayMetrics.widthPixels;
1142+
displayHeight = displayMetrics.heightPixels;
1143+
11291144
containerPaint.setColor(containerColor);
11301145
startContainerPaint.setColor(startContainerColor);
11311146
endContainerPaint.setColor(endContainerColor);
@@ -1235,8 +1250,7 @@ private void drawElevationShadowWithMaterialShapeDrawable(Canvas canvas) {
12351250
(int) currentMaskBounds.right,
12361251
(int) currentMaskBounds.bottom);
12371252
compatShadowDrawable.setElevation(currentElevation);
1238-
compatShadowDrawable.setShadowVerticalOffset(
1239-
(int) (currentElevation * COMPAT_SHADOW_OFFSET_MULTIPLIER));
1253+
compatShadowDrawable.setShadowVerticalOffset((int) currentElevationDy);
12401254
compatShadowDrawable.setShapeAppearanceModel(maskEvaluator.getCurrentShapeAppearanceModel());
12411255
compatShadowDrawable.draw(canvas);
12421256
}
@@ -1312,10 +1326,6 @@ private void updateProgress(float progress) {
13121326
// Fade in/out scrim over non-shared elements
13131327
scrimPaint.setAlpha((int) (entering ? lerp(0, 255, progress) : lerp(255, 0, progress)));
13141328

1315-
// Calculate current elevation and set up shadow layer
1316-
currentElevation = lerp(startElevation, endElevation, progress);
1317-
shadowPaint.setShadowLayer(currentElevation, 0, currentElevation, SHADOW_COLOR);
1318-
13191329
// Calculate position based on motion path
13201330
motionPathMeasure.getPosTan(motionPathLength * progress, motionPathPosition, null);
13211331
float motionPathX = motionPathPosition[0];
@@ -1372,6 +1382,15 @@ private void updateProgress(float progress) {
13721382
currentEndBoundsMasked,
13731383
progressThresholds.shapeMask);
13741384

1385+
// Calculate current elevation and set up shadow layer
1386+
currentElevation = lerp(startElevation, endElevation, progress);
1387+
float dxMultiplier = calculateElevationDxMultiplier(currentMaskBounds, displayWidth);
1388+
float dyMultiplier = calculateElevationDyMultiplier(currentMaskBounds, displayHeight);
1389+
float currentElevationDx = (int) (currentElevation * dxMultiplier);
1390+
currentElevationDy = (int) (currentElevation * dyMultiplier);
1391+
shadowPaint.setShadowLayer(
1392+
currentElevation, currentElevationDx, currentElevationDy, SHADOW_COLOR);
1393+
13751394
// Cross-fade images of the start/end states over range of `progress`
13761395
float fadeStartFraction = checkNotNull(progressThresholds.fade.start);
13771396
float fadeEndFraction = checkNotNull(progressThresholds.fade.end);
@@ -1393,6 +1412,36 @@ private static PointF getMotionPathPoint(RectF bounds) {
13931412
return new PointF(bounds.centerX(), bounds.top);
13941413
}
13951414

1415+
/**
1416+
* The dx value for the elevation shadow's horizontal offset should be based on where the
1417+
* current bounds are located in relation to the horizontal mid-point of the screen, since
1418+
* that's where the native light source is located.
1419+
*
1420+
* <p>If the bounds are at the mid-point, the offset should be 0, which results in even shadows
1421+
* to the left and right of the view.
1422+
*
1423+
* <p>If the bounds are to the left of the mid-point, the offset should be negative, which
1424+
* results in more shadow to the left of the view.
1425+
*
1426+
* <p>If the bounds are to the right of the mid-point, the offset should be positive, which
1427+
* results in more shadow to the right of the view.
1428+
*/
1429+
private static float calculateElevationDxMultiplier(RectF bounds, float displayWidth) {
1430+
return (bounds.centerX() / (displayWidth / 2) - 1) * SHADOW_DX_MULTIPLIER_ADJUSTMENT;
1431+
}
1432+
1433+
/**
1434+
* The dy value for the elevation shadow's vertical offset should be based on where the current
1435+
* bounds are located in relation to the top of the screen, since that's where the native light
1436+
* source is located.
1437+
*
1438+
* <p>The offset should be 0 at the top of the screen and increase as the bounds get further
1439+
* away from the top of the screen.
1440+
*/
1441+
private static float calculateElevationDyMultiplier(RectF bounds, float displayHeight) {
1442+
return bounds.centerY() / displayHeight * SHADOW_DY_MULTIPLIER_ADJUSTMENT;
1443+
}
1444+
13961445
private void drawDebugCumulativePath(
13971446
Canvas canvas, RectF bounds, Path path, @ColorInt int color) {
13981447
PointF point = getMotionPathPoint(bounds);

0 commit comments

Comments
 (0)