PieRenderer.java revision e7ff2e945d1a13e4766d55a4679afa83ea9088c3
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(255, 51, 181, 229)); //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 mState = STATE_IDLE; 199 } 200 201 public void showFade() { 202 mShowFade = true; 203 } 204 205 public void addItem(PieItem item) { 206 // add the item to the pie itself 207 mItems.add(item); 208 } 209 210 public void removeItem(PieItem item) { 211 mItems.remove(item); 212 } 213 214 public void clearItems() { 215 mItems.clear(); 216 } 217 218 public void fade() { 219 mShowFade = false; 220 setCenter(mCenterX, mCenterY); 221 Animation anim = new AlphaAnimation(); 222 anim.setFillAfter(true); 223 anim.setAnimationListener(new AnimationListener() { 224 @Override 225 public void onAnimationStart(Animation animation) { 226 mAnimating = true; 227 update(); 228 } 229 @Override 230 public void onAnimationEnd(Animation animation) { 231 show(false); 232 resetAnimation(); 233 } 234 @Override 235 public void onAnimationRepeat(Animation animation) { 236 } 237 }); 238 anim.reset(); 239 anim.setDuration(1000); 240 show(true); 241 mOverlay.startAnimation(anim); 242 } 243 244 private void resetAnimation() { 245 mAlpha = 0f; 246 mAnimating = false; 247 setViewAlpha(mOverlay, 1); 248 } 249 250 /** 251 * guaranteed has center set 252 * @param show 253 */ 254 private void show(boolean show) { 255 if (show) { 256 if (mAnimating) { 257 resetAnimation(); 258 } 259 mState = STATE_PIE; 260 // ensure clean state 261 mAnimating = false; 262 mCurrentItem = null; 263 mOpenItem = null; 264 for (PieItem item : mItems) { 265 item.setSelected(false); 266 } 267 layoutPie(); 268 } else { 269 mState = STATE_IDLE; 270 } 271 setVisible(show); 272 mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE); 273 } 274 275 public void setCenter(int x, int y) { 276 mCenter.x = x; 277 mCenter.y = y; 278 // when using the pie menu, align the focus ring 279 alignFocus(x, y); 280 } 281 282 private void setupPie(int x, int y) { 283 // when using the focus ring, align pie items 284 mCenter.x = x; 285 mCenter.y = y; 286 mAnimating = false; 287 mCurrentItem = null; 288 mOpenItem = null; 289 for (PieItem item : mItems) { 290 item.setSelected(false); 291 } 292 layoutPie(); 293 } 294 295 private void layoutPie() { 296 int rgap = 2; 297 int inner = mRadius + rgap; 298 int outer = mRadius + mRadiusInc - rgap; 299 int gap = 1; 300 layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap); 301 } 302 303 private void layoutItems(List<PieItem> items, float centerAngle, int inner, 304 int outer, int gap) { 305 float emptyangle = PIE_SWEEP / 16; 306 float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size(); 307 float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2; 308 // check if we have custom geometry 309 // first item we find triggers custom sweep for all 310 // this allows us to re-use the path 311 for (PieItem item : items) { 312 if (item.getCenter() >= 0) { 313 sweep = item.getSweep(); 314 break; 315 } 316 } 317 Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap, 318 outer, inner, mCenter); 319 for (PieItem item : items) { 320 // shared between items 321 item.setPath(path); 322 View view = item.getView(); 323 if (item.getCenter() >= 0) { 324 angle = item.getCenter(); 325 } 326 if (view != null) { 327 view.measure(view.getLayoutParams().width, 328 view.getLayoutParams().height); 329 int w = view.getMeasuredWidth(); 330 int h = view.getMeasuredHeight(); 331 // move views to outer border 332 int r = inner + (outer - inner) * 2 / 3; 333 int x = (int) (r * Math.cos(angle)); 334 int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2; 335 x = mCenter.x + x - w / 2; 336 view.layout(x, y, x + w, y + h); 337 } 338 float itemstart = angle - sweep / 2; 339 item.setGeometry(itemstart, sweep, inner, outer); 340 if (item.hasItems()) { 341 layoutItems(item.getItems(), angle, inner, 342 outer + mRadiusInc / 2, gap); 343 } 344 angle += sweep; 345 } 346 } 347 348 private Path makeSlice(float start, float end, int outer, int inner, Point center) { 349 RectF bb = 350 new RectF(center.x - outer, center.y - outer, center.x + outer, 351 center.y + outer); 352 RectF bbi = 353 new RectF(center.x - inner, center.y - inner, center.x + inner, 354 center.y + inner); 355 Path path = new Path(); 356 path.arcTo(bb, start, end - start, true); 357 path.arcTo(bbi, end, start - end); 358 path.close(); 359 return path; 360 } 361 362 /** 363 * converts a 364 * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock) 365 * @return skia angle 366 */ 367 private float getDegrees(double angle) { 368 return (float) (360 - 180 * angle / Math.PI); 369 } 370 371 @Override 372 public void onDraw(Canvas canvas) { 373 drawFocus(canvas); 374 if (mState == STATE_FINISHING) return; 375 if (mAnimating) { 376 setViewAlpha(mOverlay, mAlpha); 377 } 378 if (mOpenItem == null) { 379 // draw base menu 380 for (PieItem item : mItems) { 381 drawItem(canvas, item); 382 } 383 } else { 384 for (PieItem inner : mOpenItem.getItems()) { 385 drawItem(canvas, inner); 386 } 387 } 388 } 389 390 private void drawItem(Canvas canvas, PieItem item) { 391 if (item.getView() != null) { 392 if ((mFocusFromTap && mShowItems) || (mState == STATE_PIE)) { 393 if (item.getPath() != null) { 394 Paint p = item.isSelected() ? mSelectedPaint : mNormalPaint; 395 int state = canvas.save(); 396 float r = getDegrees(item.getStartAngle()); 397 canvas.rotate(r, mCenter.x, mCenter.y); 398 canvas.drawPath(item.getPath(), p); 399 canvas.restoreToCount(state); 400 // draw the item view 401 View view = item.getView(); 402 state = canvas.save(); 403 canvas.translate(view.getX(), view.getY()); 404 view.draw(canvas); 405 canvas.restoreToCount(state); 406 } 407 } else if (mState == STATE_FOCUSING && !mFocusFromTap) { 408 View view = item.getView(); 409 canvas.drawCircle(view.getLeft() + view.getWidth() / 2, 410 view.getTop() + view.getHeight() / 2, mDotRadius, 411 mDotPaint); 412 } 413 } 414 } 415 416 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) 417 private void setViewAlpha(View v, float alpha) { 418 if (ApiHelper.HAS_VIEW_TRANSFORM_PROPERTIES) { 419 v.setAlpha(alpha); 420 } 421 } 422 423 // touch handling for pie 424 425 @Override 426 public boolean onTouchEvent(MotionEvent evt) { 427 float x = evt.getX(); 428 float y = evt.getY(); 429 int action = evt.getActionMasked(); 430 if (MotionEvent.ACTION_DOWN == action) { 431 setCenter((int) x, (int) y); 432 show(true); 433 return true; 434 } else if (MotionEvent.ACTION_UP == action) { 435 if (isVisible()) { 436 PieItem item = mCurrentItem; 437 if (!mAnimating) { 438 deselect(); 439 } 440 show(false); 441 if ((item != null) && (item.getView() != null)) { 442 if ((item == mOpenItem) || !mAnimating) { 443 item.getView().performClick(); 444 } 445 } 446 return true; 447 } 448 } else if (MotionEvent.ACTION_CANCEL == action) { 449 if (isVisible()) { 450 show(false); 451 } 452 if (!mAnimating) { 453 deselect(); 454 } 455 return false; 456 } else if (MotionEvent.ACTION_MOVE == action) { 457 if (mAnimating) return false; 458 PointF polar = getPolar(x, y); 459 if (polar.y < mRadius) { 460 if (mOpenItem != null) { 461 mOpenItem = null; 462 } else if (!mAnimating) { 463 deselect(); 464 } 465 return false; 466 } 467 PieItem item = findItem(polar); 468 if ((item != null) && (mCurrentItem != item)) { 469 onEnter(item); 470 } 471 } 472 return false; 473 } 474 475 /** 476 * enter a slice for a view 477 * updates model only 478 * @param item 479 */ 480 private void onEnter(PieItem item) { 481 if (mCurrentItem != null) { 482 mCurrentItem.setSelected(false); 483 } 484 if (item != null && item.isEnabled()) { 485 item.setSelected(true); 486 mCurrentItem = item; 487 if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) { 488 mHandler.sendEmptyMessageDelayed(MSG_SUBMENU, PIE_OPEN_DELAY); 489 } 490 } else { 491 mCurrentItem = null; 492 } 493 } 494 495 private void deselect() { 496 if (mCurrentItem != null) { 497 mCurrentItem.setSelected(false); 498 mHandler.removeMessages(MSG_SUBMENU); 499 } 500 if (mOpenItem != null) { 501 mOpenItem = null; 502 } 503 mCurrentItem = null; 504 } 505 506 private void openCurrentItem() { 507 if ((mCurrentItem != null) && mCurrentItem.hasItems()) { 508 mOpenItem = mCurrentItem; 509 } 510 } 511 512 private PointF getPolar(float x, float y) { 513 PointF res = new PointF(); 514 // get angle and radius from x/y 515 res.x = (float) Math.PI / 2; 516 x = x - mCenter.x; 517 y = mCenter.y - y; 518 res.y = (float) Math.sqrt(x * x + y * y); 519 if (x != 0) { 520 res.x = (float) Math.atan2(y, x); 521 if (res.x < 0) { 522 res.x = (float) (2 * Math.PI + res.x); 523 } 524 } 525 res.y = res.y + mTouchOffset; 526 return res; 527 } 528 529 /** 530 * @param polar x: angle, y: dist 531 * @return the item at angle/dist or null 532 */ 533 private PieItem findItem(PointF polar) { 534 // find the matching item: 535 List<PieItem> items = (mOpenItem != null) ? mOpenItem.getItems() : mItems; 536 for (PieItem item : items) { 537 if (inside(polar, item)) { 538 return item; 539 } 540 } 541 return null; 542 } 543 544 private boolean inside(PointF polar, PieItem item) { 545 return (item.getInnerRadius() < polar.y) 546 && (item.getOuterRadius() > polar.y) 547 && (item.getStartAngle() < polar.x) 548 && (item.getStartAngle() + item.getSweep() > polar.x); 549 } 550 551 @Override 552 public boolean handlesTouch() { 553 return true; 554 } 555 556 private class AlphaAnimation extends Animation { 557 @Override 558 protected void applyTransformation(float interpolatedTime, Transformation t) { 559 mAlpha = 1 - interpolatedTime; 560 } 561 } 562 563 // focus specific code 564 565 public void setFocus(int x, int y) { 566 mFocusFromTap = true; 567 mShowItems = true; 568 switch(mOverlay.getOrientation()) { 569 case 0: 570 mFocusX = x; 571 mFocusY = y; 572 break; 573 case 180: 574 mFocusX = getWidth() - x; 575 mFocusY = getHeight() - y; 576 break; 577 case 90: 578 mFocusX = getWidth() - y; 579 mFocusY = x; 580 break; 581 case 270: 582 mFocusX = y ; 583 mFocusY = getHeight() - x; 584 break; 585 } 586 setCircle(mFocusX, mFocusY); 587 setupPie(mFocusX, mFocusY); 588 } 589 590 public void alignFocus(int x, int y) { 591 mOverlay.removeCallbacks(mDisappear); 592 mAnimation.cancel(); 593 mAnimation.reset(); 594 mFocusX = x; 595 mFocusY = y; 596 mDialAngle = DIAL_HORIZONTAL; 597 setCircle(x, y); 598 mFocused = false; 599 } 600 601 public int getSize() { 602 return 2 * mCircleSize; 603 } 604 605 private int getRandomAngle() { 606 return (int)(90 * Math.random()); 607 } 608 609 private int getRandomRange() { 610 return (int)(120 * Math.random()); 611 } 612 613 @Override 614 public void layout(int l, int t, int r, int b) { 615 super.layout(l, t, r, b); 616 mCircleSize = Math.min(200, Math.min(getWidth(), getHeight()) / 5); 617 mCenterX = (r - l) / 2; 618 mCenterY = (b - t) / 2; 619 mFocusX = mCenterX; 620 mFocusY = mCenterY; 621 setCircle(mFocusX, mFocusY); 622 if (mShowFade) { 623 fade(); 624 } 625 } 626 627 private void setCircle(int cx, int cy) { 628 mCircle.set(cx - mCircleSize, cy - mCircleSize, 629 cx + mCircleSize, cy + mCircleSize); 630 mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset, 631 cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset); 632 } 633 634 public void drawFocus(Canvas canvas) { 635 mFocusPaint.setStrokeWidth(mOuterStroke); 636 canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint); 637 Paint inner = (mFocused ? mSuccessPaint : mFocusPaint); 638 inner.setStrokeWidth(mInnerStroke); 639 canvas.drawArc(mDial, mDialAngle, 45, false, inner); 640 canvas.drawArc(mDial, mDialAngle + 180, 45, false, inner); 641 drawLine(canvas, mDialAngle, inner); 642 drawLine(canvas, mDialAngle + 45, inner); 643 drawLine(canvas, mDialAngle + 180, inner); 644 drawLine(canvas, mDialAngle + 225, inner); 645 } 646 647 private void drawLine(Canvas canvas, int angle, Paint p) { 648 convertCart(angle, mCircleSize - mInnerOffset, mPoint1); 649 convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2); 650 canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY, 651 mPoint2.x + mFocusX, mPoint2.y + mFocusY, p); 652 } 653 654 private static void convertCart(int angle, int radius, Point out) { 655 double a = 2 * Math.PI * (angle % 360) / 360; 656 out.x = (int) (radius * Math.cos(a) + 0.5); 657 out.y = (int) (radius * Math.sin(a) + 0.5); 658 } 659 660 @Override 661 public void showStart() { 662 if (mAnimating) { 663 mState = STATE_IDLE; 664 resetAnimation(); 665 } 666 if (mState == STATE_IDLE) { 667 if (mFocusFromTap) { 668 mHandler.removeMessages(MSG_FOCUS_TAP); 669 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_FOCUS_TAP), 670 FOCUS_TAP_TIMEOUT); 671 } 672 int angle = getRandomAngle(); 673 int range = getRandomRange(); 674 startAnimation(R.drawable.ic_focus_focusing, SCALING_UP_TIME, 675 false, angle, angle + range); 676 mState = STATE_FOCUSING; 677 mStartAnimationAngle = angle; 678 } 679 } 680 681 @Override 682 public void showSuccess(boolean timeout) { 683 if (mState == STATE_FOCUSING) { 684 startAnimation(R.drawable.ic_focus_focused, SCALING_DOWN_TIME, 685 timeout, mStartAnimationAngle); 686 mState = STATE_FINISHING; 687 mFocused = true; 688 } 689 } 690 691 @Override 692 public void showFail(boolean timeout) { 693 if (mState == STATE_FOCUSING) { 694 startAnimation(R.drawable.ic_focus_failed, SCALING_DOWN_TIME, 695 timeout, mStartAnimationAngle); 696 mState = STATE_FINISHING; 697 mFocused = false; 698 } 699 } 700 701 @Override 702 public void clear() { 703 mAnimation.cancel(); 704 mFocused = false; 705 mFocusFromTap = false; 706 mOverlay.removeCallbacks(mDisappear); 707 mDisappear.run(); 708 } 709 710 private void startAnimation(int resid, long duration, boolean timeout, 711 float toScale) { 712 startAnimation(resid, duration, timeout, mDialAngle, 713 toScale); 714 } 715 716 private void startAnimation(int resid, long duration, boolean timeout, 717 float fromScale, float toScale) { 718 setVisible(true); 719 mAnimation.cancel(); 720 mAnimation.reset(); 721 mAnimation.setDuration(duration); 722 mAnimation.setScale(fromScale, toScale); 723 mAnimation.setAnimationListener(timeout ? mEndAction : null); 724 mOverlay.startAnimation(mAnimation); 725 update(); 726 } 727 728 private class EndAction implements Animation.AnimationListener { 729 @Override 730 public void onAnimationEnd(Animation animation) { 731 // Keep the focus indicator for some time. 732 mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT); 733 } 734 735 @Override 736 public void onAnimationRepeat(Animation animation) { 737 } 738 739 @Override 740 public void onAnimationStart(Animation animation) { 741 } 742 } 743 744 private class Disappear implements Runnable { 745 @Override 746 public void run() { 747 setVisible(false); 748 mFocusX = mCenterX; 749 mFocusY = mCenterY; 750 mState = STATE_IDLE; 751 setCircle(mFocusX, mFocusY); 752 setupPie(mFocusX, mFocusY); 753 mFocused = false; 754 } 755 } 756 757 private class ScaleAnimation extends Animation { 758 private float mFrom = 1f; 759 private float mTo = 1f; 760 761 public ScaleAnimation() { 762 setFillAfter(true); 763 } 764 765 public void setScale(float from, float to) { 766 mFrom = from; 767 mTo = to; 768 } 769 770 @Override 771 protected void applyTransformation(float interpolatedTime, Transformation t) { 772 mDialAngle = (int)(mFrom + (mTo - mFrom) * interpolatedTime); 773 } 774 } 775 776} 777