1package com.xtremelabs.robolectric.shadows; 2 3import static com.xtremelabs.robolectric.Robolectric.shadowOf; 4import static com.xtremelabs.robolectric.Robolectric.Reflection.newInstanceOf; 5 6import android.content.Context; 7import android.content.res.Resources; 8import android.graphics.Bitmap; 9import android.graphics.Point; 10import android.graphics.drawable.ColorDrawable; 11import android.graphics.drawable.Drawable; 12import android.util.AttributeSet; 13import android.view.KeyEvent; 14import android.view.MotionEvent; 15import android.view.View; 16import android.view.View.MeasureSpec; 17import android.view.ViewGroup; 18import android.view.ViewParent; 19import android.view.ViewTreeObserver; 20import android.view.animation.Animation; 21 22import com.xtremelabs.robolectric.Robolectric; 23import com.xtremelabs.robolectric.internal.Implementation; 24import com.xtremelabs.robolectric.internal.Implements; 25import com.xtremelabs.robolectric.internal.RealObject; 26 27import java.io.PrintStream; 28import java.lang.reflect.InvocationTargetException; 29import java.lang.reflect.Method; 30import java.util.HashMap; 31import java.util.Map; 32 33/** 34 * Shadow implementation of {@code View} that simulates the behavior of this 35 * class. 36 * <p/> 37 * Supports listeners, focusability (but not focus order), resource loading, 38 * visibility, onclick, tags, and tracks the size and shape of the view. 39 */ 40@SuppressWarnings({"UnusedDeclaration"}) 41@Implements(View.class) 42public class ShadowView { 43 @RealObject 44 protected View realView; 45 46 private int id; 47 ShadowView parent; 48 protected Context context; 49 private boolean selected; 50 private View.OnClickListener onClickListener; 51 private View.OnLongClickListener onLongClickListener; 52 private Object tag; 53 private boolean enabled = true; 54 private int visibility = View.VISIBLE; 55 int left; 56 int top; 57 int right; 58 int bottom; 59 private int paddingLeft; 60 private int paddingTop; 61 private int paddingRight; 62 private int paddingBottom; 63 private ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(0, 0); 64 private final Map<Integer, Object> tags = new HashMap<Integer, Object>(); 65 private boolean clickable; 66 protected boolean focusable; 67 boolean focusableInTouchMode; 68 private int backgroundResourceId = -1; 69 private int backgroundColor; 70 protected View.OnKeyListener onKeyListener; 71 private boolean isFocused; 72 private View.OnFocusChangeListener onFocusChangeListener; 73 private boolean wasInvalidated; 74 private View.OnTouchListener onTouchListener; 75 protected AttributeSet attributeSet; 76 private boolean drawingCacheEnabled; 77 public Point scrollToCoordinates; 78 private boolean didRequestLayout; 79 private Drawable background; 80 private Animation animation; 81 private ViewTreeObserver viewTreeObserver; 82 private MotionEvent lastTouchEvent; 83 private int nextFocusDownId = View.NO_ID; 84 private CharSequence contentDescription = null; 85 private int measuredWidth = 0; 86 private int measuredHeight = 0; 87 88 public void __constructor__(Context context) { 89 __constructor__(context, null); 90 } 91 92 public void __constructor__(Context context, AttributeSet attributeSet) { 93 __constructor__(context, attributeSet, 0); 94 } 95 96 public void __constructor__(Context context, AttributeSet attributeSet, int defStyle) { 97 this.context = context; 98 this.attributeSet = attributeSet; 99 100 if (attributeSet != null) { 101 applyAttributes(); 102 } 103 } 104 105 public void applyAttributes() { 106 applyIdAttribute(); 107 applyVisibilityAttribute(); 108 applyEnabledAttribute(); 109 applyBackgroundAttribute(); 110 applyTagAttribute(); 111 applyOnClickAttribute(); 112 applyContentDescriptionAttribute(); 113 } 114 115 @Implementation 116 public void setId(int id) { 117 this.id = id; 118 } 119 120 @Implementation 121 public void setClickable(boolean clickable) { 122 this.clickable = clickable; 123 } 124 125 /** 126 * Also sets focusable in touch mode to false if {@code focusable} is false, which is the Android behavior. 127 * 128 * @param focusable the new status of the {@code View}'s focusability 129 */ 130 @Implementation 131 public void setFocusable(boolean focusable) { 132 this.focusable = focusable; 133 if (!focusable) { 134 setFocusableInTouchMode(false); 135 } 136 } 137 138 @Implementation 139 public final boolean isFocusableInTouchMode() { 140 return focusableInTouchMode; 141 } 142 143 /** 144 * Also sets focusable to true if {@code focusableInTouchMode} is true, which is the Android behavior. 145 * 146 * @param focusableInTouchMode the new status of the {@code View}'s touch mode focusability 147 */ 148 @Implementation 149 public void setFocusableInTouchMode(boolean focusableInTouchMode) { 150 this.focusableInTouchMode = focusableInTouchMode; 151 if (focusableInTouchMode) { 152 setFocusable(true); 153 } 154 } 155 156 @Implementation(i18nSafe = false) 157 public void setContentDescription(CharSequence contentDescription) { 158 this.contentDescription = contentDescription; 159 } 160 161 @Implementation 162 public boolean isFocusable() { 163 return focusable; 164 } 165 166 @Implementation 167 public int getId() { 168 return id; 169 } 170 171 @Implementation 172 public CharSequence getContentDescription() { 173 return contentDescription; 174 } 175 176 /** 177 * Simulates the inflating of the requested resource. 178 * 179 * @param context the context from which to obtain a layout inflater 180 * @param resource the ID of the resource to inflate 181 * @param root the {@code ViewGroup} to add the inflated {@code View} to 182 * @return the inflated View 183 */ 184 @Implementation 185 public static View inflate(Context context, int resource, ViewGroup root) { 186 return ShadowLayoutInflater.from(context).inflate(resource, root); 187 } 188 189 /** 190 * Finds this {@code View} if it's ID is passed in, returns {@code null} otherwise 191 * 192 * @param id the id of the {@code View} to find 193 * @return the {@code View}, if found, {@code null} otherwise 194 */ 195 @Implementation 196 public View findViewById(int id) { 197 if (id == this.id) { 198 return realView; 199 } 200 201 return null; 202 } 203 204 @Implementation 205 public View findViewWithTag(Object obj) { 206 if (obj.equals(realView.getTag())) { 207 return realView; 208 } 209 210 return null; 211 } 212 213 @Implementation 214 public View getRootView() { 215 ShadowView root = this; 216 while (root.parent != null) { 217 root = root.parent; 218 } 219 return root.realView; 220 } 221 222 @Implementation 223 public ViewGroup.LayoutParams getLayoutParams() { 224 return layoutParams; 225 } 226 227 @Implementation 228 public void setLayoutParams(ViewGroup.LayoutParams params) { 229 layoutParams = params; 230 } 231 232 @Implementation 233 public final ViewParent getParent() { 234 return parent == null ? null : (ViewParent) parent.realView; 235 } 236 237 @Implementation 238 public final Context getContext() { 239 return context; 240 } 241 242 @Implementation 243 public Resources getResources() { 244 return context.getResources(); 245 } 246 247 @Implementation 248 public void setBackgroundResource(int backgroundResourceId) { 249 this.backgroundResourceId = backgroundResourceId; 250 setBackgroundDrawable(getResources().getDrawable(backgroundResourceId)); 251 } 252 253 /** 254 * Non-Android accessor. 255 * 256 * @return the resource ID of this views background 257 */ 258 public int getBackgroundResourceId() { 259 return backgroundResourceId; 260 } 261 262 @Implementation 263 public void setBackgroundColor(int color) { 264 backgroundColor = color; 265 setBackgroundDrawable(new ColorDrawable(getResources().getColor(color))); 266 } 267 268 /** 269 * Non-Android accessor. 270 * 271 * @return the resource color ID of this views background 272 */ 273 public int getBackgroundColor() { 274 return backgroundColor; 275 } 276 277 @Implementation 278 public void setBackgroundDrawable(Drawable d) { 279 this.background = d; 280 } 281 282 @Implementation 283 public Drawable getBackground() { 284 return background; 285 } 286 287 @Implementation 288 public int getVisibility() { 289 return visibility; 290 } 291 292 @Implementation 293 public void setVisibility(int visibility) { 294 this.visibility = visibility; 295 } 296 297 @Implementation 298 public void setSelected(boolean selected) { 299 this.selected = selected; 300 } 301 302 @Implementation 303 public boolean isSelected() { 304 return this.selected; 305 } 306 307 @Implementation 308 public boolean isEnabled() { 309 return this.enabled; 310 } 311 312 @Implementation 313 public void setEnabled(boolean enabled) { 314 this.enabled = enabled; 315 } 316 317 @Implementation 318 public void setOnClickListener(View.OnClickListener onClickListener) { 319 this.onClickListener = onClickListener; 320 } 321 322 @Implementation 323 public boolean performClick() { 324 if (onClickListener != null) { 325 onClickListener.onClick(realView); 326 return true; 327 } else { 328 return false; 329 } 330 } 331 332 @Implementation 333 public void setOnLongClickListener(View.OnLongClickListener onLongClickListener) { 334 this.onLongClickListener = onLongClickListener; 335 } 336 337 @Implementation 338 public boolean performLongClick() { 339 if (onLongClickListener != null) { 340 onLongClickListener.onLongClick(realView); 341 return true; 342 } else { 343 return false; 344 } 345 } 346 347 @Implementation 348 public void setOnKeyListener(View.OnKeyListener onKeyListener) { 349 this.onKeyListener = onKeyListener; 350 } 351 352 @Implementation 353 public Object getTag() { 354 return this.tag; 355 } 356 357 @Implementation 358 public void setTag(Object tag) { 359 this.tag = tag; 360 } 361 362 @Implementation 363 public final int getHeight() { 364 return bottom - top; 365 } 366 367 @Implementation 368 public final int getWidth() { 369 return right - left; 370 } 371 372 @Implementation 373 public final int getMeasuredWidth() { 374 return measuredWidth; 375 } 376 377 @Implementation 378 public final int getMeasuredHeight() { 379 return measuredHeight; 380 } 381 382 @Implementation 383 public final void setMeasuredDimension(int measuredWidth, int measuredHeight) { 384 this.measuredWidth = measuredWidth; 385 this.measuredHeight = measuredHeight; 386 } 387 388 @Implementation 389 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 390 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 391 MeasureSpec.getSize(heightMeasureSpec)); 392 } 393 394 @Implementation 395 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 396 // We really want to invoke the onMeasure method of the real view, 397 // as the real View likely contains an implementation of onMeasure 398 // worthy of test, rather the default shadow implementation. 399 // But Android declares onMeasure as protected. 400 try { 401 Method onMeasureMethod = realView.getClass().getDeclaredMethod("onMeasure", Integer.TYPE, Integer.TYPE ); 402 onMeasureMethod.setAccessible(true); 403 onMeasureMethod.invoke( realView, widthMeasureSpec, heightMeasureSpec ); 404 } catch ( NoSuchMethodException e ) { 405 // use default shadow implementation 406 onMeasure(widthMeasureSpec, heightMeasureSpec); 407 } catch ( IllegalAccessException e ) { 408 throw new RuntimeException(e); 409 } catch ( InvocationTargetException e ) { 410 throw new RuntimeException(e); 411 } 412 } 413 414 @Implementation 415 public final void layout(int l, int t, int r, int b) { 416 left = l; 417 top = t; 418 right = r; 419 bottom = b; 420// todo: realView.onLayout(); 421 } 422 423 @Implementation 424 public void setPadding(int left, int top, int right, int bottom) { 425 paddingLeft = left; 426 paddingTop = top; 427 paddingRight = right; 428 paddingBottom = bottom; 429 } 430 431 @Implementation 432 public int getPaddingTop() { 433 return paddingTop; 434 } 435 436 @Implementation 437 public int getPaddingLeft() { 438 return paddingLeft; 439 } 440 441 @Implementation 442 public int getPaddingRight() { 443 return paddingRight; 444 } 445 446 @Implementation 447 public int getPaddingBottom() { 448 return paddingBottom; 449 } 450 451 @Implementation 452 public Object getTag(int key) { 453 return tags.get(key); 454 } 455 456 @Implementation 457 public void setTag(int key, Object value) { 458 tags.put(key, value); 459 } 460 461 @Implementation 462 public void requestLayout() { 463 didRequestLayout = true; 464 } 465 466 public boolean didRequestLayout() { 467 return didRequestLayout; 468 } 469 470 @Implementation 471 public final boolean requestFocus() { 472 return requestFocus(View.FOCUS_DOWN); 473 } 474 475 @Implementation 476 public final boolean requestFocus(int direction) { 477 setViewFocus(true); 478 return true; 479 } 480 481 public void setViewFocus(boolean hasFocus) { 482 this.isFocused = hasFocus; 483 484 try { 485 Class rectClass = Class.forName("android.graphics.Rect"); 486 Method method = View.class.getDeclaredMethod("onFocusChanged", Boolean.TYPE, Integer.TYPE, 487 rectClass); 488 method.setAccessible(true); 489 method.invoke(realView, this.isFocused, 0, null); 490 } catch (IllegalAccessException e) { 491 throw new RuntimeException(e); 492 } catch (InvocationTargetException e) { 493 throw new RuntimeException(e); 494 } catch (NoSuchMethodException e) { 495 throw new RuntimeException(e); 496 } catch (ClassNotFoundException e) { 497 throw new RuntimeException(e); 498 } 499 500 if (onFocusChangeListener != null) { 501 onFocusChangeListener.onFocusChange(realView, hasFocus); 502 } 503 } 504 505 @Implementation 506 public int getNextFocusDownId() { 507 return nextFocusDownId; 508 } 509 510 @Implementation 511 public void setNextFocusDownId(int nextFocusDownId) { 512 this.nextFocusDownId = nextFocusDownId; 513 } 514 515 @Implementation 516 public boolean isFocused() { 517 return isFocused; 518 } 519 520 @Implementation 521 public boolean hasFocus() { 522 return isFocused; 523 } 524 525 @Implementation 526 public void clearFocus() { 527 setViewFocus(false); 528 } 529 530 @Implementation 531 public View findFocus() { 532 return hasFocus() ? realView : null; 533 } 534 535 @Implementation 536 public void setOnFocusChangeListener(View.OnFocusChangeListener listener) { 537 onFocusChangeListener = listener; 538 } 539 540 @Implementation 541 public View.OnFocusChangeListener getOnFocusChangeListener() { 542 return onFocusChangeListener; 543 } 544 545 @Implementation 546 public void invalidate() { 547 wasInvalidated = true; 548 } 549 550 @Implementation 551 public boolean onTouchEvent(MotionEvent event) { 552 lastTouchEvent = event; 553 return false; 554 } 555 556 @Implementation 557 public void setOnTouchListener(View.OnTouchListener onTouchListener) { 558 this.onTouchListener = onTouchListener; 559 } 560 561 @Implementation 562 public boolean dispatchTouchEvent(MotionEvent event) { 563 if (onTouchListener != null && onTouchListener.onTouch(realView, event)) { 564 return true; 565 } 566 return realView.onTouchEvent(event); 567 } 568 569 public MotionEvent getLastTouchEvent() { 570 return lastTouchEvent; 571 } 572 573 @Implementation 574 public boolean dispatchKeyEvent(KeyEvent event) { 575 if (onKeyListener != null) { 576 return onKeyListener.onKey(realView, event.getKeyCode(), event); 577 } 578 return false; 579 } 580 581 /** 582 * Returns a string representation of this {@code View}. Unless overridden, it will be an empty string. 583 * <p/> 584 * Robolectric extension. 585 */ 586 public String innerText() { 587 return ""; 588 } 589 590 /** 591 * Dumps the status of this {@code View} to {@code System.out} 592 */ 593 public void dump() { 594 dump(System.out, 0); 595 } 596 597 /** 598 * Dumps the status of this {@code View} to {@code System.out} at the given indentation level 599 */ 600 public void dump(PrintStream out, int indent) { 601 dumpFirstPart(out, indent); 602 out.println("/>"); 603 } 604 605 protected void dumpFirstPart(PrintStream out, int indent) { 606 dumpIndent(out, indent); 607 608 out.print("<" + realView.getClass().getSimpleName()); 609 if (id > 0) { 610 out.print(" id=\"" + shadowOf(context).getResourceLoader().getNameForId(id) + "\""); 611 } 612 } 613 614 protected void dumpIndent(PrintStream out, int indent) { 615 for (int i = 0; i < indent; i++) out.print(" "); 616 } 617 618 /** 619 * @return left side of the view 620 */ 621 @Implementation 622 public int getLeft() { 623 return left; 624 } 625 626 /** 627 * @return top coordinate of the view 628 */ 629 @Implementation 630 public int getTop() { 631 return top; 632 } 633 634 /** 635 * @return right side of the view 636 */ 637 @Implementation 638 public int getRight() { 639 return right; 640 } 641 642 /** 643 * @return bottom coordinate of the view 644 */ 645 @Implementation 646 public int getBottom() { 647 return bottom; 648 } 649 650 /** 651 * @return whether the view is clickable 652 */ 653 @Implementation 654 public boolean isClickable() { 655 return clickable; 656 } 657 658 /** 659 * Non-Android accessor. 660 * 661 * @return whether or not {@link #invalidate()} has been called 662 */ 663 public boolean wasInvalidated() { 664 return wasInvalidated; 665 } 666 667 /** 668 * Clears the wasInvalidated flag 669 */ 670 public void clearWasInvalidated() { 671 wasInvalidated = false; 672 } 673 674 @Implementation 675 public void setLeft(int left) { 676 this.left = left; 677 } 678 679 @Implementation 680 public void setTop(int top) { 681 this.top = top; 682 } 683 684 @Implementation 685 public void setRight(int right) { 686 this.right = right; 687 } 688 689 @Implementation 690 public void setBottom(int bottom) { 691 this.bottom = bottom; 692 } 693 694 /** 695 * Non-Android accessor. 696 */ 697 public void setPaddingLeft(int paddingLeft) { 698 this.paddingLeft = paddingLeft; 699 } 700 701 /** 702 * Non-Android accessor. 703 */ 704 public void setPaddingTop(int paddingTop) { 705 this.paddingTop = paddingTop; 706 } 707 708 /** 709 * Non-Android accessor. 710 */ 711 public void setPaddingRight(int paddingRight) { 712 this.paddingRight = paddingRight; 713 } 714 715 /** 716 * Non-Android accessor. 717 */ 718 public void setPaddingBottom(int paddingBottom) { 719 this.paddingBottom = paddingBottom; 720 } 721 722 /** 723 * Non-Android accessor. 724 */ 725 public void setFocused(boolean focused) { 726 isFocused = focused; 727 } 728 729 /** 730 * Non-Android accessor. 731 * 732 * @return true if this object and all of its ancestors are {@code View.VISIBLE}, returns false if this or 733 * any ancestor is not {@code View.VISIBLE} 734 */ 735 public boolean derivedIsVisible() { 736 View parent = realView; 737 while (parent != null) { 738 if (parent.getVisibility() != View.VISIBLE) { 739 return false; 740 } 741 parent = (View) parent.getParent(); 742 } 743 return true; 744 } 745 746 /** 747 * Utility method for clicking on views exposing testing scenarios that are not possible when using the actual app. 748 * 749 * @throws RuntimeException if the view is disabled or if the view or any of its parents are not visible. 750 */ 751 public boolean checkedPerformClick() { 752 if (!derivedIsVisible()) { 753 throw new RuntimeException("View is not visible and cannot be clicked"); 754 } 755 if (!realView.isEnabled()) { 756 throw new RuntimeException("View is not enabled and cannot be clicked"); 757 } 758 759 return realView.performClick(); 760 } 761 762 public void applyFocus() { 763 if (noParentHasFocus(realView)) { 764 Boolean focusRequested = attributeSet.getAttributeBooleanValue("android", "focus", false); 765 if (focusRequested || realView.isFocusableInTouchMode()) { 766 realView.requestFocus(); 767 } 768 } 769 } 770 771 private void applyIdAttribute() { 772 Integer id = attributeSet.getAttributeResourceValue("android", "id", 0); 773 if (getId() == 0) { 774 setId(id); 775 } 776 } 777 778 private void applyTagAttribute() { 779 Object tag = attributeSet.getAttributeValue("android", "tag"); 780 if (tag != null) { 781 setTag(tag); 782 } 783 } 784 785 private void applyVisibilityAttribute() { 786 String visibility = attributeSet.getAttributeValue("android", "visibility"); 787 if (visibility != null) { 788 if (visibility.equals("gone")) { 789 setVisibility(View.GONE); 790 } else if (visibility.equals("invisible")) { 791 setVisibility(View.INVISIBLE); 792 } 793 } 794 } 795 796 private void applyEnabledAttribute() { 797 setEnabled(attributeSet.getAttributeBooleanValue("android", "enabled", true)); 798 } 799 800 private void applyBackgroundAttribute() { 801 String source = attributeSet.getAttributeValue("android", "background"); 802 if (source != null) { 803 if (source.startsWith("@drawable/")) { 804 setBackgroundResource(attributeSet.getAttributeResourceValue("android", "background", 0)); 805 } 806 } 807 } 808 809 private void applyOnClickAttribute() { 810 final String handlerName = attributeSet.getAttributeValue("android", 811 "onClick"); 812 if (handlerName == null) { 813 return; 814 } 815 816 /* good part of following code has been directly copied from original 817 * android source */ 818 setOnClickListener(new View.OnClickListener() { 819 @Override 820 public void onClick(View v) { 821 Method mHandler; 822 try { 823 mHandler = getContext().getClass().getMethod(handlerName, 824 View.class); 825 } catch (NoSuchMethodException e) { 826 int id = getId(); 827 String idText = id == View.NO_ID ? "" : " with id '" 828 + shadowOf(context).getResourceLoader() 829 .getNameForId(id) + "'"; 830 throw new IllegalStateException("Could not find a method " + 831 handlerName + "(View) in the activity " 832 + getContext().getClass() + " for onClick handler" 833 + " on view " + realView.getClass() + idText, e); 834 } 835 836 try { 837 mHandler.invoke(getContext(), realView); 838 } catch (IllegalAccessException e) { 839 throw new IllegalStateException("Could not execute non " 840 + "public method of the activity", e); 841 } catch (InvocationTargetException e) { 842 throw new IllegalStateException("Could not execute " 843 + "method of the activity", e); 844 } 845 } 846 }); 847 } 848 849 private void applyContentDescriptionAttribute() { 850 String contentDescription = attributeSet.getAttributeValue("android", "contentDescription"); 851 if (contentDescription != null) { 852 if (contentDescription.startsWith("@string/")) { 853 int resId = attributeSet.getAttributeResourceValue("android", "contentDescription", 0); 854 contentDescription = context.getResources().getString(resId); 855 } 856 setContentDescription(contentDescription); 857 } 858 } 859 860 private boolean noParentHasFocus(View view) { 861 while (view != null) { 862 if (view.hasFocus()) return false; 863 view = (View) view.getParent(); 864 } 865 return true; 866 } 867 868 /** 869 * Non-android accessor. Returns touch listener, if set. 870 * 871 * @return 872 */ 873 public View.OnTouchListener getOnTouchListener() { 874 return onTouchListener; 875 } 876 877 /** 878 * Non-android accessor. Returns click listener, if set. 879 * 880 * @return 881 */ 882 public View.OnClickListener getOnClickListener() { 883 return onClickListener; 884 } 885 886 @Implementation 887 public void setDrawingCacheEnabled(boolean drawingCacheEnabled) { 888 this.drawingCacheEnabled = drawingCacheEnabled; 889 } 890 891 @Implementation 892 public boolean isDrawingCacheEnabled() { 893 return drawingCacheEnabled; 894 } 895 896 @Implementation 897 public Bitmap getDrawingCache() { 898 return Robolectric.newInstanceOf(Bitmap.class); 899 } 900 901 @Implementation 902 public void post(Runnable action) { 903 Robolectric.getUiThreadScheduler().post(action); 904 } 905 906 @Implementation 907 public void postDelayed(Runnable action, long delayMills) { 908 Robolectric.getUiThreadScheduler().postDelayed(action, delayMills); 909 } 910 911 @Implementation 912 public void postInvalidateDelayed(long delayMilliseconds) { 913 Robolectric.getUiThreadScheduler().postDelayed(new Runnable() { 914 @Override 915 public void run() { 916 realView.invalidate(); 917 } 918 }, delayMilliseconds); 919 } 920 921 @Implementation 922 public Animation getAnimation() { 923 return animation; 924 } 925 926 @Implementation 927 public void setAnimation(Animation anim) { 928 animation = anim; 929 } 930 931 @Implementation 932 public void startAnimation(Animation anim) { 933 setAnimation(anim); 934 animation.start(); 935 } 936 937 @Implementation 938 public void clearAnimation() { 939 if (animation != null) { 940 animation.cancel(); 941 animation = null; 942 } 943 } 944 945 @Implementation 946 public void scrollTo(int x, int y) { 947 this.scrollToCoordinates = new Point(x, y); 948 } 949 950 @Implementation 951 public int getScrollX() { 952 return scrollToCoordinates != null ? scrollToCoordinates.x : 0; 953 } 954 955 @Implementation 956 public int getScrollY() { 957 return scrollToCoordinates != null ? scrollToCoordinates.y : 0; 958 } 959 960 @Implementation 961 public ViewTreeObserver getViewTreeObserver() { 962 if (viewTreeObserver == null) { 963 viewTreeObserver = newInstanceOf(ViewTreeObserver.class); 964 } 965 return viewTreeObserver; 966 } 967 968 @Implementation 969 public void onAnimationEnd() { 970 } 971 972 /* 973 * Non-Android accessor. 974 */ 975 public void finishedAnimation() { 976 try { 977 Method onAnimationEnd = realView.getClass().getDeclaredMethod("onAnimationEnd", new Class[0]); 978 onAnimationEnd.setAccessible(true); 979 onAnimationEnd.invoke(realView); 980 } catch (Exception e) { 981 throw new RuntimeException(e); 982 } 983 } 984} 985