LetterTileDrawable.java revision 6f99ad746dd89fc4998922f1bc60a4818d3d01d8
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package com.android.contacts.common.lettertiles; 18 19import android.content.res.Resources; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.ColorFilter; 23import android.graphics.Paint; 24import android.graphics.Paint.Align; 25import android.graphics.Rect; 26import android.graphics.Typeface; 27import android.graphics.drawable.Drawable; 28import android.text.TextUtils; 29 30import com.android.contacts.common.R; 31 32import junit.framework.Assert; 33 34/** 35 * A drawable that encapsulates all the functionality needed to display a letter tile to 36 * represent a contact image. 37 */ 38public class LetterTileDrawable extends Drawable { 39 40 private final String TAG = LetterTileDrawable.class.getSimpleName(); 41 42 private final Paint mPaint; 43 44 /** Letter tile */ 45 private static TypedArray sColors; 46 private static int sDefaultColor; 47 private static int sTileFontColor; 48 private static float sLetterToTileRatio; 49 private static Drawable DEFAULT_PERSON_AVATAR; 50 private static Drawable DEFAULT_BUSINESS_AVATAR; 51 private static Drawable DEFAULT_VOICEMAIL_AVATAR; 52 53 /** Reusable components to avoid new allocations */ 54 private static final Paint sPaint = new Paint(); 55 private static final Rect sRect = new Rect(); 56 private static final char[] sFirstChar = new char[1]; 57 58 /** Contact type constants */ 59 public static final int TYPE_PERSON = 1; 60 public static final int TYPE_BUSINESS = 2; 61 public static final int TYPE_VOICEMAIL = 3; 62 public static final int TYPE_DEFAULT = TYPE_PERSON; 63 64 private String mDisplayName; 65 private String mIdentifier; 66 private int mContactType = TYPE_DEFAULT; 67 private float mScale = 1.0f; 68 private float mOffset = 0.0f; 69 private boolean mIsCircle = false; 70 71 public LetterTileDrawable(final Resources res) { 72 mPaint = new Paint(); 73 mPaint.setDither(true); 74 75 if (sColors == null) { 76 sColors = res.obtainTypedArray(R.array.letter_tile_colors); 77 sDefaultColor = res.getColor(R.color.letter_tile_default_color); 78 sTileFontColor = res.getColor(R.color.letter_tile_font_color); 79 sLetterToTileRatio = res.getFraction(R.dimen.letter_to_tile_ratio, 1, 1); 80 DEFAULT_BUSINESS_AVATAR = res.getDrawable(R.drawable.ic_list_item_businessavatar); 81 DEFAULT_VOICEMAIL_AVATAR = res.getDrawable(R.drawable.ic_voicemail_avatar); 82 DEFAULT_PERSON_AVATAR = res.getDrawable(R.drawable.ic_tile_letter_avatar_white); 83 // This drawable contains a ScaleDrawable that won't be visible without setLevel(1). 84 DEFAULT_PERSON_AVATAR.setLevel(1); 85 sPaint.setTypeface(Typeface.create( 86 res.getString(R.string.letter_tile_letter_font_family), Typeface.NORMAL)); 87 sPaint.setTextAlign(Align.CENTER); 88 sPaint.setAntiAlias(true); 89 } 90 } 91 92 @Override 93 public void draw(final Canvas canvas) { 94 final Rect bounds = getBounds(); 95 if (!isVisible() || bounds.isEmpty()) { 96 return; 97 } 98 // Draw letter tile. 99 drawLetterTile(canvas); 100 } 101 102 /** 103 * Draw the inner drawable onto the canvas at the current bounds taking into account the current 104 * scale. 105 */ 106 private void drawInnerDrawable(final Drawable drawable, final Canvas canvas) { 107 // The drawable should be drawn in the middle of the canvas without changing its width to 108 // height ratio. 109 final Rect destRect = copyBounds(); 110 111 // Crop the destination bounds into a square, scaled and offset as appropriate 112 final int halfLength = (int) (mScale * Math.min(destRect.width(), destRect.height()) / 2); 113 114 destRect.set(destRect.centerX() - halfLength, 115 (int) (destRect.centerY() - halfLength + mOffset * destRect.height()), 116 destRect.centerX() + halfLength, 117 (int) (destRect.centerY() + halfLength + mOffset * destRect.height())); 118 119 drawable.setDither(true); 120 drawable.setFilterBitmap(true); 121 drawable.setBounds(destRect); 122 drawable.setAlpha(mPaint.getAlpha()); 123 drawable.draw(canvas); 124 } 125 126 private void drawLetterTile(final Canvas canvas) { 127 // Draw background color. 128 sPaint.setColor(pickColor(mIdentifier)); 129 130 sPaint.setAlpha(mPaint.getAlpha()); 131 final Rect bounds = getBounds(); 132 final int minDimension = Math.min(bounds.width(), bounds.height()); 133 134 if (mIsCircle) { 135 canvas.drawCircle(bounds.centerX(), bounds.centerY(), minDimension / 2, sPaint); 136 } else { 137 canvas.drawRect(bounds, sPaint); 138 } 139 140 // Draw letter/digit only if the first character is an english letter 141 if (mDisplayName != null && isEnglishLetter(mDisplayName.charAt(0))) { 142 // Draw letter or digit. 143 sFirstChar[0] = Character.toUpperCase(mDisplayName.charAt(0)); 144 145 // Scale text by canvas bounds and user selected scaling factor 146 sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension); 147 //sPaint.setTextSize(sTileLetterFontSize); 148 sPaint.getTextBounds(sFirstChar, 0, 1, sRect); 149 sPaint.setColor(sTileFontColor); 150 151 // Draw the letter in the canvas, vertically shifted up or down by the user-defined 152 // offset 153 canvas.drawText(sFirstChar, 0, 1, bounds.centerX(), 154 bounds.centerY() + mOffset * bounds.height() + sRect.height() / 2, 155 sPaint); 156 } else { 157 // Draw the default image if there is no letter/digit to be drawn 158 final Drawable innerDrawable = getInnerDrawableForContactType(mContactType); 159 drawInnerDrawable(innerDrawable, canvas); 160 } 161 } 162 163 public int getColor() { 164 return pickColor(mIdentifier); 165 } 166 167 /** 168 * Returns a deterministic color based on the provided contact identifier string. 169 */ 170 private int pickColor(final String identifier) { 171 if (TextUtils.isEmpty(identifier) || mContactType == TYPE_VOICEMAIL) { 172 return sDefaultColor; 173 } 174 // String.hashCode() implementation is not supposed to change across java versions, so 175 // this should guarantee the same email address always maps to the same color. 176 // The email should already have been normalized by the ContactRequest. 177 final int color = Math.abs(identifier.hashCode()) % sColors.length(); 178 return sColors.getColor(color, sDefaultColor); 179 } 180 181 private static Drawable getInnerDrawableForContactType(int contactType) { 182 switch (contactType) { 183 case TYPE_PERSON: 184 return DEFAULT_PERSON_AVATAR; 185 case TYPE_BUSINESS: 186 return DEFAULT_BUSINESS_AVATAR; 187 case TYPE_VOICEMAIL: 188 return DEFAULT_VOICEMAIL_AVATAR; 189 default: 190 return DEFAULT_PERSON_AVATAR; 191 } 192 } 193 194 private static boolean isEnglishLetter(final char c) { 195 return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); 196 } 197 198 @Override 199 public void setAlpha(final int alpha) { 200 mPaint.setAlpha(alpha); 201 } 202 203 @Override 204 public void setColorFilter(final ColorFilter cf) { 205 mPaint.setColorFilter(cf); 206 } 207 208 @Override 209 public int getOpacity() { 210 return android.graphics.PixelFormat.OPAQUE; 211 } 212 213 /** 214 * Scale the drawn letter tile to a ratio of its default size 215 * 216 * @param scale The ratio the letter tile should be scaled to as a percentage of its default 217 * size, from a scale of 0 to 2.0f. The default is 1.0f. 218 */ 219 public void setScale(float scale) { 220 mScale = scale; 221 } 222 223 /** 224 * Assigns the vertical offset of the position of the letter tile to the ContactDrawable 225 * 226 * @param offset The provided offset must be within the range of -0.5f to 0.5f. 227 * If set to -0.5f, the letter will be shifted upwards by 0.5 times the height of the canvas 228 * it is being drawn on, which means it will be drawn with the center of the letter starting 229 * at the top edge of the canvas. 230 * If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of the canvas 231 * it is being drawn on, which means it will be drawn with the center of the letter starting 232 * at the bottom edge of the canvas. 233 * The default is 0.0f. 234 */ 235 public void setOffset(float offset) { 236 Assert.assertTrue(offset >= -0.5f && offset <= 0.5f); 237 mOffset = offset; 238 } 239 240 public void setContactDetails(final String displayName, final String identifier) { 241 mDisplayName = displayName; 242 mIdentifier = identifier; 243 } 244 245 public void setContactType(int contactType) { 246 mContactType = contactType; 247 } 248 249 public void setIsCircular(boolean isCircle) { 250 mIsCircle = isCircle; 251 } 252} 253