Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2016 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 package com.android.internal.widget;
     18 
     19 import android.annotation.Nullable;
     20 import android.content.Context;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Canvas;
     23 import android.util.AttributeSet;
     24 import android.view.RemotableViewMethod;
     25 import android.view.View;
     26 import android.view.ViewGroup;
     27 import android.widget.RemoteViews;
     28 
     29 import com.android.internal.R;
     30 
     31 /**
     32  * A custom-built layout for the Notification.MessagingStyle.
     33  *
     34  * Evicts children until they all fit.
     35  */
     36 @RemoteViews.RemoteView
     37 public class MessagingLinearLayout extends ViewGroup {
     38 
     39     private static final int NOT_MEASURED_BEFORE = -1;
     40     /**
     41      * Spacing to be applied between views.
     42      */
     43     private int mSpacing;
     44 
     45     /**
     46      * The maximum height allowed.
     47      */
     48     private int mMaxHeight;
     49 
     50     private int mIndentLines;
     51 
     52     /**
     53      * Id of the child that's also visible in the contracted layout.
     54      */
     55     private int mContractedChildId;
     56     /**
     57      * The last measured with in a layout pass if it was measured before or
     58      * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
     59      */
     60     private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
     61 
     62     public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
     63         super(context, attrs);
     64 
     65         final TypedArray a = context.obtainStyledAttributes(attrs,
     66                 R.styleable.MessagingLinearLayout, 0,
     67                 0);
     68 
     69         final int N = a.getIndexCount();
     70         for (int i = 0; i < N; i++) {
     71             int attr = a.getIndex(i);
     72             switch (attr) {
     73                 case R.styleable.MessagingLinearLayout_spacing:
     74                     mSpacing = a.getDimensionPixelSize(i, 0);
     75                     break;
     76             }
     77         }
     78 
     79         a.recycle();
     80     }
     81 
     82 
     83     @Override
     84     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     85         // This is essentially a bottom-up linear layout that only adds children that fit entirely
     86         // up to a maximum height.
     87         int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
     88         switch (MeasureSpec.getMode(heightMeasureSpec)) {
     89             case MeasureSpec.UNSPECIFIED:
     90                 targetHeight = Integer.MAX_VALUE;
     91                 break;
     92         }
     93         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
     94         boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
     95                 || getMeasuredHeight() != targetHeight
     96                 || mLastMeasuredWidth != widthSize;
     97 
     98         final int count = getChildCount();
     99         if (recalculateVisibility) {
    100             // We only need to recalculate the view visibilities if the view wasn't measured already
    101             // in this pass, otherwise we may drop messages here already since we are measured
    102             // exactly with what we returned before, which was optimized already with the
    103             // line-indents.
    104             for (int i = 0; i < count; ++i) {
    105                 final View child = getChildAt(i);
    106                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    107                 lp.hide = true;
    108             }
    109 
    110             int totalHeight = mPaddingTop + mPaddingBottom;
    111             boolean first = true;
    112 
    113             // Starting from the bottom: we measure every view as if it were the only one. If it still
    114 
    115             // fits, we take it, otherwise we stop there.
    116             for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
    117                 if (getChildAt(i).getVisibility() == GONE) {
    118                     continue;
    119                 }
    120                 final View child = getChildAt(i);
    121                 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
    122                 ImageFloatingTextView textChild = null;
    123                 if (child instanceof ImageFloatingTextView) {
    124                     // Pretend we need the image padding for all views, we don't know which
    125                     // one will end up needing to do this (might end up not using all the space,
    126                     // but calculating this exactly would be more expensive).
    127                     textChild = (ImageFloatingTextView) child;
    128                     textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
    129                 }
    130 
    131                 int spacing = first ? 0 : mSpacing;
    132                 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
    133                         - mPaddingTop - mPaddingBottom + spacing);
    134 
    135                 final int childHeight = child.getMeasuredHeight();
    136                 int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
    137                         lp.bottomMargin + spacing);
    138                 first = false;
    139                 boolean measuredTooSmall = false;
    140                 if (textChild != null) {
    141                     measuredTooSmall = childHeight < textChild.getLayoutHeight()
    142                             + textChild.getPaddingTop() + textChild.getPaddingBottom();
    143                 }
    144 
    145                 if (newHeight <= targetHeight && !measuredTooSmall) {
    146                     totalHeight = newHeight;
    147                     lp.hide = false;
    148                 } else {
    149                     break;
    150                 }
    151             }
    152         }
    153 
    154         // Now that we know which views to take, fix up the indents and see what width we get.
    155         int measuredWidth = mPaddingLeft + mPaddingRight;
    156         int imageLines = mIndentLines;
    157         // Need to redo the height because it may change due to changing indents.
    158         int totalHeight = mPaddingTop + mPaddingBottom;
    159         boolean first = true;
    160         for (int i = 0; i < count; i++) {
    161             final View child = getChildAt(i);
    162             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    163 
    164             if (child.getVisibility() == GONE || lp.hide) {
    165                 continue;
    166             }
    167 
    168             if (child instanceof ImageFloatingTextView) {
    169                 ImageFloatingTextView textChild = (ImageFloatingTextView) child;
    170                 if (imageLines == 2 && textChild.getLineCount() > 2) {
    171                     // HACK: If we need indent for two lines, and they're coming from the same
    172                     // view, we need extra spacing to compensate for the lack of margins,
    173                     // so add an extra line of indent.
    174                     imageLines = 3;
    175                 }
    176                 boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
    177                 if (changed || !recalculateVisibility) {
    178                     final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
    179                             mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
    180                             lp.width);
    181                     // we want to measure it at most as high as it is currently, otherwise we'll
    182                     // drop later lines
    183                     final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
    184                             targetHeight - child.getMeasuredHeight(), lp.height);
    185 
    186                     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
    187                 }
    188                 imageLines -= textChild.getLineCount();
    189             }
    190 
    191             measuredWidth = Math.max(measuredWidth,
    192                     child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
    193                             + mPaddingLeft + mPaddingRight);
    194             totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
    195                     lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
    196             first = false;
    197         }
    198 
    199 
    200         setMeasuredDimension(
    201                 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
    202                         widthMeasureSpec),
    203                 resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
    204                         heightMeasureSpec));
    205         mLastMeasuredWidth = widthSize;
    206     }
    207 
    208     @Override
    209     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    210         final int paddingLeft = mPaddingLeft;
    211 
    212         int childTop;
    213 
    214         // Where right end of child should go
    215         final int width = right - left;
    216         final int childRight = width - mPaddingRight;
    217 
    218         final int layoutDirection = getLayoutDirection();
    219         final int count = getChildCount();
    220 
    221         childTop = mPaddingTop;
    222 
    223         boolean first = true;
    224 
    225         for (int i = 0; i < count; i++) {
    226             final View child = getChildAt(i);
    227             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    228 
    229             if (child.getVisibility() == GONE || lp.hide) {
    230                 continue;
    231             }
    232 
    233             final int childWidth = child.getMeasuredWidth();
    234             final int childHeight = child.getMeasuredHeight();
    235 
    236             int childLeft;
    237             if (layoutDirection == LAYOUT_DIRECTION_RTL) {
    238                 childLeft = childRight - childWidth - lp.rightMargin;
    239             } else {
    240                 childLeft = paddingLeft + lp.leftMargin;
    241             }
    242 
    243             if (!first) {
    244                 childTop += mSpacing;
    245             }
    246 
    247             childTop += lp.topMargin;
    248             child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
    249 
    250             childTop += childHeight + lp.bottomMargin;
    251 
    252             first = false;
    253         }
    254         mLastMeasuredWidth = NOT_MEASURED_BEFORE;
    255     }
    256 
    257     @Override
    258     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    259         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    260         if (lp.hide) {
    261             return true;
    262         }
    263         return super.drawChild(canvas, child, drawingTime);
    264     }
    265 
    266     @Override
    267     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    268         return new LayoutParams(mContext, attrs);
    269     }
    270 
    271     @Override
    272     protected LayoutParams generateDefaultLayoutParams() {
    273         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    274 
    275     }
    276 
    277     @Override
    278     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
    279         LayoutParams copy = new LayoutParams(lp.width, lp.height);
    280         if (lp instanceof MarginLayoutParams) {
    281             copy.copyMarginsFrom((MarginLayoutParams) lp);
    282         }
    283         return copy;
    284     }
    285 
    286     /**
    287      * Sets how many lines should be indented to avoid a floating image.
    288      */
    289     @RemotableViewMethod
    290     public void setNumIndentLines(int numberLines) {
    291         mIndentLines = numberLines;
    292     }
    293 
    294     /**
    295      * Set id of the child that's also visible in the contracted layout.
    296      */
    297     @RemotableViewMethod
    298     public void setContractedChildId(int contractedChildId) {
    299         mContractedChildId = contractedChildId;
    300     }
    301 
    302     /**
    303      * Get id of the child that's also visible in the contracted layout.
    304      */
    305     public int getContractedChildId() {
    306         return mContractedChildId;
    307     }
    308 
    309     public static class LayoutParams extends MarginLayoutParams {
    310 
    311         boolean hide = false;
    312 
    313         public LayoutParams(Context c, AttributeSet attrs) {
    314             super(c, attrs);
    315         }
    316 
    317         public LayoutParams(int width, int height) {
    318             super(width, height);
    319         }
    320     }
    321 }
    322