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}