blob: 521981171a58f8af7337424932edeacdf9c3a010 [file] [log] [blame]
/*
* 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<Integer> lineHeights = new ArrayList<Integer>();
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;
}
}
}
}