/* * Copyright (C) 2017 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.icons; import static android.graphics.Paint.ANTI_ALIAS_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.ViewDebug; /** * Used to draw a notification dot on top of an icon. */ public class DotRenderer { private static final String TAG = "DotRenderer"; // The dot size is defined as a percentage of the app icon size. private static final float SIZE_PERCENTAGE = 0.228f; private final float mCircleRadius; private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); private final Bitmap mBackgroundWithShadow; private final float mBitmapOffset; // Stores the center x and y position as a percentage (0 to 1) of the icon size private final float[] mRightDotPosition; private final float[] mLeftDotPosition; public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) { int size = Math.round(SIZE_PERCENTAGE * iconSizePx); ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); builder.ambientShadowAlpha = 88; mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size); mCircleRadius = builder.radius; mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width. // Find the points on the path that are closest to the top left and right corners. mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1); mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1); } private static float[] getPathPoint(Path path, float size, float direction) { float halfSize = size / 2; // Small delta so that we don't get a zero size triangle float delta = 1; float x = halfSize + direction * halfSize; Path trianglePath = new Path(); trianglePath.moveTo(halfSize, halfSize); trianglePath.lineTo(x + delta * direction, 0); trianglePath.lineTo(x, -delta); trianglePath.close(); trianglePath.op(path, Path.Op.INTERSECT); float[] pos = new float[2]; new PathMeasure(trianglePath, false).getPosTan(0, pos, null); pos[0] = pos[0] / size; pos[1] = pos[1] / size; return pos; } public float[] getLeftDotPosition() { return mLeftDotPosition; } public float[] getRightDotPosition() { return mRightDotPosition; } /** * Draw a circle on top of the canvas according to the given params. */ public void draw(Canvas canvas, DrawParams params) { if (params == null) { Log.e(TAG, "Invalid null argument(s) passed in call to draw."); return; } canvas.save(); Rect iconBounds = params.iconBounds; float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition; float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0]; float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1]; // Ensure dot fits entirely in canvas clip bounds. Rect canvasBounds = canvas.getClipBounds(); float offsetX = params.leftAlign ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset)) : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset)); float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset)); // We draw the dot relative to its center. canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY); canvas.scale(params.scale, params.scale); mCirclePaint.setColor(Color.BLACK); canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint); mCirclePaint.setColor(params.color); canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); canvas.restore(); } public static class DrawParams { /** The color (possibly based on the icon) to use for the dot. */ @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) public int color; /** The bounds of the icon that the dot is drawn on top of. */ @ViewDebug.ExportedProperty(category = "notification dot") public Rect iconBounds = new Rect(); /** The progress of the animation, from 0 to 1. */ @ViewDebug.ExportedProperty(category = "notification dot") public float scale; /** Whether the dot should align to the top left of the icon rather than the top right. */ @ViewDebug.ExportedProperty(category = "notification dot") public boolean leftAlign; } }