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