/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.messaging.ui; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import com.android.messaging.util.OsUtil; import com.android.messaging.util.UiUtils; import java.util.ArrayList; /** * A line-wrapping flow layout. Arranges children in horizontal flow, packing as many * child views as possible on each line. When the current line does not * have enough horizontal space, the layout continues on the next line. */ public class LineWrapLayout extends ViewGroup { public LineWrapLayout(Context context) { this(context, null); } public LineWrapLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int startPadding = UiUtils.getPaddingStart(this); final int endPadding = UiUtils.getPaddingEnd(this); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int widthSize = MeasureSpec.getSize(widthMeasureSpec) - startPadding - endPadding; final boolean isFixedSize = (widthMode == MeasureSpec.EXACTLY); int height = 0; int childCount = getChildCount(); int childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST); int x = startPadding; int currLineWidth = 0; int currLineHeight = 0; int maxLineWidth = 0; for (int i = 0; i < childCount; i++) { View currChild = getChildAt(i); if (currChild.getVisibility() == GONE) { continue; } LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams(); int startMargin = layoutParams.getStartMargin(); int endMargin = layoutParams.getEndMargin(); currChild.measure(childWidthSpec, MeasureSpec.UNSPECIFIED); int childMeasuredWidth = currChild.getMeasuredWidth() + startMargin + endMargin; int childMeasuredHeight = currChild.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin; if ((x + childMeasuredWidth) > widthSize) { // New line. Update the overall height and reset trackers. height += currLineHeight; currLineHeight = 0; x = startPadding; currLineWidth = 0; startMargin = 0; } x += childMeasuredWidth; currLineWidth += childMeasuredWidth; currLineHeight = Math.max(currLineHeight, childMeasuredHeight); maxLineWidth = Math.max(currLineWidth, maxLineWidth); } // And account for the height of the last line. height += currLineHeight; int width = isFixedSize ? widthSize : maxLineWidth; setMeasuredDimension(width + startPadding + endPadding, height + getPaddingTop() + getPaddingBottom()); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int startPadding = UiUtils.getPaddingStart(this); final int endPadding = UiUtils.getPaddingEnd(this); int width = getWidth() - startPadding - endPadding; int y = getPaddingTop(); int x = startPadding; int childCount = getChildCount(); int currLineHeight = 0; // Do a dry-run first to get the line heights. final ArrayList lineHeights = new ArrayList(); for (int i = 0; i < childCount; i++) { View currChild = getChildAt(i); if (currChild.getVisibility() == GONE) { continue; } LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams(); int childWidth = currChild.getMeasuredWidth(); int childHeight = currChild.getMeasuredHeight(); int startMargin = layoutParams.getStartMargin(); int endMargin = layoutParams.getEndMargin(); if ((x + childWidth + startMargin + endMargin) > width) { // new line lineHeights.add(currLineHeight); currLineHeight = 0; x = startPadding; startMargin = 0; } currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin + layoutParams.bottomMargin); x += childWidth + startMargin + endMargin; } // Add the last line height. lineHeights.add(currLineHeight); // Now perform the actual layout. x = startPadding; currLineHeight = 0; int lineIndex = 0; for (int i = 0; i < childCount; i++) { View currChild = getChildAt(i); if (currChild.getVisibility() == GONE) { continue; } LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams(); int childWidth = currChild.getMeasuredWidth(); int childHeight = currChild.getMeasuredHeight(); int startMargin = layoutParams.getStartMargin(); int endMargin = layoutParams.getEndMargin(); if ((x + childWidth + startMargin + endMargin) > width) { // new line y += currLineHeight; currLineHeight = 0; x = startPadding; startMargin = 0; lineIndex++; } final int startPositionX = x + startMargin; int startPositionY = y + layoutParams.topMargin; // default to top gravity final int majorGravity = layoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK; if (majorGravity != Gravity.TOP && lineHeights.size() > lineIndex) { final int lineHeight = lineHeights.get(lineIndex); switch (majorGravity) { case Gravity.BOTTOM: startPositionY = y + lineHeight - childHeight - layoutParams.bottomMargin; break; case Gravity.CENTER_VERTICAL: startPositionY = y + (lineHeight - childHeight) / 2; break; } } if (OsUtil.isAtLeastJB_MR2() && getResources().getConfiguration() .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { currChild.layout(width - startPositionX - childWidth, startPositionY, width - startPositionX, startPositionY + childHeight); } else { currChild.layout(startPositionX, startPositionY, startPositionX + childWidth, startPositionY + childHeight); } currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin + layoutParams.bottomMargin); x += childWidth + startMargin + endMargin; } } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } public static final class LayoutParams extends FrameLayout.LayoutParams { public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public int getStartMargin() { if (OsUtil.isAtLeastJB_MR2()) { return getMarginStart(); } else { return leftMargin; } } public int getEndMargin() { if (OsUtil.isAtLeastJB_MR2()) { return getMarginEnd(); } else { return rightMargin; } } } }