1 /* 2 * Copyright (C) 2014 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.testingcamera2; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.util.AttributeSet; 22 import android.util.Log; 23 import android.view.SurfaceView; 24 import android.view.View; 25 import android.view.ViewGroup.LayoutParams; 26 27 /** 28 * A SurfaceView that maintains its aspect ratio to be a desired target value. 29 * 30 * <p>Depending on the layout, the FixedAspectSurfaceView may not be able to maintain the 31 * requested aspect ratio. This can happen if both the width and the height are exactly 32 * determined by the layout. To avoid this, ensure that either the height or the width is 33 * adjustable by the view; for example, by setting the layout parameters to be WRAP_CONTENT for 34 * the dimension that is best adjusted to maintain the aspect ratio.</p> 35 */ 36 public class FixedAspectSurfaceView extends SurfaceView { 37 38 private static final String TAG = "FixedAspectSurfaceView"; 39 40 /** 41 * Desired width/height ratio 42 */ 43 private float mAspectRatio; 44 FixedAspectSurfaceView(Context context, AttributeSet attrs)45 public FixedAspectSurfaceView(Context context, AttributeSet attrs) { 46 super(context, attrs); 47 48 // Get initial aspect ratio from custom attributes 49 TypedArray a = 50 context.getTheme().obtainStyledAttributes(attrs, 51 R.styleable.FixedAspectSurfaceView, 0, 0); 52 setAspectRatio(a.getFloat( 53 R.styleable.FixedAspectSurfaceView_aspectRatio, 1.f)); 54 a.recycle(); 55 } 56 57 /** 58 * Set the desired aspect ratio for this view. 59 * 60 * @param aspect the desired width/height ratio in the current UI orientation. Must be a 61 * positive value. 62 */ setAspectRatio(float aspect)63 public void setAspectRatio(float aspect) { 64 if (aspect <= 0) { 65 throw new IllegalArgumentException("Aspect ratio must be positive"); 66 } 67 mAspectRatio = aspect; 68 requestLayout(); 69 } 70 71 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)72 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 73 74 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 75 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 76 int width = MeasureSpec.getSize(widthMeasureSpec); 77 int height = MeasureSpec.getSize(heightMeasureSpec); 78 79 // General goal: Adjust dimensions to maintain the requested aspect ratio as much 80 // as possible. Depending on the measure specs handed down, this may not be possible 81 82 // Only set one of these to true 83 boolean scaleWidth = false; 84 boolean scaleHeight = false; 85 86 // Sort out which dimension to scale, if either can be. There are 9 combinations of 87 // possible measure specs; a few cases below handle multiple combinations 88 if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) { 89 // Can't adjust sizes at all, do nothing 90 } else if (widthMode == MeasureSpec.EXACTLY) { 91 // Width is fixed, heightMode either AT_MOST or UNSPECIFIED, so adjust height 92 scaleHeight = true; 93 } else if (heightMode == MeasureSpec.EXACTLY) { 94 // Height is fixed, widthMode either AT_MOST or UNSPECIFIED, so adjust width 95 scaleWidth = true; 96 } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { 97 // Need to fit into box <= [width, height] in size. 98 // Maximize the View's area while maintaining aspect ratio 99 // This means keeping one dimension as large as possible and shrinking the other 100 float boxAspectRatio = width / (float) height; 101 if (boxAspectRatio > mAspectRatio) { 102 // Box is wider than requested aspect; pillarbox 103 scaleWidth = true; 104 } else { 105 // Box is narrower than requested aspect; letterbox 106 scaleHeight = true; 107 } 108 } else if (widthMode == MeasureSpec.AT_MOST) { 109 // Maximize width, heightSpec is UNSPECIFIED 110 scaleHeight = true; 111 } else if (heightMode == MeasureSpec.AT_MOST) { 112 // Maximize height, widthSpec is UNSPECIFIED 113 scaleWidth = true; 114 } else { 115 // Both MeasureSpecs are UNSPECIFIED. This is probably a pathological layout, 116 // with width == height == 0 117 // but arbitrarily scale height anyway 118 scaleHeight = true; 119 } 120 121 // Do the scaling 122 if (scaleWidth) { 123 width = (int) (height * mAspectRatio); 124 } else if (scaleHeight) { 125 height = (int) (width / mAspectRatio); 126 } 127 128 // Override width/height if needed for EXACTLY and AT_MOST specs 129 width = View.resolveSizeAndState(width, widthMeasureSpec, 0); 130 height = View.resolveSizeAndState(height, heightMeasureSpec, 0); 131 132 // Finally set the calculated dimensions 133 setMeasuredDimension(width, height); 134 } 135 } 136