1fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford/* 2fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * Copyright (C) 2015 The Android Open Source Project 3fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * 4fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * Licensed under the Apache License, Version 2.0 (the "License"); 5fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * you may not use this file except in compliance with the License. 6fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * You may obtain a copy of the License at 7fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * 8fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * http://www.apache.org/licenses/LICENSE-2.0 9fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * 10fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * Unless required by applicable law or agreed to in writing, software 11fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * distributed under the License is distributed on an "AS IS" BASIS, 12fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * See the License for the specific language governing permissions and 14fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * limitations under the License. 15fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford */ 16fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordpackage com.android.example.rscamera; 17fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 18fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.content.Context; 19fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.content.res.TypedArray; 20fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.util.AttributeSet; 21fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.util.Log; 22fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.view.GestureDetector; 23fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.view.MotionEvent; 24fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.view.SurfaceView; 25fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.view.View; 26fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport android.view.ViewGroup.LayoutParams; 27fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 28fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordimport com.android.example.rscamera.rscamera.R; 29fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 30fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford/** 31fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * A SurfaceView that maintains its aspect ratio to be a desired target value. 32fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * <p/> 33fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * <p>Depending on the layout, the FixedAspectSurfaceView may not be able to maintain the 34fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * requested aspect ratio. This can happen if both the width and the height are exactly 35fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * determined by the layout. To avoid this, ensure that either the height or the width is 36fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * adjustable by the view; for example, by setting the layout parameters to be WRAP_CONTENT for 37fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * the dimension that is best adjusted to maintain the aspect ratio.</p> 38fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford */ 39fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hofordpublic class FixedAspectSurfaceView extends SurfaceView { 40fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 41fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford /** 42fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * Desired width/height ratio 43fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford */ 44fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford private float mAspectRatio; 45fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 46fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford private GestureDetector mGestureDetector; 47fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 48fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford public FixedAspectSurfaceView(Context context, AttributeSet attrs) { 49fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford super(context, attrs); 50fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 51fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Get initial aspect ratio from custom attributes 52fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford TypedArray a = 53fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford context.getTheme().obtainStyledAttributes(attrs, 54fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford R.styleable.FixedAspectSurfaceView, 0, 0); 55fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford setAspectRatio(a.getFloat( 56fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford R.styleable.FixedAspectSurfaceView_aspectRatio, 1.f)); 57fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford a.recycle(); 58fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 59fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 60fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford /** 61fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * Set the desired aspect ratio for this view. 62fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * 63fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * @param aspect the desired width/height ratio in the current UI orientation. Must be a 64fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * positive value. 65fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford */ 66fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford public void setAspectRatio(float aspect) { 67fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford if (aspect <= 0) { 68fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford throw new IllegalArgumentException("Aspect ratio must be positive"); 69fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 70fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford mAspectRatio = aspect; 71fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford requestLayout(); 72fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 73fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 74fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford /** 75fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford * Set a gesture listener to listen for touch events 76fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford */ 77fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford public void setGestureListener(Context context, GestureDetector.OnGestureListener listener) { 78fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford if (listener == null) { 79fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford mGestureDetector = null; 80fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else { 81fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford mGestureDetector = new GestureDetector(context, listener); 82fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 83fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 84fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 85fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford @Override 86fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 87fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 88fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford int widthMode = MeasureSpec.getMode(widthMeasureSpec); 89fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford int heightMode = MeasureSpec.getMode(heightMeasureSpec); 90fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford int width = MeasureSpec.getSize(widthMeasureSpec); 91fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford int height = MeasureSpec.getSize(heightMeasureSpec); 92fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 93fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // General goal: Adjust dimensions to maintain the requested aspect ratio as much 94fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // as possible. Depending on the measure specs handed down, this may not be possible 95fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 96fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Only set one of these to true 97fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford boolean scaleWidth = false; 98fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford boolean scaleHeight = false; 99fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 100fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Sort out which dimension to scale, if either can be. There are 9 combinations of 101fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // possible measure specs; a few cases below handle multiple combinations 102fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) { 103fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Can't adjust sizes at all, do nothing 104fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else if (widthMode == MeasureSpec.EXACTLY) { 105fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Width is fixed, heightMode either AT_MOST or UNSPECIFIED, so adjust height 106fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford scaleHeight = true; 107fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else if (heightMode == MeasureSpec.EXACTLY) { 108fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Height is fixed, widthMode either AT_MOST or UNSPECIFIED, so adjust width 109fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford scaleWidth = true; 110fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { 111fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Need to fit into box <= [width, height] in size. 112fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Maximize the View's area while maintaining aspect ratio 113fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // This means keeping one dimension as large as possible and shrinking the other 114fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford float boxAspectRatio = width / (float) height; 115fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford if (boxAspectRatio > mAspectRatio) { 116fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Box is wider than requested aspect; pillarbox 117fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford scaleWidth = true; 118fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else { 119fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Box is narrower than requested aspect; letterbox 120fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford scaleHeight = true; 121fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 122fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else if (widthMode == MeasureSpec.AT_MOST) { 123fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Maximize width, heightSpec is UNSPECIFIED 124fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford scaleHeight = true; 125fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else if (heightMode == MeasureSpec.AT_MOST) { 126fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Maximize height, widthSpec is UNSPECIFIED 127fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford scaleWidth = true; 128fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else { 129fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Both MeasureSpecs are UNSPECIFIED. This is probably a pathological layout, 130fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // with width == height == 0 131fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // but arbitrarily scale height anyway 132fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford scaleHeight = true; 133fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 134fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 135fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Do the scaling 136fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford if (scaleWidth) { 137fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford width = (int) (height * mAspectRatio); 138fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } else if (scaleHeight) { 139fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford height = (int) (width / mAspectRatio); 140fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 141fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 142fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Override width/height if needed for EXACTLY and AT_MOST specs 143fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford width = View.resolveSizeAndState(width, widthMeasureSpec, 0); 144fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford height = View.resolveSizeAndState(height, heightMeasureSpec, 0); 145fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 146fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford // Finally set the calculated dimensions 147fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford setMeasuredDimension(width, height); 148fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 149fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford 150fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford @Override 151fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford public boolean onTouchEvent(MotionEvent event) { 152fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford if (mGestureDetector != null) { 153fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford return mGestureDetector.onTouchEvent(event); 154fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 155fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford return false; 156fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford } 157fbb9dd1843197a0d2f7fcda29abbe9d170682a5dJohn Hoford}