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