blob: dc7b7f5b964634842bdd016b2d11a2f6f27ab036 [file] [log] [blame]
Adrian Roosc1a80b02016-04-05 14:54:55 -07001/*
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}