1/*
2 * Copyright (C) 2015 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 */
16package com.android.messaging.ui;
17
18import android.content.Context;
19import android.util.AttributeSet;
20import android.view.Gravity;
21import android.view.View;
22import android.view.ViewGroup;
23import android.widget.FrameLayout;
24
25import com.android.messaging.util.OsUtil;
26import com.android.messaging.util.UiUtils;
27
28import java.util.ArrayList;
29
30/**
31* A line-wrapping flow layout. Arranges children in horizontal flow, packing as many
32* child views as possible on each line. When the current line does not
33* have enough horizontal space, the layout continues on the next line.
34*/
35public class LineWrapLayout extends ViewGroup {
36    public LineWrapLayout(Context context) {
37        this(context, null);
38    }
39
40    public LineWrapLayout(Context context, AttributeSet attrs) {
41        super(context, attrs);
42    }
43
44    @Override
45    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
46        final int startPadding = UiUtils.getPaddingStart(this);
47        final int endPadding = UiUtils.getPaddingEnd(this);
48        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
49        final int widthSize = MeasureSpec.getSize(widthMeasureSpec) - startPadding - endPadding;
50        final boolean isFixedSize = (widthMode == MeasureSpec.EXACTLY);
51
52        int height = 0;
53
54        int childCount = getChildCount();
55        int childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
56
57        int x = startPadding;
58        int currLineWidth = 0;
59        int currLineHeight = 0;
60        int maxLineWidth = 0;
61
62        for (int i = 0; i < childCount; i++) {
63            View currChild = getChildAt(i);
64            if (currChild.getVisibility() == GONE) {
65                continue;
66            }
67            LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
68            int startMargin = layoutParams.getStartMargin();
69            int endMargin = layoutParams.getEndMargin();
70            currChild.measure(childWidthSpec, MeasureSpec.UNSPECIFIED);
71            int childMeasuredWidth = currChild.getMeasuredWidth() + startMargin + endMargin;
72            int childMeasuredHeight = currChild.getMeasuredHeight() + layoutParams.topMargin +
73                    layoutParams.bottomMargin;
74
75            if ((x + childMeasuredWidth) > widthSize) {
76                // New line. Update the overall height and reset trackers.
77                height += currLineHeight;
78                currLineHeight = 0;
79                x = startPadding;
80                currLineWidth = 0;
81                startMargin = 0;
82            }
83
84            x += childMeasuredWidth;
85            currLineWidth += childMeasuredWidth;
86            currLineHeight = Math.max(currLineHeight, childMeasuredHeight);
87            maxLineWidth = Math.max(currLineWidth, maxLineWidth);
88        }
89        // And account for the height of the last line.
90        height += currLineHeight;
91
92        int width = isFixedSize ? widthSize : maxLineWidth;
93        setMeasuredDimension(width + startPadding + endPadding,
94                height + getPaddingTop() + getPaddingBottom());
95    }
96
97    @Override
98    protected void onLayout(boolean changed, int l, int t, int r, int b) {
99        final int startPadding = UiUtils.getPaddingStart(this);
100        final int endPadding = UiUtils.getPaddingEnd(this);
101        int width = getWidth() - startPadding - endPadding;
102        int y = getPaddingTop();
103        int x = startPadding;
104        int childCount = getChildCount();
105
106        int currLineHeight = 0;
107
108        // Do a dry-run first to get the line heights.
109        final ArrayList<Integer> lineHeights = new ArrayList<Integer>();
110        for (int i = 0; i < childCount; i++) {
111            View currChild = getChildAt(i);
112            if (currChild.getVisibility() == GONE) {
113                continue;
114            }
115            LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
116            int childWidth = currChild.getMeasuredWidth();
117            int childHeight = currChild.getMeasuredHeight();
118            int startMargin = layoutParams.getStartMargin();
119            int endMargin = layoutParams.getEndMargin();
120
121            if ((x + childWidth + startMargin + endMargin) > width) {
122                // new line
123                lineHeights.add(currLineHeight);
124                currLineHeight = 0;
125                x = startPadding;
126                startMargin = 0;
127            }
128            currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin +
129                    layoutParams.bottomMargin);
130            x += childWidth + startMargin + endMargin;
131        }
132        // Add the last line height.
133        lineHeights.add(currLineHeight);
134
135        // Now perform the actual layout.
136        x = startPadding;
137        currLineHeight = 0;
138        int lineIndex = 0;
139        for (int i = 0; i < childCount; i++) {
140            View currChild = getChildAt(i);
141            if (currChild.getVisibility() == GONE) {
142                continue;
143            }
144            LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
145            int childWidth = currChild.getMeasuredWidth();
146            int childHeight = currChild.getMeasuredHeight();
147            int startMargin = layoutParams.getStartMargin();
148            int endMargin = layoutParams.getEndMargin();
149
150            if ((x + childWidth + startMargin + endMargin) > width) {
151                // new line
152                y += currLineHeight;
153                currLineHeight = 0;
154                x = startPadding;
155                startMargin = 0;
156                lineIndex++;
157            }
158            final int startPositionX = x + startMargin;
159            int startPositionY = y + layoutParams.topMargin;    // default to top gravity
160            final int majorGravity = layoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK;
161            if (majorGravity != Gravity.TOP && lineHeights.size() > lineIndex) {
162                final int lineHeight = lineHeights.get(lineIndex);
163                switch (majorGravity) {
164                    case Gravity.BOTTOM:
165                        startPositionY = y + lineHeight - childHeight - layoutParams.bottomMargin;
166                        break;
167
168                    case Gravity.CENTER_VERTICAL:
169                        startPositionY = y + (lineHeight - childHeight) / 2;
170                        break;
171                }
172            }
173
174            if (OsUtil.isAtLeastJB_MR2() && getResources().getConfiguration()
175                    .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
176                currChild.layout(width - startPositionX - childWidth, startPositionY,
177                        width - startPositionX, startPositionY + childHeight);
178            } else {
179                currChild.layout(startPositionX, startPositionY, startPositionX + childWidth,
180                        startPositionY + childHeight);
181            }
182            currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin +
183                    layoutParams.bottomMargin);
184            x += childWidth + startMargin + endMargin;
185        }
186    }
187
188    @Override
189    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
190        return new LayoutParams(p);
191    }
192
193    @Override
194    public LayoutParams generateLayoutParams(AttributeSet attrs) {
195        return new LayoutParams(getContext(), attrs);
196    }
197
198    @Override
199    protected LayoutParams generateDefaultLayoutParams() {
200        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
201    }
202
203    public static final class LayoutParams extends FrameLayout.LayoutParams {
204        public LayoutParams(Context c, AttributeSet attrs) {
205            super(c, attrs);
206        }
207
208        public LayoutParams(int width, int height) {
209            super(width, height);
210        }
211
212        public LayoutParams(ViewGroup.LayoutParams source) {
213            super(source);
214        }
215
216        public int getStartMargin() {
217            if (OsUtil.isAtLeastJB_MR2()) {
218                return getMarginStart();
219            } else {
220                return leftMargin;
221            }
222        }
223
224        public int getEndMargin() {
225            if (OsUtil.isAtLeastJB_MR2()) {
226                return getMarginEnd();
227            } else {
228                return rightMargin;
229            }
230        }
231    }
232}
233