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