2 * Copyright (C) 2013 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.cyanogenmod.eleven.widgets;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.content.res.TypedArray;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.graphics.Canvas;
25 import android.graphics.ColorFilter;
26 import android.graphics.Paint;
27 import android.graphics.Paint.Align;
28 import android.graphics.Rect;
29 import android.graphics.Typeface;
30 import android.graphics.drawable.Drawable;
31 import android.text.TextUtils;
33 import junit.framework.Assert;
35 import com.cyanogenmod.eleven.R;
36 import com.cyanogenmod.eleven.cache.ImageWorker.ImageType;
37 import com.cyanogenmod.eleven.utils.BitmapWithColors;
38 import com.cyanogenmod.eleven.utils.MusicUtils;
41 * A drawable that encapsulates all the functionality needed to display a letter tile to
42 * represent a artist/album/playlist image.
44 public class LetterTileDrawable extends Drawable {
46 private final String TAG = LetterTileDrawable.class.getSimpleName();
48 private final Paint mPaint;
51 private static TypedArray sColors;
52 private static TypedArray sVibrantDarkColors;
53 private static int sDefaultColor;
54 private static int sTileFontColor;
55 private static float sLetterToTileRatio;
56 private static Bitmap DEFAULT_ARTIST;
57 private static Bitmap DEFAULT_ARTIST_LARGE;
58 private static Bitmap DEFAULT_ALBUM;
59 private static Bitmap DEFAULT_ALBUM_LARGE;
60 private static Bitmap DEFAULT_PLAYLIST;
61 private static Bitmap DEFAULT_PLAYLIST_LARGE;
63 /** Reusable components to avoid new allocations */
64 private static final Paint sPaint = new Paint();
65 private static final Rect sRect = new Rect();
66 private static final char[] sChars = new char[2];
68 private String mDisplayName;
69 private String mIdentifier;
70 private float mScale = 1.0f;
71 private float mOffset = 0.0f;
72 private Resources res;
73 private boolean mIsCircle = false;
75 private ImageType mImageType;
77 private static synchronized void initializeStaticVariables(final Resources res) {
78 if (sColors == null) {
79 sColors = res.obtainTypedArray(R.array.letter_tile_colors);
80 sVibrantDarkColors = res.obtainTypedArray(R.array.letter_tile_vibrant_dark_colors);
81 sDefaultColor = res.getColor(R.color.letter_tile_default_color);
82 sTileFontColor = res.getColor(R.color.letter_tile_font_color);
83 sLetterToTileRatio = res.getFraction(R.dimen.letter_to_tile_ratio, 1, 1);
84 DEFAULT_ARTIST = BitmapFactory.decodeResource(res, R.drawable.ic_artist);
85 DEFAULT_ARTIST_LARGE = BitmapFactory.decodeResource(res, R.drawable.ic_artist_lg);
86 DEFAULT_ALBUM = BitmapFactory.decodeResource(res, R.drawable.ic_album);
87 DEFAULT_ALBUM_LARGE = BitmapFactory.decodeResource(res, R.drawable.ic_album_lg);
88 DEFAULT_PLAYLIST = BitmapFactory.decodeResource(res, R.drawable.ic_playlist);
89 DEFAULT_PLAYLIST_LARGE = BitmapFactory.decodeResource(res, R.drawable.ic_playlist_lg);
91 sPaint.setTypeface(Typeface.create(
92 res.getString(R.string.letter_tile_letter_font_family), Typeface.NORMAL));
93 sPaint.setTextAlign(Align.CENTER);
94 sPaint.setAntiAlias(true);
98 public LetterTileDrawable(final Context context) {
100 mPaint.setFilterBitmap(true);
101 mPaint.setDither(true);
102 res = context.getResources();
104 initializeStaticVariables(res);
108 public void draw(final Canvas canvas) {
109 //setBounds(0, 0, 120, 120);
110 final Rect bounds = getBounds();
111 if (!isVisible() || bounds.isEmpty()) {
115 drawLetterTile(canvas);
119 public void setBounds(Rect bounds) {
120 super.setBounds(bounds);
123 private void drawLetterTile(final Canvas canvas) {
124 // Draw background color.
125 sPaint.setColor(pickColor(mIdentifier));
127 sPaint.setAlpha(mPaint.getAlpha());
128 final Rect bounds = getBounds();
129 final int minDimension = Math.min(bounds.width(), bounds.height());
132 canvas.drawCircle(bounds.centerX(), bounds.centerY(), minDimension / 2, sPaint);
134 canvas.drawRect(bounds, sPaint);
137 // Draw letter/digit only if the first character is an english letter
138 if (mDisplayName != null
139 && isEnglishLetter(mDisplayName.charAt(0))) {
142 // Draw letter or digit.
143 sChars[0] = Character.toUpperCase(mDisplayName.charAt(0));
145 if (mDisplayName.length() > 1 && isEnglishLetter(mDisplayName.charAt(1))) {
146 sChars[1] = Character.toLowerCase(mDisplayName.charAt(1));
150 // Scale text by canvas bounds and user selected scaling factor
151 sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension);
152 //sPaint.setTextSize(sTileLetterFontSize);
153 sPaint.getTextBounds(sChars, 0, numChars, sRect);
154 sPaint.setColor(sTileFontColor);
156 // Draw the letter in the canvas, vertically shifted up or down by the user-defined
158 canvas.drawText(sChars, 0, numChars, bounds.centerX(),
159 bounds.centerY() + mOffset * bounds.height() + sRect.height() / 2,
162 // Draw the default image if there is no letter/digit to be drawn
163 final Bitmap bitmap = getDefaultBitmapForImageType(mImageType, bounds);
165 // The bitmap should be drawn in the middle of the canvas without changing its width to
167 final Rect destRect = copyBounds();
169 drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(), canvas, destRect, mScale,
174 public int getColor() {
175 return pickColor(mIdentifier);
179 * @return the corresponding index in the color palette based on the identifier
181 private static int getColorIndex(final String identifier) {
182 if (TextUtils.isEmpty(identifier)) {
186 return Math.abs(identifier.hashCode()) % sColors.length();
190 * Returns a deterministic color based on the provided contact identifier string.
192 private static int pickColor(final String identifier) {
193 final int idx = getColorIndex(identifier);
195 return sDefaultColor;
198 return sColors.getColor(idx, sDefaultColor);
202 * Returns the vibrant matching color based on the provided contact identifier string.
204 private static int pickVibrantDarkColor(final String identifier) {
205 final int idx = getColorIndex(identifier);
207 return sDefaultColor;
210 return sVibrantDarkColors.getColor(idx, sDefaultColor);
214 * Gets the default image to show for the image type. If the bounds are large,
215 * it will use the large default bitmap
217 private static Bitmap getDefaultBitmapForImageType(ImageType type, Rect bounds) {
218 Bitmap ret = getDefaultBitmap(type, true);
219 if (Math.max(bounds.width(), bounds.height()) > Math.max(ret.getWidth(), ret.getHeight())) {
220 ret = getDefaultBitmap(type, false);
226 private static Bitmap getDefaultBitmap(ImageType type, boolean small) {
229 return small ? DEFAULT_ARTIST : DEFAULT_ARTIST_LARGE;
231 return small ? DEFAULT_ALBUM : DEFAULT_ALBUM_LARGE;
233 return small ? DEFAULT_PLAYLIST : DEFAULT_PLAYLIST_LARGE;
235 throw new IllegalArgumentException("Unrecognized image type");
239 private static boolean isEnglishLetter(final char c) {
240 return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9');
244 public void setAlpha(final int alpha) {
245 mPaint.setAlpha(alpha);
249 public void setColorFilter(final ColorFilter cf) {
250 mPaint.setColorFilter(cf);
254 public int getOpacity() {
255 return android.graphics.PixelFormat.OPAQUE;
259 * Scale the drawn letter tile to a ratio of its default size
261 * @param scale The ratio the letter tile should be scaled to as a percentage of its default
262 * size, from a scale of 0 to 2.0f. The default is 1.0f.
264 public void setScale(float scale) {
269 * Assigns the vertical offset of the position of the letter tile to the ContactDrawable
271 * @param offset The provided offset must be within the range of -0.5f to 0.5f.
272 * If set to -0.5f, the letter will be shifted upwards by 0.5 times the height of the canvas
273 * it is being drawn on, which means it will be drawn with the center of the letter starting
274 * at the top edge of the canvas.
275 * If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of the canvas
276 * it is being drawn on, which means it will be drawn with the center of the letter starting
277 * at the bottom edge of the canvas.
278 * The default is 0.0f.
280 public void setOffset(float offset) {
281 Assert.assertTrue(offset >= -0.5f && offset <= 0.5f);
286 * Sets the tile data used to determine the display text and color
287 * @param displayName the name to display - Some logic will be applied to do some trimming
288 * and up to the first two letters will be displayed
289 * @param identifier the identifier used to determine the color of the background. For
290 * album, use albumId, for artist use artistName and for playlist use
292 * @param type the type of item that this tile drawable corresponds to
294 public void setTileDetails(final String displayName, final String identifier,
295 final ImageType type) {
296 mDisplayName = MusicUtils.getTrimmedName(displayName);
297 mIdentifier = MusicUtils.getTrimmedName(identifier);
302 public void setIsCircular(boolean isCircle) {
303 mIsCircle = isCircle;
307 * Draw the bitmap onto the canvas at the current bounds taking into account the current scale.
309 private static void drawBitmap(final Bitmap bitmap, final int width, final int height,
310 final Canvas canvas, final Rect destRect, final float scale,
311 final float offset, final Paint paint) {
312 // Crop the destination bounds into a square, scaled and offset as appropriate
313 final int halfLength = (int) (scale * Math.min(destRect.width(), destRect.height()) / 2);
315 destRect.set(destRect.centerX() - halfLength,
316 (int) (destRect.centerY() - halfLength + offset * destRect.height()),
317 destRect.centerX() + halfLength,
318 (int) (destRect.centerY() + halfLength + offset * destRect.height()));
320 // Source rectangle remains the entire bounds of the source bitmap.
321 sRect.set(0, 0, width, height);
323 canvas.drawBitmap(bitmap, sRect, destRect, paint);
327 * Draws the default letter tile drawable for the image type to a bitmap
328 * @param identifier the identifier used to determine the color of the background. For
329 * album, use albumId, for artist use artistName and for playlist use
331 * @param type the type of item that this tile drawable corresponds to
332 * @param isCircle whether to draw a circle or a square
333 * @param smallArtwork true if you want to draw a smaller version of the default bitmap for
334 * perf/memory reasons
336 public static BitmapWithColors createDefaultBitmap(Context context, String identifier,
337 ImageType type, boolean isCircle, boolean smallArtwork) {
338 initializeStaticVariables(context.getResources());
340 identifier = MusicUtils.getTrimmedName(identifier);
342 // get the default bitmap to determine what to draw to
343 Bitmap defaultBitmap = getDefaultBitmap(type, smallArtwork);
344 final Rect bounds = new Rect(0, 0, defaultBitmap.getWidth(), defaultBitmap.getHeight());
346 // create a bitmap and canvas for drawing
347 Bitmap createdBitmap = Bitmap.createBitmap(defaultBitmap.getWidth(),
348 defaultBitmap.getHeight(), defaultBitmap.getConfig());
349 Canvas canvas = new Canvas(createdBitmap);
351 int color = pickColor(identifier);
352 int vibrantDarkColor = pickVibrantDarkColor(identifier);
354 Paint paint = new Paint();
355 paint.setColor(color);
357 final int minDimension = Math.min(bounds.width(), bounds.height());
360 canvas.drawCircle(bounds.centerX(), bounds.centerY(), minDimension / 2, paint);
362 canvas.drawRect(bounds, paint);
365 // draw to the bitmap
366 drawBitmap(defaultBitmap, defaultBitmap.getWidth(), defaultBitmap.getHeight(), canvas,
367 bounds, 1, 0, paint);
369 return new BitmapWithColors(createdBitmap, identifier.hashCode(), color, vibrantDarkColor);