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