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  */
16 package com.android.messaging.ui;
17 
18 import android.content.Context;
19 import android.util.AttributeSet;
20 import android.view.Gravity;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.widget.FrameLayout;
24 
25 import com.android.messaging.util.OsUtil;
26 import com.android.messaging.util.UiUtils;
27 
28 import 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 */
35 public class LineWrapLayout extends ViewGroup {
LineWrapLayout(Context context)36     public LineWrapLayout(Context context) {
37         this(context, null);
38     }
39 
LineWrapLayout(Context context, AttributeSet attrs)40     public LineWrapLayout(Context context, AttributeSet attrs) {
41         super(context, attrs);
42     }
43 
44     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
onLayout(boolean changed, int l, int t, int r, int b)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
generateLayoutParams(ViewGroup.LayoutParams p)189     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
190         return new LayoutParams(p);
191     }
192 
193     @Override
generateLayoutParams(AttributeSet attrs)194     public LayoutParams generateLayoutParams(AttributeSet attrs) {
195         return new LayoutParams(getContext(), attrs);
196     }
197 
198     @Override
generateDefaultLayoutParams()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 {
LayoutParams(Context c, AttributeSet attrs)204         public LayoutParams(Context c, AttributeSet attrs) {
205             super(c, attrs);
206         }
207 
LayoutParams(int width, int height)208         public LayoutParams(int width, int height) {
209             super(width, height);
210         }
211 
LayoutParams(ViewGroup.LayoutParams source)212         public LayoutParams(ViewGroup.LayoutParams source) {
213             super(source);
214         }
215 
getStartMargin()216         public int getStartMargin() {
217             if (OsUtil.isAtLeastJB_MR2()) {
218                 return getMarginStart();
219             } else {
220                 return leftMargin;
221             }
222         }
223 
getEndMargin()224         public int getEndMargin() {
225             if (OsUtil.isAtLeastJB_MR2()) {
226                 return getMarginEnd();
227             } else {
228                 return rightMargin;
229             }
230         }
231     }
232 }
233