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