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