blob: af89910f7c96fe64d7fb559053feb37905ef35fe [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.tools.idea.editors.theme.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
/**
* Custom layout used in the theme editor to display the component preview. It arranges the child
* Views as a grid of cards.
* <p/>
* The Views are measured and the maximum width and height are used to dimension all the child
* components. Any margin attributes from the children are ignored and only the item_margin element
* is used.
*/
@SuppressWarnings("unused")
public class ThemePreviewLayout extends ViewGroup {
private final int mMaxColumns;
private final int mMaxColumnWidth;
private final int mMinColumnWidth;
private final int mItemHorizontalMargin;
private final int mItemVerticalMargin;
/** Item width to use for every card component. This includes margins. */
private int mItemWidth;
/** Item height to use for every card component. This includes margins. */
private int mItemHeight;
/** Calculated number of columns */
private int mNumColumns;
public ThemePreviewLayout(Context context) {
this(context, null);
}
public ThemePreviewLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ThemePreviewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (attrs == null) {
mMaxColumnWidth = Integer.MAX_VALUE;
mMinColumnWidth = 0;
mMaxColumns = Integer.MAX_VALUE;
mItemHorizontalMargin = 0;
mItemVerticalMargin = 0;
return;
}
DisplayMetrics dm = getResources().getDisplayMetrics();
int maxColumnWidth = attrs.getAttributeIntValue(null, "max_column_width", Integer
.MAX_VALUE);
int minColumnWidth = attrs.getAttributeIntValue(null, "min_column_width", 0);
int itemHorizontalMargin = attrs.getAttributeIntValue(null, "item_horizontal_margin", 0);
int itemVerticalMargin = attrs.getAttributeIntValue(null, "item_vertical_margin", 0);
mMaxColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
maxColumnWidth,
dm);
mMinColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
minColumnWidth,
dm);
mItemHorizontalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
itemHorizontalMargin,
dm);
mItemVerticalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
itemVerticalMargin,
dm);
mMaxColumns = attrs.getAttributeIntValue(null, "max_columns", Integer.MAX_VALUE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure the column size.
// The column has a minimum width that will be used to calculate the maximum number of
// columns that we can fit in the available space.
//
// Once we have the maximum number of columns, we will span all columns width evenly to fill
// all the available space.
int wSize = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
// Calculate the desired width of all columns and take the maximum.
// This step can be skipped if we have a fixed column height so we do not have to
// dynamically calculate it.
int childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int itemWidth = 0;
int itemHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
if (v.getVisibility() == GONE) {
continue;
}
measureChild(v, childWidthSpec, childHeightSpec);
itemWidth = Math.max(itemWidth, v.getMeasuredWidth());
itemHeight = Math.max(itemHeight, v.getMeasuredHeight());
}
itemWidth = Math.min(Math.max(itemWidth, mMinColumnWidth), mMaxColumnWidth);
mNumColumns = Math.min((int) Math.ceil((double) wSize / itemWidth), mMaxColumns);
// Check how much space this distribution would take taking into account the margins.
// If it's bigger than what we have, remove one column.
int wSizeNeeded = mNumColumns * itemWidth + (mNumColumns - 1) * mItemHorizontalMargin;
if (wSizeNeeded > wSize && mNumColumns > 1) {
mNumColumns--;
}
if (getChildCount() < mNumColumns) {
mNumColumns = getChildCount();
}
if (mNumColumns == 0) {
mNumColumns = 1;
}
// Inform each child of the measurement
childWidthSpec = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY);
childHeightSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY);
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
if (v.getVisibility() == GONE) {
continue;
}
measureChild(v, childWidthSpec, childHeightSpec);
}
// Calculate the height of the first column to measure our own size
int firstColumnItems = getChildCount() / mNumColumns + ((getChildCount() % mNumColumns) > 0
? 1 : 0);
int horizontalMarginsTotalWidth = (mNumColumns - 1) * mItemHorizontalMargin;
int verticalMarginsTotalHeight = (firstColumnItems - 1) * mItemVerticalMargin;
int totalWidth = mNumColumns * itemWidth + horizontalMarginsTotalWidth +
mPaddingRight + mPaddingLeft;
int totalHeight = firstColumnItems * itemHeight + verticalMarginsTotalHeight +
mPaddingBottom + mPaddingTop;
setMeasuredDimension(resolveSize(totalWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec));
mItemWidth = itemWidth;
mItemHeight = itemHeight;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int itemsPerColumn = getChildCount() / mNumColumns;
// The remainder items are distributed one per column.
int remainderItems = getChildCount() % mNumColumns;
int x = mPaddingLeft;
int y = mPaddingTop;
int position = 1;
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
v.layout(x,
y,
x + mItemWidth,
y + mItemHeight);
if (position == itemsPerColumn + (remainderItems > 0 ? 1 : 0)) {
// Break column
position = 1;
remainderItems--;
x += mItemWidth + mItemHorizontalMargin;
y = mPaddingTop;
} else {
position++;
y += mItemHeight + mItemVerticalMargin;
}
}
}
}