Home | History | Annotate | Download | only in activity
      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