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