1aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez/* 2aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * Copyright (C) 2015 The Android Open Source Project 3aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * 4aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * Licensed under the Apache License, Version 2.0 (the "License"); 5aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * you may not use this file except in compliance with the License. 6aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * You may obtain a copy of the License at 7aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * 8aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * http://www.apache.org/licenses/LICENSE-2.0 9aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * 10aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * Unless required by applicable law or agreed to in writing, software 11aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * distributed under the License is distributed on an "AS IS" BASIS, 12aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * See the License for the specific language governing permissions and 14aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * limitations under the License. 15aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez */ 16aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 17aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perezpackage com.android.tools.idea.editors.theme.widgets; 18aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 19aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perezimport android.content.Context; 20aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perezimport android.util.AttributeSet; 21aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perezimport android.util.DisplayMetrics; 22aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perezimport android.util.TypedValue; 23aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perezimport android.view.View; 24aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perezimport android.view.ViewGroup; 25aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 26aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez/** 27aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * Custom layout used in the theme editor to display the component preview. It arranges the child 28aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * Views as a grid of cards. 29aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * <p/> 30aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * The Views are measured and the maximum width and height are used to dimension all the child 31aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * components. Any margin attributes from the children are ignored and only the item_margin element 32aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez * is used. 33aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez */ 34aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez@SuppressWarnings("unused") 35aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perezpublic class ThemePreviewLayout extends ViewGroup { 36aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez private final int mMaxColumns; 37aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez private final int mMaxColumnWidth; 38aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez private final int mMinColumnWidth; 39aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez private final int mItemHorizontalMargin; 40aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez private final int mItemVerticalMargin; 41aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 42aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez /** Item width to use for every card component. This includes margins. */ 43aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez private int mItemWidth; 44aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez /** Item height to use for every card component. This includes margins. */ 45aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez private int mItemHeight; 46aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 47aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez /** Calculated number of columns */ 48aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez private int mNumColumns; 49aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 50aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez public ThemePreviewLayout(Context context) { 51aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez this(context, null); 52aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 53aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 54aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez public ThemePreviewLayout(Context context, AttributeSet attrs) { 55aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez this(context, attrs, 0); 56aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 57aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 58aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez public ThemePreviewLayout(Context context, AttributeSet attrs, int defStyleAttr) { 59aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez super(context, attrs, defStyleAttr); 60aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 61aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez if (attrs == null) { 62aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mMaxColumnWidth = Integer.MAX_VALUE; 63aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mMinColumnWidth = 0; 64aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mMaxColumns = Integer.MAX_VALUE; 65aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mItemHorizontalMargin = 0; 66aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mItemVerticalMargin = 0; 67aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez return; 68aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 69aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 70aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez DisplayMetrics dm = getResources().getDisplayMetrics(); 71aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int maxColumnWidth = attrs.getAttributeIntValue(null, "max_column_width", Integer 72aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez .MAX_VALUE); 73aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int minColumnWidth = attrs.getAttributeIntValue(null, "min_column_width", 0); 74aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int itemHorizontalMargin = attrs.getAttributeIntValue(null, "item_horizontal_margin", 0); 75aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int itemVerticalMargin = attrs.getAttributeIntValue(null, "item_vertical_margin", 0); 76aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 77aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mMaxColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 78aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez maxColumnWidth, 79aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez dm); 80aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mMinColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 81aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez minColumnWidth, 82aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez dm); 83aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mItemHorizontalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 84aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez itemHorizontalMargin, 85aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez dm); 86aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mItemVerticalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 87aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez itemVerticalMargin, 88aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez dm); 89aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mMaxColumns = attrs.getAttributeIntValue(null, "max_columns", Integer.MAX_VALUE); 90aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 91aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 92aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez @Override 93aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 94aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // Measure the column size. 95aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // The column has a minimum width that will be used to calculate the maximum number of 96aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // columns that we can fit in the available space. 97aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // 98aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // Once we have the maximum number of columns, we will span all columns width evenly to fill 99aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // all the available space. 100aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int wSize = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight; 101aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 102aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // Calculate the desired width of all columns and take the maximum. 103aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // This step can be skipped if we have a fixed column height so we do not have to 104aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // dynamically calculate it. 105aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 106aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 107aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int itemWidth = 0; 108aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int itemHeight = 0; 109aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez for (int i = 0; i < getChildCount(); i++) { 110aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez View v = getChildAt(i); 111aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 112aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez if (v.getVisibility() == GONE) { 113aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez continue; 114aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 115aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 116aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez measureChild(v, childWidthSpec, childHeightSpec); 117aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 118aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez itemWidth = Math.max(itemWidth, v.getMeasuredWidth()); 119aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez itemHeight = Math.max(itemHeight, v.getMeasuredHeight()); 120aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 121aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 122aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez itemWidth = Math.min(Math.max(itemWidth, mMinColumnWidth), mMaxColumnWidth); 123aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mNumColumns = Math.min((int) Math.ceil((double) wSize / itemWidth), mMaxColumns); 124aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 125aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // Check how much space this distribution would take taking into account the margins. 126aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // If it's bigger than what we have, remove one column. 127aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int wSizeNeeded = mNumColumns * itemWidth + (mNumColumns - 1) * mItemHorizontalMargin; 128aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez if (wSizeNeeded > wSize && mNumColumns > 1) { 129aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mNumColumns--; 130aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 131aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 132aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez if (getChildCount() < mNumColumns) { 133aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mNumColumns = getChildCount(); 134aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 135aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez if (mNumColumns == 0) { 136aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mNumColumns = 1; 137aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 138aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 139aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // Inform each child of the measurement 140aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez childWidthSpec = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY); 141aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez childHeightSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY); 142aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez for (int i = 0; i < getChildCount(); i++) { 143aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez View v = getChildAt(i); 144aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 145aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez if (v.getVisibility() == GONE) { 146aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez continue; 147aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 148aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 149aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez measureChild(v, childWidthSpec, childHeightSpec); 150aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 151aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 152aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // Calculate the height of the first column to measure our own size 153aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int firstColumnItems = getChildCount() / mNumColumns + ((getChildCount() % mNumColumns) > 0 154aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez ? 1 : 0); 155aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 156aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int horizontalMarginsTotalWidth = (mNumColumns - 1) * mItemHorizontalMargin; 157aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int verticalMarginsTotalHeight = (firstColumnItems - 1) * mItemVerticalMargin; 158aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int totalWidth = mNumColumns * itemWidth + horizontalMarginsTotalWidth + 159aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mPaddingRight + mPaddingLeft; 160aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int totalHeight = firstColumnItems * itemHeight + verticalMarginsTotalHeight + 161aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mPaddingBottom + mPaddingTop; 162aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 163aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez setMeasuredDimension(resolveSize(totalWidth, widthMeasureSpec), 164aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez resolveSize(totalHeight, heightMeasureSpec)); 165aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 166aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mItemWidth = itemWidth; 167aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez mItemHeight = itemHeight; 168aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 169aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 170aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez @Override 171aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez protected void onLayout(boolean changed, int l, int t, int r, int b) { 172aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int itemsPerColumn = getChildCount() / mNumColumns; 173aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // The remainder items are distributed one per column. 174aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int remainderItems = getChildCount() % mNumColumns; 175aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 176aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int x = mPaddingLeft; 177aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int y = mPaddingTop; 178aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez int position = 1; 179aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez for (int i = 0; i < getChildCount(); i++) { 180aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez View v = getChildAt(i); 181aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez v.layout(x, 182aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez y, 183aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez x + mItemWidth, 184aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez y + mItemHeight); 185aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 186aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez if (position == itemsPerColumn + (remainderItems > 0 ? 1 : 0)) { 187aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez // Break column 188aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez position = 1; 189aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez remainderItems--; 190aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez x += mItemWidth + mItemHorizontalMargin; 191aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez y = mPaddingTop; 192aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } else { 193aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez position++; 194aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez y += mItemHeight + mItemVerticalMargin; 195aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 196aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 197aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez } 198aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez} 199aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 200aa3b2043f0403d064afcf0977f6e713662aceaffDiego Perez 201