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