• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.usbtuner.layout;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Rect;
22 import android.util.AttributeSet;
23 import android.util.Log;
24 import android.view.View;
25 import android.view.ViewGroup;
26 
27 import com.android.usbtuner.R;
28 
29 import java.util.Arrays;
30 import java.util.Comparator;
31 
32 /**
33  * A layout that scales its children using the given percentage value.
34  */
35 public class ScaledLayout extends ViewGroup {
36     private static final String TAG = "ScaledLayout";
37     private static final boolean DEBUG = false;
38     private static final Comparator<Rect> mRectTopLeftSorter = new Comparator<Rect>() {
39         @Override
40         public int compare(Rect lhs, Rect rhs) {
41             if (lhs.top != rhs.top) {
42                 return lhs.top - rhs.top;
43             } else {
44                 return lhs.left - rhs.left;
45             }
46         }
47     };
48 
49     private Rect[] mRectArray;
50 
ScaledLayout(Context context)51     public ScaledLayout(Context context) {
52         this(context, null);
53     }
54 
ScaledLayout(Context context, AttributeSet attrs)55     public ScaledLayout(Context context, AttributeSet attrs) {
56         this(context, attrs, 0);
57     }
58 
ScaledLayout(Context context, AttributeSet attrs, int defStyle)59     public ScaledLayout(Context context, AttributeSet attrs, int defStyle) {
60         super(context, attrs, defStyle);
61     }
62 
63     /**
64      * ScaledLayoutParams stores the four scale factors.
65      * <br>
66      * Vertical coordinate system:   ({@code scaleStartRow} * 100) % ~ ({@code scaleEndRow} * 100) %
67      * Horizontal coordinate system: ({@code scaleStartCol} * 100) % ~ ({@code scaleEndCol} * 100) %
68      * <br>
69      * In XML, for example,
70      * <pre>
71      * {@code
72      * <View
73      *     app:layout_scaleStartRow="0.1"
74      *     app:layout_scaleEndRow="0.5"
75      *     app:layout_scaleStartCol="0.4"
76      *     app:layout_scaleEndCol="1" />
77      * }
78      * </pre>
79      */
80     public static class ScaledLayoutParams extends ViewGroup.LayoutParams {
81         public static final float SCALE_UNSPECIFIED = -1;
82         public float scaleStartRow;
83         public float scaleEndRow;
84         public float scaleStartCol;
85         public float scaleEndCol;
86 
ScaledLayoutParams(float scaleStartRow, float scaleEndRow, float scaleStartCol, float scaleEndCol)87         public ScaledLayoutParams(float scaleStartRow, float scaleEndRow,
88                 float scaleStartCol, float scaleEndCol) {
89             super(MATCH_PARENT, MATCH_PARENT);
90             this.scaleStartRow = scaleStartRow;
91             this.scaleEndRow = scaleEndRow;
92             this.scaleStartCol = scaleStartCol;
93             this.scaleEndCol = scaleEndCol;
94         }
95 
ScaledLayoutParams(Context context, AttributeSet attrs)96         public ScaledLayoutParams(Context context, AttributeSet attrs) {
97             super(MATCH_PARENT, MATCH_PARENT);
98             TypedArray array =
99                 context.obtainStyledAttributes(attrs, R.styleable.utScaledLayout);
100             scaleStartRow =
101                 array.getFloat(R.styleable.utScaledLayout_layout_scaleStartRow, SCALE_UNSPECIFIED);
102             scaleEndRow =
103                 array.getFloat(R.styleable.utScaledLayout_layout_scaleEndRow, SCALE_UNSPECIFIED);
104             scaleStartCol =
105                 array.getFloat(R.styleable.utScaledLayout_layout_scaleStartCol, SCALE_UNSPECIFIED);
106             scaleEndCol =
107                 array.getFloat(R.styleable.utScaledLayout_layout_scaleEndCol, SCALE_UNSPECIFIED);
108             array.recycle();
109         }
110     }
111 
112     @Override
generateLayoutParams(AttributeSet attrs)113     public LayoutParams generateLayoutParams(AttributeSet attrs) {
114         return new ScaledLayoutParams(getContext(), attrs);
115     }
116 
117     @Override
checkLayoutParams(LayoutParams p)118     protected boolean checkLayoutParams(LayoutParams p) {
119         return (p instanceof ScaledLayoutParams);
120     }
121 
122     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)123     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
124         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
125         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
126         int width = widthSpecSize - getPaddingLeft() - getPaddingRight();
127         int height = heightSpecSize - getPaddingTop() - getPaddingBottom();
128         if (DEBUG) {
129             Log.d(TAG, String.format("onMeasure width: %d, height: %d", width, height));
130         }
131         int count = getChildCount();
132         mRectArray = new Rect[count];
133         for (int i = 0; i < count; ++i) {
134             View child = getChildAt(i);
135             ViewGroup.LayoutParams params = child.getLayoutParams();
136             float scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol;
137             if (!(params instanceof ScaledLayoutParams)) {
138                 throw new RuntimeException(
139                         "A child of ScaledLayout cannot have the UNSPECIFIED scale factors");
140             }
141             scaleStartRow = ((ScaledLayoutParams) params).scaleStartRow;
142             scaleEndRow = ((ScaledLayoutParams) params).scaleEndRow;
143             scaleStartCol = ((ScaledLayoutParams) params).scaleStartCol;
144             scaleEndCol = ((ScaledLayoutParams) params).scaleEndCol;
145             if (scaleStartRow < 0 || scaleStartRow > 1) {
146                 throw new RuntimeException("A child of ScaledLayout should have a range of "
147                         + "scaleStartRow between 0 and 1");
148             }
149             if (scaleEndRow < scaleStartRow || scaleStartRow > 1) {
150                 throw new RuntimeException("A child of ScaledLayout should have a range of "
151                         + "scaleEndRow between scaleStartRow and 1");
152             }
153             if (scaleEndCol < 0 || scaleEndCol > 1) {
154                 throw new RuntimeException("A child of ScaledLayout should have a range of "
155                         + "scaleStartCol between 0 and 1");
156             }
157             if (scaleEndCol < scaleStartCol || scaleEndCol > 1) {
158                 throw new RuntimeException("A child of ScaledLayout should have a range of "
159                         + "scaleEndCol between scaleStartCol and 1");
160             }
161             if (DEBUG) {
162                 Log.d(TAG, String.format("onMeasure child scaleStartRow: %f scaleEndRow: %f "
163                         + "scaleStartCol: %f scaleEndCol: %f",
164                         scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol));
165             }
166             mRectArray[i] = new Rect((int) (scaleStartCol * width), (int) (scaleStartRow * height),
167                     (int) (scaleEndCol * width), (int) (scaleEndRow * height));
168             int childWidthSpec = MeasureSpec.makeMeasureSpec(
169                     (int) (width * (scaleEndCol - scaleStartCol)), MeasureSpec.EXACTLY);
170             int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
171             child.measure(childWidthSpec, childHeightSpec);
172 
173             // If the height of the measured child view is bigger than the height of the calculated
174             // region by the given ScaleLayoutParams, the height of the region should be increased
175             // to fit the size of the child view.
176             if (child.getMeasuredHeight() > mRectArray[i].height()) {
177                 int overflowedHeight = child.getMeasuredHeight() - mRectArray[i].height();
178                 overflowedHeight = (overflowedHeight + 1) / 2;
179                 mRectArray[i].bottom += overflowedHeight;
180                 mRectArray[i].top -= overflowedHeight;
181                 if (mRectArray[i].top < 0) {
182                     mRectArray[i].bottom -= mRectArray[i].top;
183                     mRectArray[i].top = 0;
184                 }
185                 if (mRectArray[i].bottom > height) {
186                     mRectArray[i].top -= mRectArray[i].bottom - height;
187                     mRectArray[i].bottom = height;
188                 }
189             }
190             childHeightSpec = MeasureSpec.makeMeasureSpec(
191                     (int) (height * (scaleEndRow - scaleStartRow)), MeasureSpec.EXACTLY);
192             child.measure(childWidthSpec, childHeightSpec);
193         }
194 
195         // Avoid overlapping rectangles.
196         // Step 1. Sort rectangles by position (top-left).
197         int visibleRectCount = 0;
198         int[] visibleRectGroup = new int[count];
199         Rect[] visibleRectArray = new Rect[count];
200         for (int i = 0; i < count; ++i) {
201             if (getChildAt(i).getVisibility() == View.VISIBLE) {
202                 visibleRectGroup[visibleRectCount] = visibleRectCount;
203                 visibleRectArray[visibleRectCount] = mRectArray[i];
204                 ++visibleRectCount;
205             }
206         }
207         Arrays.sort(visibleRectArray, 0, visibleRectCount, mRectTopLeftSorter);
208 
209         // Step 2. Move down if there are overlapping rectangles.
210         for (int i = 0; i < visibleRectCount - 1; ++i) {
211             for (int j = i + 1; j < visibleRectCount; ++j) {
212                 if (Rect.intersects(visibleRectArray[i], visibleRectArray[j])) {
213                     visibleRectGroup[j] = visibleRectGroup[i];
214                     visibleRectArray[j].set(visibleRectArray[j].left,
215                             visibleRectArray[i].bottom,
216                             visibleRectArray[j].right,
217                             visibleRectArray[i].bottom + visibleRectArray[j].height());
218                 }
219             }
220         }
221 
222         // Step 3. Move up if there is any overflowed rectangle.
223         for (int i = visibleRectCount - 1; i >= 0; --i) {
224             if (visibleRectArray[i].bottom > height) {
225                 int overflowedHeight = visibleRectArray[i].bottom - height;
226                 for (int j = 0; j <= i; ++j) {
227                     if (visibleRectGroup[i] == visibleRectGroup[j]) {
228                         visibleRectArray[j].set(visibleRectArray[j].left,
229                                 visibleRectArray[j].top - overflowedHeight,
230                                 visibleRectArray[j].right,
231                                 visibleRectArray[j].bottom - overflowedHeight);
232                     }
233                 }
234             }
235         }
236         setMeasuredDimension(widthSpecSize, heightSpecSize);
237     }
238 
239     @Override
onLayout(boolean changed, int l, int t, int r, int b)240     protected void onLayout(boolean changed, int l, int t, int r, int b) {
241         int paddingLeft = getPaddingLeft();
242         int paddingTop = getPaddingTop();
243         int count = getChildCount();
244         for (int i = 0; i < count; ++i) {
245             View child = getChildAt(i);
246             if (child.getVisibility() != GONE) {
247                 int childLeft = paddingLeft + mRectArray[i].left;
248                 int childTop = paddingTop + mRectArray[i].top;
249                 int childBottom = paddingLeft + mRectArray[i].bottom;
250                 int childRight = paddingTop + mRectArray[i].right;
251                 if (DEBUG) {
252                     Log.d(TAG, String.format("layoutChild bottom: %d left: %d right: %d top: %d",
253                             childBottom, childLeft,
254                             childRight, childTop));
255                 }
256                 child.layout(childLeft, childTop, childRight, childBottom);
257             }
258         }
259     }
260 }
261