1 /* 2 * Copyright (C) 2011 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 17 18 package com.android.email.activity; 19 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.graphics.Typeface; 23 import android.text.TextPaint; 24 import android.util.SparseArray; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.View.MeasureSpec; 28 import android.view.ViewGroup; 29 import android.view.ViewParent; 30 import android.widget.TextView; 31 32 import com.android.email.R; 33 34 /** 35 * Represents the coordinates of elements inside a CanvasConversationHeaderView 36 * (eg, checkmark, star, subject, sender, labels, etc.) It will inflate a view, 37 * and record the coordinates of each element after layout. This will allows us 38 * to easily improve performance by creating custom view while still defining 39 * layout in XML files. 40 */ 41 public class MessageListItemCoordinates { 42 // Modes. 43 public static final int WIDE_MODE = 0; 44 public static final int NORMAL_MODE = 1; 45 46 // Static threshold. 47 private static int MINIMUM_WIDTH_WIDE_MODE = -1; 48 private static int[] SUBJECT_LENGTHS; 49 50 // Checkmark. 51 int checkmarkX; 52 int checkmarkY; 53 int checkmarkWidthIncludingMargins; 54 55 // Reply and forward state. 56 int stateX; 57 int stateY; 58 59 // Star. 60 int starX; 61 int starY; 62 63 // Senders. 64 int sendersX; 65 int sendersY; 66 int sendersWidth; 67 int sendersLineCount; 68 int sendersFontSize; 69 int sendersAscent; 70 71 // Subject. 72 int subjectX; 73 int subjectY; 74 int subjectWidth; 75 int subjectLineCount; 76 int subjectFontSize; 77 int subjectAscent; 78 79 // Color chip. 80 int chipX; 81 int chipY; 82 int chipWidth; 83 int chipHeight; 84 85 // Date. 86 int dateXEnd; 87 int dateY; 88 int dateFontSize; 89 int dateAscent; 90 91 // Paperclip. 92 int paperclipY; 93 94 // Cache to save Coordinates based on view width. 95 private static SparseArray<MessageListItemCoordinates> mCache = 96 new SparseArray<MessageListItemCoordinates>(); 97 98 private static TextPaint sPaint = new TextPaint(); 99 100 static { 101 sPaint.setTypeface(Typeface.DEFAULT); 102 sPaint.setAntiAlias(true); 103 } 104 105 // Not directly instantiable. 106 private MessageListItemCoordinates() {} 107 108 /** 109 * Returns the mode of the header view (Wide/Normal/Narrow) given the its 110 * measured width. 111 */ 112 public static int getMode(Context context, int width) { 113 Resources res = context.getResources(); 114 if (MINIMUM_WIDTH_WIDE_MODE <= 0) { 115 MINIMUM_WIDTH_WIDE_MODE = res.getDimensionPixelSize(R.dimen.minimum_width_wide_mode); 116 } 117 118 // Choose the correct mode based on view width. 119 int mode = NORMAL_MODE; 120 if (width > MINIMUM_WIDTH_WIDE_MODE) { 121 mode = WIDE_MODE; 122 } 123 return mode; 124 } 125 126 /** 127 * Returns the layout id to be inflated in this mode. 128 */ 129 private static int getLayoutId(int mode) { 130 switch (mode) { 131 case WIDE_MODE: 132 return R.layout.message_list_item_wide; 133 case NORMAL_MODE: 134 return R.layout.message_list_item_normal; 135 default: 136 throw new IllegalArgumentException("Unknown conversation header view mode " + mode); 137 } 138 } 139 140 /** 141 * Returns a value array multiplied by the specified density. 142 */ 143 public static int[] getDensityDependentArray(int[] values, float density) { 144 int result[] = new int[values.length]; 145 for (int i = 0; i < values.length; ++i) { 146 result[i] = (int) (values[i] * density); 147 } 148 return result; 149 } 150 151 /** 152 * Returns the height of the view in this mode. 153 */ 154 public static int getHeight(Context context, int mode) { 155 return context.getResources().getDimensionPixelSize( 156 (mode == WIDE_MODE) 157 ? R.dimen.message_list_item_height_wide 158 : R.dimen.message_list_item_height_normal); 159 } 160 161 /** 162 * Returns the x coordinates of a view by tracing up its hierarchy. 163 */ 164 private static int getX(View view) { 165 int x = 0; 166 while (view != null) { 167 x += (int) view.getX(); 168 ViewParent parent = view.getParent(); 169 view = parent != null ? (View) parent : null; 170 } 171 return x; 172 } 173 174 /** 175 * Returns the y coordinates of a view by tracing up its hierarchy. 176 */ 177 private static int getY(View view) { 178 int y = 0; 179 while (view != null) { 180 y += (int) view.getY(); 181 ViewParent parent = view.getParent(); 182 view = parent != null ? (View) parent : null; 183 } 184 return y; 185 } 186 187 /** 188 * Returns the width of a view. 189 * 190 * @param includeMargins whether or not to include margins when calculating 191 * width. 192 */ 193 public static int getWidth(View view, boolean includeMargins) { 194 ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 195 return view.getWidth() + (includeMargins ? params.leftMargin + params.rightMargin : 0); 196 } 197 198 /** 199 * Returns the height of a view. 200 * 201 * @param includeMargins whether or not to include margins when calculating 202 * height. 203 */ 204 public static int getHeight(View view, boolean includeMargins) { 205 ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 206 return view.getHeight() + (includeMargins ? params.topMargin + params.bottomMargin : 0); 207 } 208 209 /** 210 * Returns the number of lines of this text view. 211 */ 212 private static int getLineCount(TextView textView) { 213 return textView.getHeight() / textView.getLineHeight(); 214 } 215 216 /** 217 * Returns the length (maximum of characters) of subject in this mode. 218 */ 219 public static int getSubjectLength(Context context, int mode) { 220 Resources res = context.getResources(); 221 if (SUBJECT_LENGTHS == null) { 222 SUBJECT_LENGTHS = res.getIntArray(R.array.subject_lengths); 223 } 224 return SUBJECT_LENGTHS[mode]; 225 } 226 227 /** 228 * Returns coordinates for elements inside a conversation header view given 229 * the view width. 230 */ 231 public static MessageListItemCoordinates forWidth(Context context, int width) { 232 MessageListItemCoordinates coordinates = mCache.get(width); 233 if (coordinates == null) { 234 coordinates = new MessageListItemCoordinates(); 235 mCache.put(width, coordinates); 236 // TODO: make the field computation done inside of the constructor and mark fields final 237 238 // Layout the appropriate view. 239 int mode = getMode(context, width); 240 int height = getHeight(context, mode); 241 View view = LayoutInflater.from(context).inflate(getLayoutId(mode), null); 242 int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 243 int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 244 view.measure(widthSpec, heightSpec); 245 view.layout(0, 0, width, height); 246 247 // Records coordinates. 248 View checkmark = view.findViewById(R.id.checkmark); 249 coordinates.checkmarkX = getX(checkmark); 250 coordinates.checkmarkY = getY(checkmark); 251 coordinates.checkmarkWidthIncludingMargins = getWidth(checkmark, true); 252 253 View star = view.findViewById(R.id.star); 254 coordinates.starX = getX(star); 255 coordinates.starY = getY(star); 256 257 View state = view.findViewById(R.id.reply_state); 258 coordinates.stateX = getX(state); 259 coordinates.stateY = getY(state); 260 261 TextView senders = (TextView) view.findViewById(R.id.senders); 262 coordinates.sendersX = getX(senders); 263 coordinates.sendersY = getY(senders); 264 coordinates.sendersWidth = getWidth(senders, false); 265 coordinates.sendersLineCount = getLineCount(senders); 266 coordinates.sendersFontSize = (int) senders.getTextSize(); 267 coordinates.sendersAscent = Math.round(senders.getPaint().ascent()); 268 269 TextView subject = (TextView) view.findViewById(R.id.subject); 270 coordinates.subjectX = getX(subject); 271 coordinates.subjectY = getY(subject); 272 coordinates.subjectWidth = getWidth(subject, false); 273 coordinates.subjectLineCount = getLineCount(subject); 274 coordinates.subjectFontSize = (int) subject.getTextSize(); 275 coordinates.subjectAscent = Math.round(subject.getPaint().ascent()); 276 277 View chip = view.findViewById(R.id.color_chip); 278 coordinates.chipX = getX(chip); 279 coordinates.chipY = getY(chip); 280 coordinates.chipWidth = getWidth(chip, false); 281 coordinates.chipHeight = getHeight(chip, false); 282 283 TextView date = (TextView) view.findViewById(R.id.date); 284 coordinates.dateXEnd = getX(date) + date.getWidth(); 285 coordinates.dateY = getY(date); 286 coordinates.dateFontSize = (int) date.getTextSize(); 287 coordinates.dateAscent = Math.round(date.getPaint().ascent()); 288 289 // The x-value is computed relative to the date. 290 View paperclip = view.findViewById(R.id.paperclip); 291 coordinates.paperclipY = getY(paperclip); 292 } 293 return coordinates; 294 } 295 } 296