PieRenderer.java revision a0dd52298b55357a711663180677efa39d35e5ab
1/*
2 * Copyright (C) 2012 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 android.annotation.TargetApi;
20import android.content.Context;
21import android.content.res.Resources;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Paint;
25import android.graphics.Path;
26import android.graphics.Point;
27import android.graphics.PointF;
28import android.graphics.RectF;
29import android.os.Handler;
30import android.os.Message;
31import android.util.Log;
32import android.view.MotionEvent;
33import android.view.View;
34import android.view.animation.Animation;
35import android.view.animation.Animation.AnimationListener;
36import android.view.animation.Transformation;
37
38import com.android.camera.R;
39import com.android.gallery3d.common.ApiHelper;
40
41import java.util.ArrayList;
42import java.util.List;
43
44public class PieRenderer extends OverlayRenderer
45        implements FocusIndicator {
46
47    private static final String TAG = "CAM Pie";
48
49    // Sometimes continuous autofocus starts and stops several times quickly.
50    // These states are used to make sure the animation is run for at least some
51    // time.
52    private int mState;
53    private ScaleAnimation mAnimation = new ScaleAnimation();
54    private static final int STATE_IDLE = 0;
55    private static final int STATE_FOCUSING = 1;
56    private static final int STATE_FINISHING = 2;
57    private static final int STATE_PIE = 3;
58
59    private Runnable mDisappear = new Disappear();
60    private Animation.AnimationListener mEndAction = new EndAction();
61    private static final int SCALING_UP_TIME = 1000;
62    private static final int SCALING_DOWN_TIME = 200;
63    private static final int DISAPPEAR_TIMEOUT = 200;
64    private static final int DIAL_HORIZONTAL = 157;
65
66    private static final long PIE_OPEN_DELAY = 200;
67    private static final long FOCUS_TAP_TIMEOUT = 200;
68
69    private static final int MSG_OPEN = 2;
70    private static final int MSG_CLOSE = 3;
71    private static final int MSG_SUBMENU = 4;
72    private static final int MSG_FOCUS_TAP = 5;
73    private static final float PIE_SWEEP = (float)(Math.PI * 2 / 3);
74    // geometry
75    private Point mCenter;
76    private int mRadius;
77    private int mRadiusInc;
78    private int mSlop;
79
80    // the detection if touch is inside a slice is offset
81    // inbounds by this amount to allow the selection to show before the
82    // finger covers it
83    private int mTouchOffset;
84
85    private List<PieItem> mItems;
86
87    private PieItem mOpenItem;
88
89    private Paint mNormalPaint;
90    private Paint mSelectedPaint;
91    private Paint mSubPaint;
92
93    // touch handling
94    private PieItem mCurrentItem;
95
96    private boolean mAnimating;
97    private float mAlpha;
98
99    private Paint mFocusPaint;
100    private Paint mSuccessPaint;
101    private Paint mDotPaint;
102    private int mCircleSize;
103    private int mDotRadius;
104    private int mFocusX;
105    private int mFocusY;
106    private int mCenterX;
107    private int mCenterY;
108
109    private int mDialAngle;
110    private RectF mCircle;
111    private RectF mDial;
112    private Point mPoint1;
113    private Point mPoint2;
114    private int mStartAnimationAngle;
115    private boolean mFocused;
116    private int mInnerOffset;
117    private int mOuterStroke;
118    private int mInnerStroke;
119    private boolean mShowFade = true;
120    private boolean mFocusFromTap;
121    private boolean mShowItems;
122
123
124    private Handler mHandler = new Handler() {
125        public void handleMessage(Message msg) {
126            switch(msg.what) {
127            case MSG_OPEN:
128                if (mListener != null && !mAnimating) {
129                    mListener.onPieOpened(mCenter.x, mCenter.y);
130                }
131                break;
132            case MSG_CLOSE:
133                if (mListener != null && !mAnimating) {
134                    mListener.onPieClosed();
135                }
136                break;
137            case MSG_SUBMENU:
138                openCurrentItem();
139                break;
140            case MSG_FOCUS_TAP:
141                // reset flag
142                mShowItems = false;
143                break;
144            }
145        }
146    };
147
148    private PieListener mListener;
149
150    static public interface PieListener {
151        public void onPieOpened(int centerX, int centerY);
152        public void onPieClosed();
153    }
154
155    public void setPieListener(PieListener pl) {
156        mListener = pl;
157    }
158
159    public PieRenderer(Context context) {
160        init(context);
161    }
162    private void init(Context ctx) {
163        setVisible(false);
164        mItems = new ArrayList<PieItem>();
165        Resources res = ctx.getResources();
166        mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
167        mRadiusInc =  (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
168        mSlop = (int) res.getDimensionPixelSize(R.dimen.pie_touch_slop);
169        mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
170        mCenter = new Point(0,0);
171        mNormalPaint = new Paint();
172        mNormalPaint.setColor(Color.argb(0, 0, 0, 0));
173        mNormalPaint.setAntiAlias(true);
174        mSelectedPaint = new Paint();
175        mSelectedPaint.setColor(Color.argb(128, 0, 0, 0)); //res.getColor(R.color.qc_selected));
176        mSelectedPaint.setAntiAlias(true);
177        mSubPaint = new Paint();
178        mSubPaint.setAntiAlias(true);
179        mSubPaint.setColor(Color.argb(200, 250, 230, 128)); //res.getColor(R.color.qc_sub));
180        mFocusPaint = new Paint();
181        mFocusPaint.setAntiAlias(true);
182        mFocusPaint.setColor(Color.WHITE);
183        mFocusPaint.setStyle(Paint.Style.STROKE);
184        mSuccessPaint = new Paint(mFocusPaint);
185        mSuccessPaint.setColor(Color.GREEN);
186        mDotPaint = new Paint();
187        mDotPaint.setAntiAlias(true);
188        mDotPaint.setColor(Color.argb(160, 255, 255, 255));
189        mDotPaint.setStyle(Paint.Style.FILL);
190        mCircle = new RectF();
191        mDial = new RectF();
192        mPoint1 = new Point();
193        mPoint2 = new Point();
194        mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset);
195        mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
196        mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
197        mDotRadius = res.getDimensionPixelSize(R.dimen.focus_dot_radius);
198        setVisible(false);
199        mState = STATE_IDLE;
200    }
201
202    public void showFade() {
203        mShowFade = true;
204    }
205
206    public void addItem(PieItem item) {
207        // add the item to the pie itself
208        mItems.add(item);
209    }
210
211    public void removeItem(PieItem item) {
212        mItems.remove(item);
213    }
214
215    public void clearItems() {
216        mItems.clear();
217    }
218
219    public void fade() {
220        mShowFade = false;
221        setCenter(mCenterX, mCenterY);
222        Animation anim = new AlphaAnimation();
223        anim.setFillAfter(true);
224        anim.setAnimationListener(new AnimationListener() {
225            @Override
226            public void onAnimationStart(Animation animation) {
227                mAnimating = true;
228                update();
229            }
230            @Override
231            public void onAnimationEnd(Animation animation) {
232                show(false);
233                mAlpha = 0f;
234                mAnimating = false;
235                setViewAlpha(mOverlay, 1);
236            }
237            @Override
238            public void onAnimationRepeat(Animation animation) {
239            }
240        });
241        anim.reset();
242        anim.setDuration(1000);
243        show(true);
244        mOverlay.startAnimation(anim);
245    }
246
247    /**
248     * guaranteed has center set
249     * @param show
250     */
251    private void show(boolean show) {
252        if (show) {
253            mState = STATE_PIE;
254            // ensure clean state
255            mAnimating = false;
256            mCurrentItem = null;
257            mOpenItem = null;
258            for (PieItem item : mItems) {
259                item.setSelected(false);
260            }
261            layoutPie();
262        }
263        setVisible(show);
264        mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE);
265    }
266
267    public void setCenter(int x, int y) {
268        mCenter.x = x;
269        mCenter.y = y;
270        // when using the pie menu, align the focus ring
271        alignFocus(x, y);
272    }
273
274    private void setupPie(int x, int y) {
275        // when using the focus ring, align pie items
276        mCenter.x = x;
277        mCenter.y = y;
278        mAnimating = false;
279        mCurrentItem = null;
280        mOpenItem = null;
281        for (PieItem item : mItems) {
282            item.setSelected(false);
283        }
284        layoutPie();
285    }
286
287    private void layoutPie() {
288        int rgap = 2;
289        int inner = mRadius + rgap;
290        int outer = mRadius + mRadiusInc - rgap;
291        int gap = 1;
292        layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap);
293    }
294
295    private void layoutItems(List<PieItem> items, float centerAngle, int inner,
296            int outer, int gap) {
297        float emptyangle = PIE_SWEEP / 16;
298        float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size();
299        float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2;
300        // check if we have custom geometry
301        // first item we find triggers custom sweep for all
302        // this allows us to re-use the path
303        for (PieItem item : items) {
304            if (item.getCenter() >= 0) {
305                sweep = item.getSweep();
306                break;
307            }
308        }
309        Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap,
310                outer, inner, mCenter);
311        for (PieItem item : items) {
312            // shared between items
313            item.setPath(path);
314            View view = item.getView();
315            if (item.getCenter() >= 0) {
316                angle = item.getCenter();
317            }
318            if (view != null) {
319                view.measure(view.getLayoutParams().width,
320                        view.getLayoutParams().height);
321                int w = view.getMeasuredWidth();
322                int h = view.getMeasuredHeight();
323                // move views to outer border
324                int r = inner + (outer - inner) * 2 / 3;
325                int x = (int) (r * Math.cos(angle));
326                int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2;
327                x = mCenter.x + x - w / 2;
328                view.layout(x, y, x + w, y + h);
329            }
330            float itemstart = angle - sweep / 2;
331            item.setGeometry(itemstart, sweep, inner, outer);
332            if (item.hasItems()) {
333                layoutItems(item.getItems(), angle, inner,
334                        outer + mRadiusInc / 2, gap);
335            }
336            angle += sweep;
337        }
338    }
339
340    private Path makeSlice(float start, float end, int outer, int inner, Point center) {
341        outer = inner + (outer - inner) * 2 / 3;
342        RectF bb =
343                new RectF(center.x - outer, center.y - outer, center.x + outer,
344                        center.y + outer);
345        RectF bbi =
346                new RectF(center.x - inner, center.y - inner, center.x + inner,
347                        center.y + inner);
348        Path path = new Path();
349        path.arcTo(bb, start, end - start, true);
350        path.arcTo(bbi, end, start - end);
351        path.close();
352        return path;
353    }
354
355    /**
356     * converts a
357     * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
358     * @return skia angle
359     */
360    private float getDegrees(double angle) {
361        return (float) (360 - 180 * angle / Math.PI);
362    }
363
364    @Override
365    public void onDraw(Canvas canvas) {
366        drawFocus(canvas);
367        if (mState == STATE_FINISHING) return;
368        if (mAnimating) {
369            setViewAlpha(mOverlay, mAlpha);
370        }
371        if (mOpenItem == null) {
372            // draw base menu
373            for (PieItem item : mItems) {
374                drawItem(canvas, item);
375            }
376        } else {
377            for (PieItem inner : mOpenItem.getItems()) {
378                drawItem(canvas, inner);
379            }
380        }
381    }
382
383    private void drawItem(Canvas canvas, PieItem item) {
384        if (item.getView() != null) {
385            if ((mFocusFromTap && mShowItems) || (mState == STATE_PIE)) {
386                if (item.getPath() != null) {
387                    Paint p = item.isSelected() ? mSelectedPaint : mNormalPaint;
388                    int state = canvas.save();
389                    float r = getDegrees(item.getStartAngle());
390                    canvas.rotate(r, mCenter.x, mCenter.y);
391                    canvas.drawPath(item.getPath(), p);
392                    canvas.restoreToCount(state);
393                    // draw the item view
394                    View view = item.getView();
395                    state = canvas.save();
396                    canvas.translate(view.getX(), view.getY());
397                    view.draw(canvas);
398                    canvas.restoreToCount(state);
399                }
400            } else if (mState == STATE_FOCUSING && !mFocusFromTap) {
401                View view = item.getView();
402                canvas.drawCircle(view.getLeft() + view.getWidth() / 2,
403                        view.getTop() + view.getHeight() / 2, mDotRadius,
404                        mDotPaint);
405            }
406        }
407    }
408
409    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
410    private void setViewAlpha(View v, float alpha) {
411        if (ApiHelper.HAS_VIEW_TRANSFORM_PROPERTIES) {
412            v.setAlpha(alpha);
413        }
414    }
415
416    // touch handling for pie
417
418    @Override
419    public boolean onTouchEvent(MotionEvent evt) {
420        float x = evt.getX();
421        float y = evt.getY();
422        int action = evt.getActionMasked();
423        if (MotionEvent.ACTION_DOWN == action) {
424            setCenter((int) x, (int) y);
425            show(true);
426            return true;
427        } else if (MotionEvent.ACTION_UP == action) {
428            if (isVisible()) {
429                PieItem item = mCurrentItem;
430                if (!mAnimating) {
431                    deselect();
432                }
433                show(false);
434                if ((item != null) && (item.getView() != null)) {
435                    if ((item == mOpenItem) || !mAnimating) {
436                        item.getView().performClick();
437                    }
438                }
439                return true;
440            }
441        } else if (MotionEvent.ACTION_CANCEL == action) {
442            if (isVisible()) {
443                show(false);
444            }
445            if (!mAnimating) {
446                deselect();
447            }
448            return false;
449        } else if (MotionEvent.ACTION_MOVE == action) {
450            if (mAnimating) return false;
451            PointF polar = getPolar(x, y);
452            int maxr = mRadius + mRadiusInc + 50;
453            if (polar.y < mRadius) {
454                if (mOpenItem != null) {
455                    mOpenItem = null;
456                } else if (!mAnimating) {
457                    deselect();
458                }
459                return false;
460            }
461            if (polar.y > maxr) {
462                deselect();
463                show(false);
464                evt.setAction(MotionEvent.ACTION_DOWN);
465                return false;
466            }
467            PieItem item = findItem(polar);
468            if (item == null) {
469            } else if (mCurrentItem != item) {
470                onEnter(item);
471            }
472        }
473        return false;
474    }
475
476    /**
477     * enter a slice for a view
478     * updates model only
479     * @param item
480     */
481    private void onEnter(PieItem item) {
482        if (mCurrentItem != null) {
483            mCurrentItem.setSelected(false);
484        }
485        if (item != null && item.isEnabled()) {
486            item.setSelected(true);
487            mCurrentItem = item;
488            if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) {
489                mHandler.sendEmptyMessageDelayed(MSG_SUBMENU, PIE_OPEN_DELAY);
490            }
491        } else {
492            mCurrentItem = null;
493        }
494    }
495
496    private void deselect() {
497        if (mCurrentItem != null) {
498            mCurrentItem.setSelected(false);
499            mHandler.removeMessages(MSG_SUBMENU);
500        }
501        if (mOpenItem != null) {
502            mOpenItem = null;
503        }
504        mCurrentItem = null;
505    }
506
507    private void openCurrentItem() {
508        if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
509            mOpenItem = mCurrentItem;
510        }
511    }
512
513    private PointF getPolar(float x, float y) {
514        PointF res = new PointF();
515        // get angle and radius from x/y
516        res.x = (float) Math.PI / 2;
517        x = x - mCenter.x;
518        y = mCenter.y - y;
519        res.y = (float) Math.sqrt(x * x + y * y);
520        if (x != 0) {
521            res.x = (float) Math.atan2(y,  x);
522            if (res.x < 0) {
523                res.x = (float) (2 * Math.PI + res.x);
524            }
525        }
526        res.y = res.y + mTouchOffset;
527        return res;
528    }
529
530    /**
531     * @param polar x: angle, y: dist
532     * @return the item at angle/dist or null
533     */
534    private PieItem findItem(PointF polar) {
535        // find the matching item:
536        List<PieItem> items = (mOpenItem != null) ? mOpenItem.getItems() : mItems;
537        for (PieItem item : items) {
538            if (inside(polar, item)) {
539                return item;
540            }
541        }
542        return null;
543    }
544
545    private boolean inside(PointF polar, PieItem item) {
546        return (item.getInnerRadius() < polar.y)
547        && (item.getOuterRadius() > polar.y)
548        && (item.getStartAngle() < polar.x)
549        && (item.getStartAngle() + item.getSweep() > polar.x);
550    }
551
552    @Override
553    public boolean handlesTouch() {
554        return true;
555    }
556
557    private class AlphaAnimation extends Animation {
558        @Override
559        protected void applyTransformation(float interpolatedTime, Transformation t) {
560            mAlpha = 1 - interpolatedTime;
561        }
562    }
563
564    // focus specific code
565
566    public void setFocus(int x, int y) {
567        mFocusFromTap = true;
568        mShowItems = true;
569        switch(mOverlay.getOrientation()) {
570        case 0:
571            mFocusX = x;
572            mFocusY = y;
573            break;
574        case 180:
575            mFocusX = getWidth() - x;
576            mFocusY = getHeight() - y;
577            break;
578        case 90:
579            mFocusX = getWidth() - y;
580            mFocusY = x;
581            break;
582        case 270:
583            mFocusX = y ;
584            mFocusY = getHeight() - x;
585            break;
586        }
587        setCircle(mFocusX, mFocusY);
588        setupPie(mFocusX, mFocusY);
589    }
590
591    public void alignFocus(int x, int y) {
592        mOverlay.removeCallbacks(mDisappear);
593        mAnimation.cancel();
594        mAnimation.reset();
595        mFocusX = x;
596        mFocusY = y;
597        mDialAngle = DIAL_HORIZONTAL;
598        setCircle(x, y);
599        mFocused = false;
600    }
601
602    public int getSize() {
603        return 2 * mCircleSize;
604    }
605
606    private int getRandomAngle() {
607        return (int)(90 * Math.random());
608    }
609
610    private int getRandomRange() {
611        return (int)(120 * Math.random());
612    }
613
614    @Override
615    public void layout(int l, int t, int r, int b) {
616        super.layout(l, t, r, b);
617        mCircleSize = Math.min(200, Math.min(getWidth(), getHeight()) / 5);
618        mCenterX = (r - l) / 2;
619        mCenterY = (b - t) / 2;
620        mFocusX = mCenterX;
621        mFocusY = mCenterY;
622        setCircle(mFocusX, mFocusY);
623        if (mShowFade) {
624            fade();
625        }
626    }
627
628    private void setCircle(int cx, int cy) {
629        mCircle.set(cx - mCircleSize, cy - mCircleSize,
630                cx + mCircleSize, cy + mCircleSize);
631        mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset,
632                cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset);
633    }
634
635    public void drawFocus(Canvas canvas) {
636        mFocusPaint.setStrokeWidth(mOuterStroke);
637        canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint);
638        Paint inner = (mFocused ? mSuccessPaint : mFocusPaint);
639        inner.setStrokeWidth(mInnerStroke);
640        canvas.drawArc(mDial, mDialAngle, 45, false, inner);
641        canvas.drawArc(mDial, mDialAngle + 180, 45, false, inner);
642        drawLine(canvas, mDialAngle, inner);
643        drawLine(canvas, mDialAngle + 45, inner);
644        drawLine(canvas, mDialAngle + 180, inner);
645        drawLine(canvas, mDialAngle + 225, inner);
646    }
647
648    private void drawLine(Canvas canvas, int angle, Paint p) {
649        convertCart(angle, mCircleSize - mInnerOffset, mPoint1);
650        convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2);
651        canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY,
652                mPoint2.x + mFocusX, mPoint2.y + mFocusY, p);
653    }
654
655    private static void convertCart(int angle, int radius, Point out) {
656        double a = 2 * Math.PI * (angle % 360) / 360;
657        out.x = (int) (radius * Math.cos(a) + 0.5);
658        out.y = (int) (radius * Math.sin(a) + 0.5);
659    }
660
661    @Override
662    public void showStart() {
663        if (mState == STATE_IDLE) {
664            if (mFocusFromTap) {
665                mHandler.removeMessages(MSG_FOCUS_TAP);
666                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_FOCUS_TAP),
667                        FOCUS_TAP_TIMEOUT);
668            }
669            int angle = getRandomAngle();
670            int range = getRandomRange();
671            startAnimation(R.drawable.ic_focus_focusing, SCALING_UP_TIME,
672                    false, angle, angle + range);
673            mState = STATE_FOCUSING;
674            mStartAnimationAngle = angle;
675        }
676    }
677
678    @Override
679    public void showSuccess(boolean timeout) {
680        if (mState == STATE_FOCUSING) {
681            startAnimation(R.drawable.ic_focus_focused, SCALING_DOWN_TIME,
682                    timeout, mStartAnimationAngle);
683            mState = STATE_FINISHING;
684            mFocused = true;
685        }
686    }
687
688    @Override
689    public void showFail(boolean timeout) {
690        if (mState == STATE_FOCUSING) {
691            startAnimation(R.drawable.ic_focus_failed, SCALING_DOWN_TIME,
692                    timeout, mStartAnimationAngle);
693            mState = STATE_FINISHING;
694            mFocused = false;
695        }
696    }
697
698    @Override
699    public void clear() {
700        mAnimation.cancel();
701        mFocused = false;
702        mFocusFromTap = false;
703        mOverlay.removeCallbacks(mDisappear);
704        mDisappear.run();
705    }
706
707    private void startAnimation(int resid, long duration, boolean timeout,
708            float toScale) {
709        startAnimation(resid, duration, timeout, mDialAngle,
710                toScale);
711    }
712
713    private void startAnimation(int resid, long duration, boolean timeout,
714            float fromScale, float toScale) {
715        setVisible(true);
716        mAnimation.cancel();
717        mAnimation.reset();
718        mAnimation.setDuration(duration);
719        mAnimation.setScale(fromScale, toScale);
720        mAnimation.setAnimationListener(timeout ? mEndAction : null);
721        mOverlay.startAnimation(mAnimation);
722        update();
723    }
724
725    private class EndAction implements Animation.AnimationListener {
726        @Override
727        public void onAnimationEnd(Animation animation) {
728            // Keep the focus indicator for some time.
729            mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT);
730        }
731
732        @Override
733        public void onAnimationRepeat(Animation animation) {
734        }
735
736        @Override
737        public void onAnimationStart(Animation animation) {
738        }
739    }
740
741    private class Disappear implements Runnable {
742        @Override
743        public void run() {
744            setVisible(false);
745            mFocusX = mCenterX;
746            mFocusY = mCenterY;
747            mState = STATE_IDLE;
748            setCircle(mFocusX, mFocusY);
749            setupPie(mFocusX, mFocusY);
750            mFocused = false;
751        }
752    }
753
754    private class ScaleAnimation extends Animation {
755        private float mFrom = 1f;
756        private float mTo = 1f;
757
758        public ScaleAnimation() {
759            setFillAfter(true);
760        }
761
762        public void setScale(float from, float to) {
763            mFrom = from;
764            mTo = to;
765        }
766
767        @Override
768        protected void applyTransformation(float interpolatedTime, Transformation t) {
769            mDialAngle = (int)(mFrom + (mTo - mFrom) * interpolatedTime);
770        }
771    }
772
773}
774