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