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