/* * 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; } } } }