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 17 package com.android.videoeditor.widgets; 18 19 import com.android.videoeditor.R; 20 21 import android.content.Context; 22 import android.graphics.Canvas; 23 import android.graphics.drawable.Drawable; 24 import android.util.AttributeSet; 25 import android.view.MotionEvent; 26 import android.view.View; 27 28 /** 29 * The zoom control 30 */ 31 public class ZoomControl extends View { 32 33 private static final double MAX_ANGLE = Math.PI / 3; 34 private static final double THUMB_RADIUS_CONTAINER_SIZE_RATIO = 0.432; 35 private static final double THUMB_INTERNAL_RADIUS_CONTAINER_SIZE_RATIO = 0.24; 36 37 // Instance variables 38 private final Drawable mThumb; 39 private double mRadius; 40 private double mInternalRadius; 41 private int mMaxProgress, mProgress; 42 private OnZoomChangeListener mListener; 43 private int mThumbX, mThumbY; 44 private double mInterval; 45 46 /** 47 * The zoom change listener 48 */ 49 public interface OnZoomChangeListener { 50 /** 51 * The progress value has changed 52 * 53 * @param progress The progress value 54 * @param fromUser true if the user is changing the zoom 55 */ onProgressChanged(int progress, boolean fromUser)56 public void onProgressChanged(int progress, boolean fromUser); 57 } 58 ZoomControl(Context context, AttributeSet attrs, int defStyle)59 public ZoomControl(Context context, AttributeSet attrs, int defStyle) { 60 super(context, attrs, defStyle); 61 62 // Set the default maximum progress 63 mMaxProgress = 100; 64 computeInterval(); 65 66 // Load the thumb selector 67 mThumb = context.getResources().getDrawable(R.drawable.zoom_thumb_selector); 68 } 69 70 @Override onLayout(boolean changed, int left, int top, int right, int bottom)71 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 72 super.onLayout(changed, left, top, right, bottom); 73 double width = right - left; 74 mRadius = width * THUMB_RADIUS_CONTAINER_SIZE_RATIO; 75 mInternalRadius = width * THUMB_INTERNAL_RADIUS_CONTAINER_SIZE_RATIO; 76 } 77 ZoomControl(Context context, AttributeSet attrs)78 public ZoomControl(Context context, AttributeSet attrs) { 79 this(context, attrs, 0); 80 } 81 ZoomControl(Context context)82 public ZoomControl(Context context) { 83 this(context, null, 0); 84 } 85 86 @Override refreshDrawableState()87 public void refreshDrawableState() { 88 mThumb.setState(isPressed() ? PRESSED_WINDOW_FOCUSED_STATE_SET : ENABLED_STATE_SET); 89 invalidate(); 90 } 91 92 /** 93 * @param max The maximum value 94 */ setMax(int max)95 public void setMax(int max) { 96 mMaxProgress = max; 97 computeInterval(); 98 } 99 100 /** 101 * @param progress The progress 102 */ setProgress(int progress)103 public void setProgress(int progress) { 104 mProgress = progress; 105 106 progressToPosition(); 107 invalidate(); 108 } 109 110 /** 111 * @param listener The listener 112 */ setOnZoomChangeListener(OnZoomChangeListener listener)113 public void setOnZoomChangeListener(OnZoomChangeListener listener) { 114 mListener = listener; 115 } 116 117 @Override onDraw(Canvas canvas)118 protected void onDraw(Canvas canvas) { 119 super.onDraw(canvas); 120 121 if (mThumbX == 0 && mThumbY == 0) { 122 progressToPosition(); 123 } 124 125 final int halfWidth = mThumb.getIntrinsicWidth() / 2; 126 final int halfHeight = mThumb.getIntrinsicHeight() / 2; 127 mThumb.setBounds(mThumbX - halfWidth, mThumbY - halfHeight, mThumbX + halfWidth, 128 mThumbY + halfHeight); 129 mThumb.setAlpha(isEnabled() ? 255 : 100); 130 mThumb.draw(canvas); 131 } 132 133 @Override onTouchEvent(MotionEvent ev)134 public boolean onTouchEvent(MotionEvent ev) { 135 super.onTouchEvent(ev); 136 switch (ev.getAction()) { 137 case MotionEvent.ACTION_DOWN: { 138 if (isEnabled()) { 139 getParent().requestDisallowInterceptTouchEvent(true); 140 } 141 break; 142 } 143 144 case MotionEvent.ACTION_MOVE: { 145 if (isEnabled()) { 146 final float x = ev.getX() - (getWidth() / 2); 147 final float y = -(ev.getY() - (getHeight() / 2)); 148 final double alpha = Math.atan((double)y / (double)x); 149 150 if (!checkHit(x, y, alpha)) { 151 return true; 152 } 153 154 final int progress; 155 if (x >= 0 && y >= 0) { 156 mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2)); 157 mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha))); 158 progress = (int)((mMaxProgress / 2) - (alpha / mInterval)); 159 } else if (x >= 0 && y <= 0) { 160 mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2)); 161 mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha))); 162 progress = (int)((mMaxProgress / 2) - (alpha / mInterval)); 163 } else if (x <= 0 && y >= 0) { 164 mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha))); 165 mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha))); 166 progress = -(int)(((alpha + MAX_ANGLE) / mInterval)); 167 } else { 168 mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha))); 169 mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha))); 170 progress = (int)(mMaxProgress - ((alpha - MAX_ANGLE) / mInterval)); 171 } 172 173 invalidate(); 174 175 if (mListener != null) { 176 if (progress != mProgress) { 177 mProgress = progress; 178 mListener.onProgressChanged(mProgress, true); 179 } 180 } 181 } 182 break; 183 } 184 185 case MotionEvent.ACTION_CANCEL: 186 case MotionEvent.ACTION_UP: { 187 break; 188 } 189 190 default: { 191 break; 192 } 193 } 194 195 return true; 196 } 197 198 /** 199 * Check if the user is touching the correct area 200 * 201 * @param x The horizontal coordinate 202 * @param y The vertical coordinate 203 * @param alpha The angle 204 * @return true if there is a hit in the allowed area 205 */ checkHit(float x, float y, double alpha)206 private boolean checkHit(float x, float y, double alpha) { 207 final double radius = Math.sqrt((x * x) + (y * y)); 208 if (radius < mInternalRadius) { 209 return false; 210 } 211 212 if (x >= 0) { 213 return true; 214 } else if (y >= 0) { 215 if ((alpha >= -(Math.PI / 2)) && (alpha <= -MAX_ANGLE)) { 216 return true; 217 } 218 } else { 219 if ((alpha >= MAX_ANGLE) && (alpha <= (Math.PI / 2))) { 220 return true; 221 } 222 } 223 224 return false; 225 } 226 227 /** 228 * Compute the position of the thumb based on the progress values 229 */ progressToPosition()230 private void progressToPosition() { 231 if (getWidth() == 0) { // Layout is not yet complete 232 return; 233 } 234 235 final double beta; 236 if (mProgress <= mMaxProgress / 2) { 237 beta = ((mMaxProgress / 2) - mProgress) * mInterval; 238 } else { 239 beta = ((mMaxProgress - mProgress) * mInterval) + Math.PI + MAX_ANGLE; 240 } 241 242 final double alpha; 243 if (beta >= 0 && beta <= Math.PI / 2) { 244 alpha = beta; 245 mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2)); 246 mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha))); 247 } else if (beta > Math.PI / 2 && beta < (Math.PI / 2) + MAX_ANGLE) { 248 alpha = beta - Math.PI; 249 mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha))); 250 mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha))); 251 } else if (beta <= 2 * Math.PI && beta > (3 * Math.PI) / 2) { 252 alpha = beta - (2 * Math.PI); 253 mThumbX = (int)((mRadius * Math.cos(alpha)) + (getWidth() / 2)); 254 mThumbY = (int)((getHeight() / 2) - (mRadius * Math.sin(alpha))); 255 } else { 256 alpha = beta - Math.PI; 257 mThumbX = (int)((getWidth() / 2) - (mRadius * Math.cos(alpha))); 258 mThumbY = (int)((getHeight() / 2) + (mRadius * Math.sin(alpha))); 259 } 260 } 261 262 /** 263 * Compute the radians interval between progress values 264 */ computeInterval()265 private void computeInterval() { 266 mInterval = (Math.PI - MAX_ANGLE) / (mMaxProgress / 2); 267 } 268 } 269