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