Merge "Using clippath for adaptive icon drawable instead of bitmap shader" into tm-qpr-dev

This commit is contained in:
Sunny Goyal
2022-07-26 16:16:48 +00:00
committed by Android (Google) Code Review
2 changed files with 59 additions and 133 deletions

View File

@@ -204,6 +204,19 @@ public class AdaptiveIconDrawableTest extends AndroidTestCase {
assertEquals(100, Color.alpha(bitmap.getPixel(50, 50))); assertEquals(100, Color.alpha(bitmap.getPixel(50, 50)));
} }
@Test
public void testSetBounds() throws Exception {
mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
mIconDrawable.setBounds(0, 0, 100, 100);
assertEquals(new Rect(-25, -25, 125, 125), mBackgroundDrawable.getBounds());
assertEquals(new Rect(-25, -25, 125, 125), mForegroundDrawable.getBounds());
mIconDrawable.setBounds(10, 10, 110, 110);
assertEquals(new Rect(-15, -15, 135, 135), mBackgroundDrawable.getBounds());
assertEquals(new Rect(-15, -15, 135, 135), mForegroundDrawable.getBounds());
}
// //
// Utils // Utils
// //

View File

@@ -26,8 +26,6 @@ import android.content.res.ColorStateList;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.Resources.Theme; import android.content.res.Resources.Theme;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.BlendMode; import android.graphics.BlendMode;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
@@ -39,8 +37,6 @@ import android.graphics.Path;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Region; import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.PathParser; import android.util.PathParser;
@@ -106,7 +102,8 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE); private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
/** /**
* Clip path defined in R.string.config_icon_mask. * Unused path.
* TODO: Remove once the layoutLib is updated
*/ */
private static Path sMask; private static Path sMask;
@@ -114,9 +111,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
* Scaled mask based on the view bounds. * Scaled mask based on the view bounds.
*/ */
private final Path mMask; private final Path mMask;
private final Path mMaskScaleOnly; private final Path mMaskTransformed;
private final Matrix mMaskMatrix; private final Matrix mMaskMatrix;
private final Region mTransparentRegion; private final Region mTransparentRegion;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/** /**
* Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
@@ -131,19 +129,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
*/ */
LayerState mLayerState; LayerState mLayerState;
private Shader mLayersShader;
private Bitmap mLayersBitmap;
private final Rect mTmpOutRect = new Rect(); private final Rect mTmpOutRect = new Rect();
private Rect mHotspotBounds; private Rect mHotspotBounds;
private boolean mMutated; private boolean mMutated;
private boolean mSuspendChildInvalidation;
private boolean mChildRequestedInvalidation;
private final Canvas mCanvas;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
Paint.FILTER_BITMAP_FLAG);
/** /**
* Constructor used for xml inflation. * Constructor used for xml inflation.
*/ */
@@ -157,19 +146,16 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
*/ */
AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) { AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
mLayerState = createConstantState(state, res); mLayerState = createConstantState(state, res);
// config_icon_mask from context bound resource may have been chaged using // config_icon_mask from context bound resource may have been changed using
// OverlayManager. Read that one first. // OverlayManager. Read that one first.
Resources r = ActivityThread.currentActivityThread() == null Resources r = ActivityThread.currentActivityThread() == null
? Resources.getSystem() ? Resources.getSystem()
: ActivityThread.currentActivityThread().getApplication().getResources(); : ActivityThread.currentActivityThread().getApplication().getResources();
// TODO: either make sMask update only when config_icon_mask changes OR mMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
// get rid of it all-together in layoutlib mMaskTransformed = new Path();
sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
mMask = new Path(sMask);
mMaskScaleOnly = new Path(mMask);
mMaskMatrix = new Matrix(); mMaskMatrix = new Matrix();
mCanvas = new Canvas();
mTransparentRegion = new Region(); mTransparentRegion = new Region();
mPaint.setColor(Color.BLACK);
} }
private ChildDrawable createChildDrawable(Drawable drawable) { private ChildDrawable createChildDrawable(Drawable drawable) {
@@ -280,7 +266,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
* @return the mask path object used to clip the drawable * @return the mask path object used to clip the drawable
*/ */
public Path getIconMask() { public Path getIconMask() {
return mMask; return mMaskTransformed;
} }
/** /**
@@ -322,92 +308,47 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
if (bounds.isEmpty()) { if (bounds.isEmpty()) {
return; return;
} }
updateLayerBounds(bounds); // Set the child layer bounds bigger than the view port size
} // by {@link #DEFAULT_VIEW_PORT_SCALE}
float cX = bounds.exactCenterX();
private void updateLayerBounds(Rect bounds) { float cY = bounds.exactCenterY();
if (bounds.isEmpty()) { float insetWidth = bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2);
return; float insetHeight = bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2);
} final Rect outRect = mTmpOutRect;
try { outRect.set(
suspendChildInvalidation(); (int) (cX - insetWidth),
updateLayerBoundsInternal(bounds); (int) (cY - insetHeight),
updateMaskBoundsInternal(bounds); (int) (cX + insetWidth),
} finally { (int) (cY + insetHeight));
resumeChildInvalidation();
}
}
/**
* Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE}
*/
private void updateLayerBoundsInternal(Rect bounds) {
int cX = bounds.width() / 2;
int cY = bounds.height() / 2;
for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) { for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
final ChildDrawable r = mLayerState.mChildren[i]; final ChildDrawable r = mLayerState.mChildren[i];
final Drawable d = r.mDrawable; if (r.mDrawable != null) {
if (d == null) { r.mDrawable.setBounds(outRect);
continue;
}
int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
final Rect outRect = mTmpOutRect;
outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
d.setBounds(outRect);
} }
} }
private void updateMaskBoundsInternal(Rect b) { // Update the clipping mask
// reset everything that depends on the view bounds mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE);
mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE); mMaskMatrix.postTranslate(bounds.left, bounds.top);
sMask.transform(mMaskMatrix, mMaskScaleOnly); mMask.transform(mMaskMatrix, mMaskTransformed);
mMaskMatrix.postTranslate(b.left, b.top); // Clear the transparent region, it is calculated lazily
sMask.transform(mMaskMatrix, mMask);
if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width()
|| mLayersBitmap.getHeight() != b.height()) {
mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
}
mPaint.setShader(null);
mTransparentRegion.setEmpty(); mTransparentRegion.setEmpty();
mLayersShader = null;
} }
@Override @Override
public void draw(Canvas canvas) { public void draw(Canvas canvas) {
if (mLayersBitmap == null) { int saveCount = canvas.save();
return; canvas.clipPath(mMaskTransformed);
} canvas.drawPaint(mPaint);
if (mLayersShader == null) {
mCanvas.setBitmap(mLayersBitmap);
mCanvas.drawColor(Color.BLACK);
if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) { if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas); mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(canvas);
} }
if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) { if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas); mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(canvas);
} }
mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP); canvas.restoreToCount(saveCount);
mPaint.setShader(mLayersShader);
}
if (mMaskScaleOnly != null) {
Rect bounds = getBounds();
canvas.translate(bounds.left, bounds.top);
canvas.drawPath(mMaskScaleOnly, mPaint);
canvas.translate(-bounds.left, -bounds.top);
}
}
@Override
public void invalidateSelf() {
mLayersShader = null;
super.invalidateSelf();
} }
@Override @Override
@@ -600,38 +541,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
return false; return false;
} }
/**
* Temporarily suspends child invalidation.
*
* @see #resumeChildInvalidation()
*/
private void suspendChildInvalidation() {
mSuspendChildInvalidation = true;
}
/**
* Resumes child invalidation after suspension, immediately performing an
* invalidation if one was requested by a child during suspension.
*
* @see #suspendChildInvalidation()
*/
private void resumeChildInvalidation() {
mSuspendChildInvalidation = false;
if (mChildRequestedInvalidation) {
mChildRequestedInvalidation = false;
invalidateSelf();
}
}
@Override @Override
public void invalidateDrawable(@NonNull Drawable who) { public void invalidateDrawable(@NonNull Drawable who) {
if (mSuspendChildInvalidation) {
mChildRequestedInvalidation = true;
} else {
invalidateSelf(); invalidateSelf();
} }
}
@Override @Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
@@ -714,6 +627,13 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
@Override @Override
public void setAlpha(int alpha) { public void setAlpha(int alpha) {
mPaint.setAlpha(alpha); mPaint.setAlpha(alpha);
final ChildDrawable[] array = mLayerState.mChildren;
for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setAlpha(alpha);
}
}
} }
@Override @Override
@@ -816,10 +736,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
} }
} }
if (changed) {
updateLayerBounds(getBounds());
}
return changed; return changed;
} }
@@ -835,10 +751,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
} }
} }
if (changed) {
updateLayerBounds(getBounds());
}
return changed; return changed;
} }
@@ -979,6 +891,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
int mDensity; int mDensity;
// The density to use when inflating/looking up the children drawables. A value of 0 means // The density to use when inflating/looking up the children drawables. A value of 0 means
// use the system's density. // use the system's density.
int mSrcDensityOverride = 0; int mSrcDensityOverride = 0;