diff options
Diffstat (limited to 'tuner/src/com/android/tv/tuner/layout/ScaledLayout.java')
-rw-r--r-- | tuner/src/com/android/tv/tuner/layout/ScaledLayout.java | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/tuner/src/com/android/tv/tuner/layout/ScaledLayout.java b/tuner/src/com/android/tv/tuner/layout/ScaledLayout.java new file mode 100644 index 00000000..dd92b641 --- /dev/null +++ b/tuner/src/com/android/tv/tuner/layout/ScaledLayout.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.layout; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import com.android.tv.tuner.R; +import java.util.Arrays; +import java.util.Comparator; + +/** A layout that scales its children using the given percentage value. */ +public class ScaledLayout extends ViewGroup { + private static final String TAG = "ScaledLayout"; + private static final boolean DEBUG = false; + private static final Comparator<Rect> mRectTopLeftSorter = + new Comparator<Rect>() { + @Override + public int compare(Rect lhs, Rect rhs) { + if (lhs.top != rhs.top) { + return lhs.top - rhs.top; + } else { + return lhs.left - rhs.left; + } + } + }; + + private Rect[] mRectArray; + private final int mMaxWidth; + private final int mMaxHeight; + + public ScaledLayout(Context context) { + this(context, null); + } + + public ScaledLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ScaledLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + Point size = new Point(); + DisplayManager displayManager = + (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE); + Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + display.getRealSize(size); + mMaxWidth = size.x; + mMaxHeight = size.y; + } + + /** + * ScaledLayoutParams stores the four scale factors. <br> + * Vertical coordinate system: ({@code scaleStartRow} * 100) % ~ ({@code scaleEndRow} * 100) % + * Horizontal coordinate system: ({@code scaleStartCol} * 100) % ~ ({@code scaleEndCol} * 100) % + * <br> + * In XML, for example, + * + * <pre>{@code + * <View + * app:layout_scaleStartRow="0.1" + * app:layout_scaleEndRow="0.5" + * app:layout_scaleStartCol="0.4" + * app:layout_scaleEndCol="1" /> + * }</pre> + */ + public static class ScaledLayoutParams extends ViewGroup.LayoutParams { + public static final float SCALE_UNSPECIFIED = -1; + public final float scaleStartRow; + public final float scaleEndRow; + public final float scaleStartCol; + public final float scaleEndCol; + + public ScaledLayoutParams( + float scaleStartRow, float scaleEndRow, float scaleStartCol, float scaleEndCol) { + super(MATCH_PARENT, MATCH_PARENT); + this.scaleStartRow = scaleStartRow; + this.scaleEndRow = scaleEndRow; + this.scaleStartCol = scaleStartCol; + this.scaleEndCol = scaleEndCol; + } + + public ScaledLayoutParams(Context context, AttributeSet attrs) { + super(MATCH_PARENT, MATCH_PARENT); + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.utScaledLayout); + scaleStartRow = + array.getFloat( + R.styleable.utScaledLayout_layout_scaleStartRow, SCALE_UNSPECIFIED); + scaleEndRow = + array.getFloat( + R.styleable.utScaledLayout_layout_scaleEndRow, SCALE_UNSPECIFIED); + scaleStartCol = + array.getFloat( + R.styleable.utScaledLayout_layout_scaleStartCol, SCALE_UNSPECIFIED); + scaleEndCol = + array.getFloat( + R.styleable.utScaledLayout_layout_scaleEndCol, SCALE_UNSPECIFIED); + array.recycle(); + } + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new ScaledLayoutParams(getContext(), attrs); + } + + @Override + protected boolean checkLayoutParams(LayoutParams p) { + return (p instanceof ScaledLayoutParams); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + int width = widthSpecSize - getPaddingLeft() - getPaddingRight(); + int height = heightSpecSize - getPaddingTop() - getPaddingBottom(); + if (DEBUG) { + Log.d(TAG, String.format("onMeasure width: %d, height: %d", width, height)); + } + int count = getChildCount(); + mRectArray = new Rect[count]; + for (int i = 0; i < count; ++i) { + View child = getChildAt(i); + ViewGroup.LayoutParams params = child.getLayoutParams(); + float scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol; + if (!(params instanceof ScaledLayoutParams)) { + throw new RuntimeException( + "A child of ScaledLayout cannot have the UNSPECIFIED scale factors"); + } + scaleStartRow = ((ScaledLayoutParams) params).scaleStartRow; + scaleEndRow = ((ScaledLayoutParams) params).scaleEndRow; + scaleStartCol = ((ScaledLayoutParams) params).scaleStartCol; + scaleEndCol = ((ScaledLayoutParams) params).scaleEndCol; + if (scaleStartRow < 0 || scaleStartRow > 1) { + throw new RuntimeException( + "A child of ScaledLayout should have a range of " + + "scaleStartRow between 0 and 1"); + } + if (scaleEndRow < scaleStartRow || scaleStartRow > 1) { + throw new RuntimeException( + "A child of ScaledLayout should have a range of " + + "scaleEndRow between scaleStartRow and 1"); + } + if (scaleEndCol < 0 || scaleEndCol > 1) { + throw new RuntimeException( + "A child of ScaledLayout should have a range of " + + "scaleStartCol between 0 and 1"); + } + if (scaleEndCol < scaleStartCol || scaleEndCol > 1) { + throw new RuntimeException( + "A child of ScaledLayout should have a range of " + + "scaleEndCol between scaleStartCol and 1"); + } + if (DEBUG) { + Log.d( + TAG, + String.format( + "onMeasure child scaleStartRow: %f scaleEndRow: %f " + + "scaleStartCol: %f scaleEndCol: %f", + scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol)); + } + mRectArray[i] = + new Rect( + (int) (scaleStartCol * width), + (int) (scaleStartRow * height), + (int) (scaleEndCol * width), + (int) (scaleEndRow * height)); + int scaleWidth = (int) (width * (scaleEndCol - scaleStartCol)); + int childWidthSpec = + MeasureSpec.makeMeasureSpec( + scaleWidth > mMaxWidth ? mMaxWidth : scaleWidth, MeasureSpec.EXACTLY); + int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + child.measure(childWidthSpec, childHeightSpec); + + // If the height of the measured child view is bigger than the height of the calculated + // region by the given ScaleLayoutParams, the height of the region should be increased + // to fit the size of the child view. + if (child.getMeasuredHeight() > mRectArray[i].height()) { + int overflowedHeight = child.getMeasuredHeight() - mRectArray[i].height(); + overflowedHeight = (overflowedHeight + 1) / 2; + mRectArray[i].bottom += overflowedHeight; + mRectArray[i].top -= overflowedHeight; + if (mRectArray[i].top < 0) { + mRectArray[i].bottom -= mRectArray[i].top; + mRectArray[i].top = 0; + } + if (mRectArray[i].bottom > height) { + mRectArray[i].top -= mRectArray[i].bottom - height; + mRectArray[i].bottom = height; + } + } + int scaleHeight = (int) (height * (scaleEndRow - scaleStartRow)); + childHeightSpec = + MeasureSpec.makeMeasureSpec( + scaleHeight > mMaxHeight ? mMaxHeight : scaleHeight, + MeasureSpec.EXACTLY); + child.measure(childWidthSpec, childHeightSpec); + } + + // Avoid overlapping rectangles. + // Step 1. Sort rectangles by position (top-left). + int visibleRectCount = 0; + int[] visibleRectGroup = new int[count]; + Rect[] visibleRectArray = new Rect[count]; + for (int i = 0; i < count; ++i) { + if (getChildAt(i).getVisibility() == View.VISIBLE) { + visibleRectGroup[visibleRectCount] = visibleRectCount; + visibleRectArray[visibleRectCount] = mRectArray[i]; + ++visibleRectCount; + } + } + Arrays.sort(visibleRectArray, 0, visibleRectCount, mRectTopLeftSorter); + + // Step 2. Move down if there are overlapping rectangles. + for (int i = 0; i < visibleRectCount - 1; ++i) { + for (int j = i + 1; j < visibleRectCount; ++j) { + if (Rect.intersects(visibleRectArray[i], visibleRectArray[j])) { + visibleRectGroup[j] = visibleRectGroup[i]; + visibleRectArray[j].set( + visibleRectArray[j].left, + visibleRectArray[i].bottom, + visibleRectArray[j].right, + visibleRectArray[i].bottom + visibleRectArray[j].height()); + } + } + } + + // Step 3. Move up if there is any overflowed rectangle. + for (int i = visibleRectCount - 1; i >= 0; --i) { + if (visibleRectArray[i].bottom > height) { + int overflowedHeight = visibleRectArray[i].bottom - height; + for (int j = 0; j <= i; ++j) { + if (visibleRectGroup[i] == visibleRectGroup[j]) { + visibleRectArray[j].set( + visibleRectArray[j].left, + visibleRectArray[j].top - overflowedHeight, + visibleRectArray[j].right, + visibleRectArray[j].bottom - overflowedHeight); + } + } + } + } + setMeasuredDimension(widthSpecSize, heightSpecSize); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + int count = getChildCount(); + for (int i = 0; i < count; ++i) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + int childLeft = paddingLeft + mRectArray[i].left; + int childTop = paddingTop + mRectArray[i].top; + int childBottom = paddingLeft + mRectArray[i].bottom; + int childRight = paddingTop + mRectArray[i].right; + if (DEBUG) { + Log.d( + TAG, + String.format( + "layoutChild bottom: %d left: %d right: %d top: %d", + childBottom, childLeft, childRight, childTop)); + } + child.layout(childLeft, childTop, childRight, childBottom); + } + } + } +} |