MessageListItemCoordinates.java revision bc47398187c6ffd132435e51d8d61e6ec79a79db
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 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