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 18package com.android.email.activity; 19 20import android.content.Context; 21import android.content.res.Resources; 22import android.graphics.Typeface; 23import android.text.TextPaint; 24import android.util.SparseArray; 25import android.view.LayoutInflater; 26import android.view.View; 27import android.view.View.MeasureSpec; 28import android.view.ViewGroup; 29import android.view.ViewParent; 30import android.widget.TextView; 31 32import 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 */ 41public 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 public static boolean isMultiPane(Context context) { 127 return UiUtilities.useTwoPane(context); 128 } 129 130 /** 131 * Returns the layout id to be inflated in this mode. 132 */ 133 private static int getLayoutId(int mode) { 134 switch (mode) { 135 case WIDE_MODE: 136 return R.layout.message_list_item_wide; 137 case NORMAL_MODE: 138 return R.layout.message_list_item_normal; 139 default: 140 throw new IllegalArgumentException("Unknown conversation header view mode " + mode); 141 } 142 } 143 144 /** 145 * Returns a value array multiplied by the specified density. 146 */ 147 public static int[] getDensityDependentArray(int[] values, float density) { 148 int result[] = new int[values.length]; 149 for (int i = 0; i < values.length; ++i) { 150 result[i] = (int) (values[i] * density); 151 } 152 return result; 153 } 154 155 /** 156 * Returns the height of the view in this mode. 157 */ 158 public static int getHeight(Context context, int mode) { 159 return context.getResources().getDimensionPixelSize( 160 (mode == WIDE_MODE) 161 ? R.dimen.message_list_item_height_wide 162 : R.dimen.message_list_item_height_normal); 163 } 164 165 /** 166 * Returns the x coordinates of a view by tracing up its hierarchy. 167 */ 168 private static int getX(View view) { 169 int x = 0; 170 while (view != null) { 171 x += (int) view.getX(); 172 ViewParent parent = view.getParent(); 173 view = parent != null ? (View) parent : null; 174 } 175 return x; 176 } 177 178 /** 179 * Returns the y coordinates of a view by tracing up its hierarchy. 180 */ 181 private static int getY(View view) { 182 int y = 0; 183 while (view != null) { 184 y += (int) view.getY(); 185 ViewParent parent = view.getParent(); 186 view = parent != null ? (View) parent : null; 187 } 188 return y; 189 } 190 191 /** 192 * Returns the width of a view. 193 * 194 * @param includeMargins whether or not to include margins when calculating 195 * width. 196 */ 197 public static int getWidth(View view, boolean includeMargins) { 198 ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 199 return view.getWidth() + (includeMargins ? params.leftMargin + params.rightMargin : 0); 200 } 201 202 /** 203 * Returns the height of a view. 204 * 205 * @param includeMargins whether or not to include margins when calculating 206 * height. 207 */ 208 public static int getHeight(View view, boolean includeMargins) { 209 ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 210 return view.getHeight() + (includeMargins ? params.topMargin + params.bottomMargin : 0); 211 } 212 213 /** 214 * Returns the number of lines of this text view. 215 */ 216 private static int getLineCount(TextView textView) { 217 return textView.getHeight() / textView.getLineHeight(); 218 } 219 220 /** 221 * Returns the length (maximum of characters) of subject in this mode. 222 */ 223 public static int getSubjectLength(Context context, int mode) { 224 Resources res = context.getResources(); 225 if (SUBJECT_LENGTHS == null) { 226 SUBJECT_LENGTHS = res.getIntArray(R.array.subject_lengths); 227 } 228 return SUBJECT_LENGTHS[mode]; 229 } 230 231 /** 232 * Reset the caches associated with the coordinate layouts. 233 */ 234 static void resetCaches() { 235 mCache.clear(); 236 } 237 238 /** 239 * Returns coordinates for elements inside a conversation header view given 240 * the view width. 241 */ 242 public static MessageListItemCoordinates forWidth(Context context, int width) { 243 MessageListItemCoordinates coordinates = mCache.get(width); 244 if (coordinates == null) { 245 coordinates = new MessageListItemCoordinates(); 246 mCache.put(width, coordinates); 247 // TODO: make the field computation done inside of the constructor and mark fields final 248 249 // Layout the appropriate view. 250 int mode = getMode(context, width); 251 int height = getHeight(context, mode); 252 View view = LayoutInflater.from(context).inflate(getLayoutId(mode), null); 253 int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 254 int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 255 view.measure(widthSpec, heightSpec); 256 view.layout(0, 0, width, height); 257 258 // Records coordinates. 259 View checkmark = view.findViewById(R.id.checkmark); 260 coordinates.checkmarkX = getX(checkmark); 261 coordinates.checkmarkY = getY(checkmark); 262 coordinates.checkmarkWidthIncludingMargins = getWidth(checkmark, true); 263 264 View star = view.findViewById(R.id.star); 265 coordinates.starX = getX(star); 266 coordinates.starY = getY(star); 267 268 View state = view.findViewById(R.id.reply_state); 269 coordinates.stateX = getX(state); 270 coordinates.stateY = getY(state); 271 272 TextView senders = (TextView) view.findViewById(R.id.senders); 273 coordinates.sendersX = getX(senders); 274 coordinates.sendersY = getY(senders); 275 coordinates.sendersWidth = getWidth(senders, false); 276 coordinates.sendersLineCount = getLineCount(senders); 277 coordinates.sendersFontSize = (int) senders.getTextSize(); 278 coordinates.sendersAscent = Math.round(senders.getPaint().ascent()); 279 280 TextView subject = (TextView) view.findViewById(R.id.subject); 281 coordinates.subjectX = getX(subject); 282 coordinates.subjectY = getY(subject); 283 coordinates.subjectWidth = getWidth(subject, false); 284 coordinates.subjectLineCount = getLineCount(subject); 285 coordinates.subjectFontSize = (int) subject.getTextSize(); 286 coordinates.subjectAscent = Math.round(subject.getPaint().ascent()); 287 288 View chip = view.findViewById(R.id.color_chip); 289 coordinates.chipX = getX(chip); 290 coordinates.chipY = getY(chip); 291 coordinates.chipWidth = getWidth(chip, false); 292 coordinates.chipHeight = getHeight(chip, false); 293 294 TextView date = (TextView) view.findViewById(R.id.date); 295 coordinates.dateXEnd = getX(date) + date.getWidth(); 296 coordinates.dateY = getY(date); 297 coordinates.dateFontSize = (int) date.getTextSize(); 298 coordinates.dateAscent = Math.round(date.getPaint().ascent()); 299 300 // The x-value is computed relative to the date. 301 View paperclip = view.findViewById(R.id.paperclip); 302 coordinates.paperclipY = getY(paperclip); 303 } 304 return coordinates; 305 } 306} 307