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