5252import android .os .Build .VERSION ;
5353import android .os .Build .VERSION_CODES ;
5454import androidx .core .view .ViewCompat ;
55+ import android .util .DisplayMetrics ;
5556import android .util .Log ;
5657import android .view .View ;
5758import android .view .ViewGroup ;
59+ import android .view .WindowManager ;
5860import androidx .annotation .ColorInt ;
5961import androidx .annotation .FloatRange ;
6062import 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 );
0 commit comments