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