• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
MessageListItemCoordinates()107     private MessageListItemCoordinates() {}
108 
109     /**
110      * Returns the mode of the header view (Wide/Normal/Narrow) given the its
111      * measured width.
112      */
getMode(Context context, int width, boolean isSearch)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 
isMultiPane(Context context)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      */
getLayoutId(int mode)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      */
getDensityDependentArray(int[] values, float density)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      */
getHeight(Context context, int mode)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      */
getX(View view)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      */
getY(View view)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      */
getWidth(View view, boolean includeMargins)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      */
getHeight(View view, boolean includeMargins)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      */
getLineCount(TextView textView)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      */
getSubjectLength(Context context, int mode)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      */
resetCaches()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      */
forWidth(Context context, int width, boolean isSearchResult)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