• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.photoeditor.actions;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.drawable.Drawable;
24 import android.util.AttributeSet;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.animation.AnimationUtils;
28 
29 import com.android.photoeditor.R;
30 
31 /**
32  * Wheel that has a draggable thumb to set and get the normalized scale value from 0 to 1.
33  */
34 class ScaleWheel extends View {
35 
36     /**
37      * Listens to scale changes.
38      */
39     public interface OnScaleChangeListener {
40 
onProgressChanged(float progress, boolean fromUser)41         void onProgressChanged(float progress, boolean fromUser);
42     }
43 
44     private static final float MATH_PI = (float) Math.PI;
45     private static final float MATH_HALF_PI = MATH_PI / 2;
46 
47     // Angles are defined between PI and -PI.
48     private static final float ANGLE_SPANNED = MATH_PI * 4 / 3;
49     private static final float ANGLE_BEGIN = ANGLE_SPANNED / 2.0f;
50 
51     private static final float THUMB_RADIUS_RATIO = 0.363f;
52     private static final float INNER_RADIUS_RATIO = 0.24f;
53 
54     private final Drawable thumb;
55     private final Drawable background;
56     private final int thumbSize;
57     private final Paint circlePaint;
58     private final int maxProgress;
59     private final float radiantInterval;
60     private int thumbRadius;
61     private int innerRadius;
62     private int centerXY;
63     private float angle;
64     private int progress;
65     private boolean dragThumb;
66     private OnScaleChangeListener listener;
67 
ScaleWheel(Context context, AttributeSet attrs)68     public ScaleWheel(Context context, AttributeSet attrs) {
69         super(context, attrs);
70 
71         Resources resources = context.getResources();
72         thumbSize = (int) resources.getDimension(R.dimen.wheel_thumb_size);
73 
74         // Set the maximum progress and compute the radiant interval between progress values.
75         maxProgress = 100;
76         radiantInterval = ANGLE_SPANNED / maxProgress;
77 
78         thumb = resources.getDrawable(R.drawable.wheel_knot_selector);
79         background = resources.getDrawable(R.drawable.scale_wheel_background);
80         background.setAlpha(160);
81 
82         circlePaint = new Paint();
83         circlePaint.setAntiAlias(true);
84         circlePaint.setColor(resources.getColor(R.color.scale_wheel_interior_color));
85     }
86 
setProgress(float progress)87     public void setProgress(float progress) {
88         if (updateProgress((int) (progress * maxProgress), false)) {
89             updateThumbPositionByProgress();
90         }
91     }
92 
setOnScaleChangeListener(OnScaleChangeListener listener)93     public void setOnScaleChangeListener(OnScaleChangeListener listener) {
94         this.listener = listener;
95     }
96 
97     @Override
setVisibility(int visibility)98     public void setVisibility(int visibility) {
99         super.setVisibility(visibility);
100 
101         startAnimation(AnimationUtils.loadAnimation(getContext(),
102                 (visibility == VISIBLE) ? R.anim.wheel_show : R.anim.wheel_hide));
103     }
104 
105     @Override
onSizeChanged(int w, int h, int oldw, int oldh)106     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
107         super.onSizeChanged(w, h, oldw, oldh);
108 
109         int wheelSize = Math.min(w, h);
110         thumbRadius = (int) (wheelSize * THUMB_RADIUS_RATIO);
111         innerRadius = (int) (wheelSize * INNER_RADIUS_RATIO);
112 
113         // The wheel would be centered at (centerXY, centerXY) and have outer-radius centerXY.
114         centerXY = wheelSize / 2;
115         updateThumbPositionByProgress();
116     }
117 
118     @Override
onDraw(Canvas canvas)119     protected void onDraw(Canvas canvas) {
120         super.onDraw(canvas);
121 
122         background.setBounds(0, 0, getWidth(), getHeight());
123         background.draw(canvas);
124 
125         int thumbX = (int) (thumbRadius * Math.cos(angle) + centerXY);
126         int thumbY = (int) (centerXY - thumbRadius * Math.sin(angle));
127         int halfSize = thumbSize / 2;
128         thumb.setBounds(thumbX - halfSize, thumbY - halfSize, thumbX + halfSize, thumbY + halfSize);
129         thumb.draw(canvas);
130 
131         float radius = (progress * innerRadius) / maxProgress;
132         canvas.drawCircle(centerXY, centerXY, radius, circlePaint);
133     }
134 
135     @Override
onTouchEvent(MotionEvent ev)136     public boolean onTouchEvent(MotionEvent ev) {
137         super.onTouchEvent(ev);
138 
139         if (isEnabled()) {
140             switch (ev.getAction()) {
141                 case MotionEvent.ACTION_DOWN:
142                     updateThumbState(
143                             isHittingThumbArea(ev.getX() - centerXY, centerXY - ev.getY()));
144                     break;
145 
146                 case MotionEvent.ACTION_MOVE:
147                     float x = ev.getX() - centerXY;
148                     float y = centerXY - ev.getY();
149                     if (!dragThumb && !updateThumbState(isHittingThumbArea(x, y))) {
150                         // The thumb wasn't dragged and isn't being dragged, either.
151                         break;
152                     }
153 
154                     if (updateAngle(x, y)) {
155                         int progress = (int) ((ANGLE_BEGIN - angle) / radiantInterval);
156                         if (updateProgress(progress, true)) {
157                             invalidate();
158                         }
159                     }
160                     break;
161 
162                 case MotionEvent.ACTION_CANCEL:
163                 case MotionEvent.ACTION_UP:
164                     updateThumbState(false);
165                     break;
166             }
167         }
168         return true;
169     }
170 
isHittingThumbArea(float x, float y)171     private boolean isHittingThumbArea(float x, float y) {
172         double radius = Math.sqrt((x * x) + (y * y));
173         return (radius > innerRadius) && (radius < centerXY);
174     }
175 
updateAngle(float x, float y)176     private boolean updateAngle(float x, float y) {
177         float angle;
178         if (x == 0) {
179             angle = (y >= 0) ? MATH_HALF_PI : -MATH_HALF_PI;
180         } else {
181             angle = (float) Math.atan(y / x);
182         }
183 
184         if ((angle >= 0) && (x < 0)) {
185             angle = angle - MATH_PI;
186         } else if ((angle < 0) && (x < 0)) {
187             angle = MATH_PI + angle;
188         }
189 
190         if ((angle > ANGLE_BEGIN) || (angle <= ANGLE_BEGIN - ANGLE_SPANNED)) {
191             return false;
192         }
193 
194         this.angle = angle;
195         return true;
196     }
197 
updateProgress(int progress, boolean fromUser)198     private boolean updateProgress(int progress, boolean fromUser) {
199         if ((this.progress != progress) && (progress >= 0) && (progress <= maxProgress)) {
200             this.progress = progress;
201 
202             if (listener != null) {
203                 listener.onProgressChanged((float) progress / maxProgress, fromUser);
204             }
205             return true;
206         }
207         return false;
208     }
209 
updateThumbPositionByProgress()210     private void updateThumbPositionByProgress() {
211         angle = ANGLE_BEGIN - progress * radiantInterval;
212         invalidate();
213     }
214 
updateThumbState(boolean dragThumb)215     private boolean updateThumbState(boolean dragThumb) {
216         if (this.dragThumb == dragThumb) {
217             // The state hasn't been changed; no need for updates.
218             return false;
219         }
220 
221         this.dragThumb = dragThumb;
222         thumb.setState(dragThumb ? PRESSED_ENABLED_STATE_SET : ENABLED_STATE_SET);
223         invalidate();
224         return true;
225     }
226 }
227