| /* |
| * 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; |
| } |
| } |