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