IndicatorControlWheel.java revision ff45331706e2fe361b4ff736d90b0ad4af0e7be9
1/*
2 * Copyright (C) 2010 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
17package com.android.camera.ui;
18
19import com.android.camera.PreferenceGroup;
20import com.android.camera.R;
21import com.android.camera.Util;
22
23import android.content.Context;
24import android.content.res.Resources;
25import android.graphics.Canvas;
26import android.graphics.Paint;
27import android.graphics.RectF;
28import android.os.SystemClock;
29import android.util.AttributeSet;
30import android.view.MotionEvent;
31import android.view.View;
32
33/**
34 * A view that contains camera setting indicators. The
35 * indicators are spreaded around the shutter button. The first child is always
36 * the shutter button.
37 */
38public class IndicatorControlWheel extends IndicatorControl {
39    public static final int HIGHLIGHT_WIDTH = 4;
40    public static final int HIGHLIGHT_DEGREES = 30;
41    public static final double HIGHLIGHT_RADIANS = Math.toRadians(HIGHLIGHT_DEGREES);
42
43    private static final String TAG = "IndicatorControlWheel";
44
45    // The width of the edges on both sides of the wheel, which has less alpha.
46    private static final float EDGE_STROKE_WIDTH = 6f;
47    private static final int TIME_LAPSE_ARC_WIDTH = 6;
48
49    private final int HIGHLIGHT_COLOR;
50    private final int TIME_LAPSE_ARC_COLOR;
51
52    // The center of the shutter button.
53    private int mCenterX, mCenterY;
54    // The width of the wheel stroke.
55    private int mStrokeWidth;
56    private double mShutterButtonRadius;
57    private double mWheelRadius;
58    private double mChildRadians[];
59    private Paint mBackgroundPaint;
60    private RectF mBackgroundRect;
61    // The index of the child that is being pressed. -1 means no child is being
62    // pressed.
63    private int mPressedIndex = -1;
64
65    // Time lapse recording variables.
66    private int mTimeLapseInterval;  // in ms
67    private long mRecordingStartTime = 0;
68    private long mNumberOfFrames = 0;
69
70    // Remember the last event for event cancelling if out of bound.
71    private MotionEvent mLastMotionEvent;
72
73    public IndicatorControlWheel(Context context, AttributeSet attrs) {
74        super(context, attrs);
75        Resources resources = context.getResources();
76        HIGHLIGHT_COLOR = resources.getColor(R.color.review_control_pressed_color);
77        TIME_LAPSE_ARC_COLOR = resources.getColor(R.color.time_lapse_arc);
78
79        setWillNotDraw(false);
80
81        mBackgroundPaint = new Paint();
82        mBackgroundPaint.setStyle(Paint.Style.STROKE);
83        mBackgroundPaint.setAntiAlias(true);
84
85        mBackgroundRect = new RectF();
86    }
87
88    public void initialize(Context context, PreferenceGroup group,
89            String flashSetting, String[] keys, String[] otherSettingKeys) {
90        mShutterButtonRadius = IndicatorControlWheelContainer.SHUTTER_BUTTON_RADIUS;
91        mStrokeWidth = Util.dpToPixel(IndicatorControlWheelContainer.STROKE_WIDTH);
92        mWheelRadius = mShutterButtonRadius + mStrokeWidth * 0.5;
93        // Add CameraPicker control.
94        initializeCameraPicker(context, group);
95        super.initialize(context, group, flashSetting, keys, otherSettingKeys);
96
97        // The radian intervals for each icon for touch events.
98        mChildRadians = new double[getChildCount()];
99    }
100
101    private int getTouchIndicatorIndex(double delta) {
102        // The delta is the touch point in radians.
103        int count = getChildCount();
104        if (count == 0) return -1;
105        int sectors = count - 1;
106        double sectorDegrees = Math.min(HIGHLIGHT_RADIANS,
107                (count == 1) ? HIGHLIGHT_RADIANS : (Math.PI / sectors));
108        // Check which indicator is touched.
109        if ((delta >= (Math.PI - HIGHLIGHT_RADIANS) / 2) &&
110            (delta <= (Math.PI + (Math.PI + HIGHLIGHT_RADIANS) / 2))) {
111            int index = (int) ((delta - Math.PI / 2) * sectors / Math.PI);
112            if (index > sectors) return sectors ; // degree greater than 270
113            if (index < 0) return 0;  // degree less than 90
114            if (delta <= (mChildRadians[index] + sectorDegrees / 2)) return index;
115            if (delta >= (mChildRadians[index + 1] - sectorDegrees / 2)) {
116                return index + 1;
117            }
118        }
119        return -1;
120    }
121
122    private void injectMotionEvent(int viewIndex, MotionEvent event, int action) {
123        View v = getChildAt(viewIndex);
124        event.setAction(action);
125        v.dispatchTouchEvent(event);
126    }
127
128    @Override
129    public boolean dispatchTouchEvent(MotionEvent event) {
130        if (!onFilterTouchEventForSecurity(event)) return false;
131        mLastMotionEvent = event;
132        int action = event.getAction();
133
134        double dx = event.getX() - mCenterX;
135        double dy = mCenterY - event.getY();
136        double radius = Math.sqrt(dx * dx + dy * dy);
137
138        // Ignore the event if too far from the shutter button.
139        if ((radius <= mWheelRadius + mStrokeWidth) && (radius > mShutterButtonRadius)) {
140            double delta = Math.atan2(dy, dx);
141            if (delta < 0) delta += Math.PI * 2;
142            int index = getTouchIndicatorIndex(delta);
143            // Move over from one indicator to another.
144            if ((index != mPressedIndex) || (action == MotionEvent.ACTION_DOWN)) {
145                if (mPressedIndex != -1) {
146                    injectMotionEvent(mPressedIndex, event, MotionEvent.ACTION_CANCEL);
147                } else {
148                    // Cancel the popup if it is different from the selected.
149                    if (getSelectedIndicatorIndex() != index) dismissSettingPopup();
150                }
151                if ((index != -1) && (action == MotionEvent.ACTION_MOVE)) {
152                    injectMotionEvent(index, event, MotionEvent.ACTION_DOWN);
153                }
154            }
155            if ((index != -1) && (action != MotionEvent.ACTION_MOVE)) {
156                getChildAt(index).dispatchTouchEvent(event);
157            }
158            // Once the button is up, reset the press index.
159            mPressedIndex = (action == MotionEvent.ACTION_UP) ? -1 : index;
160            invalidate();
161            return true;
162        }
163        // The event is not on any of the child.
164        dismissSettingPopup();
165        if (mPressedIndex != -1) {
166            View cancelChild = getChildAt(mPressedIndex);
167            event.setAction(MotionEvent.ACTION_CANCEL);
168            cancelChild.dispatchTouchEvent(event);
169            mPressedIndex = -1;
170        }
171        invalidate();
172        return false;
173    }
174
175    @Override
176    protected void onLayout(
177            boolean changed, int left, int top, int right, int bottom) {
178        int count = getChildCount();
179        if (count == 0) return;
180
181        mCenterX = right - left - Util.dpToPixel(
182                IndicatorControlWheelContainer.FULL_WHEEL_RADIUS);
183        mCenterY = (bottom - top) / 2;
184
185        // Layout the settings. The icons are spreaded on the left side of the
186        // shutter button. So the angle starts from 90 to 270 degrees.
187
188        // This will just get rid of Divide-By-Zero.
189        double intervalDegrees = (count == 1) ? 90.0 : 180.0 / (count - 1);
190        double initialDegrees = 90.0;
191
192        for (int i = 0; i < count; i++) {
193            View view = getChildAt(i);
194            double degree = initialDegrees + intervalDegrees * i;
195            double radian = Math.toRadians(degree);
196            int x = mCenterX + (int)(mWheelRadius * Math.cos(radian));
197            int y = mCenterY - (int)(mWheelRadius * Math.sin(radian));
198            int width = view.getMeasuredWidth();
199            int height = view.getMeasuredHeight();
200            view.layout(x - width / 2, y - height / 2, x + width / 2,
201                    y + height / 2);
202            // Store the radian intervals for each icon.
203            mChildRadians[i] = Math.toRadians(degree);
204        }
205    }
206
207    public void startTimeLapseAnimation(int timeLapseInterval, long startTime) {
208        mTimeLapseInterval = timeLapseInterval;
209        mRecordingStartTime = startTime;
210        mNumberOfFrames = 0;
211        invalidate();
212    }
213
214    public void stopTimeLapseAnimation() {
215        mTimeLapseInterval = 0;
216        invalidate();
217    }
218
219    private int getSelectedIndicatorIndex() {
220        for (int i = 0; i < mIndicators.size(); i++) {
221            AbstractIndicatorButton b = mIndicators.get(i);
222            if (b.getPopupWindow() != null) {
223                return indexOfChild(b);
224            }
225        }
226        if (mPressedIndex != -1) {
227            if (!(getChildAt(mPressedIndex) instanceof AbstractIndicatorButton)) {
228                return mPressedIndex;
229            }
230        }
231        return -1;
232    }
233
234    @Override
235    protected void onDraw(Canvas canvas) {
236        // Draw highlight.
237        float delta = mStrokeWidth * 0.5f;
238        float radius = (float) (mWheelRadius + mStrokeWidth * 0.5 + EDGE_STROKE_WIDTH);
239        mBackgroundRect.set(mCenterX - radius, mCenterY - radius, mCenterX + radius,
240                 mCenterY + radius);
241
242        int selectedIndex = getSelectedIndicatorIndex();
243
244        // Draw the highlight arc if an indicator is selected or being pressed.
245       if (selectedIndex >= 0) {
246            int count = getChildCount();
247            float initialDegrees = 90.0f;
248            float intervalDegrees = (count <= 1) ? 0.0f : 180.0f / (count - 1);
249            float degree;
250            degree = initialDegrees + intervalDegrees * selectedIndex;
251            mBackgroundPaint.setStrokeWidth(HIGHLIGHT_WIDTH);
252            mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
253            mBackgroundPaint.setColor(HIGHLIGHT_COLOR);
254            canvas.drawArc(mBackgroundRect, -degree - HIGHLIGHT_DEGREES / 2,
255                    HIGHLIGHT_DEGREES, false, mBackgroundPaint);
256        }
257
258        // Draw arc shaped indicator in time lapse recording.
259        if (mTimeLapseInterval != 0) {
260            // Setup rectangle and paint.
261            mBackgroundRect.set((float)(mCenterX - mShutterButtonRadius),
262                    (float)(mCenterY - mShutterButtonRadius),
263                    (float)(mCenterX + mShutterButtonRadius),
264                    (float)(mCenterY + mShutterButtonRadius));
265            mBackgroundRect.inset(3f, 3f);
266            mBackgroundPaint.setStrokeWidth(TIME_LAPSE_ARC_WIDTH);
267            mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
268            mBackgroundPaint.setColor(TIME_LAPSE_ARC_COLOR);
269
270            // Compute the start angle and sweep angle.
271            long timeDelta = SystemClock.uptimeMillis() - mRecordingStartTime;
272            long numberOfFrames = timeDelta / mTimeLapseInterval;
273            float sweepAngle;
274            if (numberOfFrames > mNumberOfFrames) {
275                // The arc just acrosses 0 degree. Draw a full circle so it
276                // looks better.
277                sweepAngle = 360;
278                mNumberOfFrames = numberOfFrames;
279            } else {
280                sweepAngle = timeDelta % mTimeLapseInterval * 360f / mTimeLapseInterval;
281            }
282
283            canvas.drawArc(mBackgroundRect, 0, sweepAngle, false, mBackgroundPaint);
284            invalidate();
285        }
286
287        super.onDraw(canvas);
288    }
289
290    public void onTouchOutBound() {
291        if (mPressedIndex != -1) {
292            dismissSettingPopup();
293            injectMotionEvent(mPressedIndex, mLastMotionEvent, MotionEvent.ACTION_CANCEL);
294            mPressedIndex = -1;
295            invalidate();
296        }
297    }
298}
299