/** * Applies rounding on the drawable's leaf. * * <p> Currently only {@link BitmapDrawable} or {@link ColorDrawable} leafs can be rounded. * <p> If the leaf cannot be rounded, or the rounding params do not specify BITMAP_ONLY mode, * the given drawable is returned without being rounded. * <p> If the given drawable is a leaf itself, and it can be rounded, then the rounded drawable * is returned. * <p> If the given drawable is not a leaf, and its leaf can be rounded, the leaf gets rounded, * and the original drawable is returned. * * @return the rounded drawable, or the original drawable if the rounding didn't take place * or it took place on a drawable's child */ static Drawable maybeApplyLeafRounding( @Nullable Drawable drawable, @Nullable RoundingParams roundingParams, Resources resources) { if (drawable == null || roundingParams == null || roundingParams.getRoundingMethod() != RoundingParams.RoundingMethod.BITMAP_ONLY) { return drawable; } if (drawable instanceof ForwardingDrawable) { DrawableParent parent = findDrawableParentForLeaf((ForwardingDrawable) drawable); Drawable child = parent.setDrawable(sEmptyDrawable); child = applyLeafRounding(child, roundingParams, resources); parent.setDrawable(child); return drawable; } else { return applyLeafRounding(drawable, roundingParams, resources); } }
@Before public void setUp() { mBuilder = new GenericDraweeHierarchyBuilder(null); mBackground = DrawableTestUtils.mockDrawable(); mOverlay1 = DrawableTestUtils.mockDrawable(); mOverlay2 = DrawableTestUtils.mockDrawable(); mPlaceholderImage = DrawableTestUtils.mockBitmapDrawable(); mFailureImage = DrawableTestUtils.mockBitmapDrawable(); mRetryImage = DrawableTestUtils.mockBitmapDrawable(); mProgressBarImage = DrawableTestUtils.mockBitmapDrawable(); mActualImage1 = DrawableTestUtils.mockBitmapDrawable(); mActualImage2 = DrawableTestUtils.mockBitmapDrawable(); mWrappedLeaf = new ColorDrawable(Color.BLUE); mWrappedImage = new ForwardingDrawable(new ForwardingDrawable(mWrappedLeaf)); mFocusPoint = new PointF(0.1f, 0.4f); }
@Test public void testHierarchy_NoScaleTypeNorMatrix() throws Exception { GenericDraweeHierarchy dh = mBuilder .setPlaceholderImage(mPlaceholderImage, null) .setRetryImage(mRetryImage, null) .setFailureImage(mFailureImage, null) .setProgressBarImage(mProgressBarImage, null) .setActualImageScaleType(null) .build(); RootDrawable rootDrawable = (RootDrawable) dh.getTopLevelDrawable(); FadeDrawable fadeDrawable = (FadeDrawable) rootDrawable.getCurrent(); assertEquals(7, fadeDrawable.getNumberOfLayers()); assertNull(fadeDrawable.getDrawable(0)); assertSame(mPlaceholderImage, fadeDrawable.getDrawable(1)); assertSame(ForwardingDrawable.class, fadeDrawable.getDrawable(2).getClass()); assertSame(mProgressBarImage, fadeDrawable.getDrawable(3)); assertSame(mRetryImage, fadeDrawable.getDrawable(4)); assertSame(mFailureImage, fadeDrawable.getDrawable(5)); assertNull(fadeDrawable.getDrawable(6)); verifyCallback(rootDrawable, mPlaceholderImage); }
public static Drawable applyRoundingBitmapOnly( @Nullable RoundingParams roundingParams, Resources resources, Drawable drawable) { if (roundingParams == null || roundingParams.getRoundingMethod() != RoundingParams.RoundingMethod.BITMAP_ONLY) { return drawable; } if (drawable instanceof BitmapDrawable || drawable instanceof ColorDrawable) { return applyRounding(roundingParams, resources, drawable); } else { Drawable parent = drawable; Drawable child = parent.getCurrent(); while (child != null && parent != child) { if (parent instanceof ForwardingDrawable && (child instanceof BitmapDrawable || child instanceof ColorDrawable)) { ((ForwardingDrawable) parent).setCurrent( applyRounding(roundingParams, resources, child)); } parent = child; child = parent.getCurrent(); } } return drawable; }
@Test public void testControlling_WithCornerRadii() throws Exception { GenericDraweeHierarchy dh = mBuilder .setPlaceholderImage(mPlaceholderImage, null) .setActualImageScaleType(null) .setRoundingParams(RoundingParams.fromCornersRadius(10)) .setFadeDuration(250) .build(); // actual image index in DH tree final int imageIndex = 2; FadeDrawable fadeDrawable = (FadeDrawable) dh.getTopLevelDrawable().getCurrent(); ForwardingDrawable settableDrawable = (ForwardingDrawable) fadeDrawable.getDrawable(imageIndex); // set temporary image dh.setImage(mActualImage1, 0.5f, true); assertNotSame(mActualImage1, settableDrawable.getCurrent()); assertEquals(RoundedBitmapDrawable.class, settableDrawable.getCurrent().getClass()); assertEquals(true, fadeDrawable.isLayerOn(imageIndex)); assertEquals(FadeDrawable.TRANSITION_NONE, fadeDrawable.getTransitionState()); verifyCallback(dh.getTopLevelDrawable(), settableDrawable.getCurrent()); // set final image dh.setImage(mActualImage2, 1f, false); assertNotSame(mActualImage2, settableDrawable.getCurrent()); assertEquals(RoundedBitmapDrawable.class, settableDrawable.getCurrent().getClass()); assertEquals(true, fadeDrawable.isLayerOn(imageIndex)); assertEquals(FadeDrawable.TRANSITION_STARTING, fadeDrawable.getTransitionState()); assertEquals(250, fadeDrawable.getTransitionDuration()); verifyCallback(dh.getTopLevelDrawable(), settableDrawable.getCurrent()); }
private void assertActualImageScaleType( ScaleType expectedScaleType, PointF expectedFocusPoint, Drawable actualBranch) { assertNotNull(actualBranch); ScaleTypeDrawable scaleTypeDrawable = (ScaleTypeDrawable) actualBranch; assertSame(expectedScaleType, scaleTypeDrawable.getScaleType()); assertSame(ForwardingDrawable.class, scaleTypeDrawable.getCurrent().getClass()); AndroidGraphicsTestUtils.assertEquals(expectedFocusPoint, scaleTypeDrawable.getFocusPoint(), 0); }
/** * Use {@link Builder} to build a DraweeSpan. */ private DraweeSpan(String uri, int verticalAlignment, Drawable placeHolder, boolean showAnim) { super(verticalAlignment); mImageUri = uri; mShouldShowAnim = showAnim; mDeferredReleaser = DeferredReleaser.getInstance(); mPlaceHolder = placeHolder; // create forwarding drawable with placeholder mActualDrawable = new ForwardingDrawable(mPlaceHolder); }
GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) { mResources = builder.getResources(); mRoundingParams = builder.getRoundingParams(); mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable); int numOverlays = (builder.getOverlays() != null) ? builder.getOverlays().size() : 1; numOverlays += (builder.getPressedStateOverlay() != null) ? 1 : 0; // layer indices and count int numLayers = OVERLAY_IMAGES_INDEX + numOverlays; // array of layers Drawable[] layers = new Drawable[numLayers]; layers[BACKGROUND_IMAGE_INDEX] = buildBranch(builder.getBackground(), null); layers[PLACEHOLDER_IMAGE_INDEX] = buildBranch( builder.getPlaceholderImage(), builder.getPlaceholderImageScaleType()); layers[ACTUAL_IMAGE_INDEX] = buildActualImageBranch( mActualImageWrapper, builder.getActualImageScaleType(), builder.getActualImageFocusPoint(), builder.getActualImageColorFilter()); layers[PROGRESS_BAR_IMAGE_INDEX] = buildBranch( builder.getProgressBarImage(), builder.getProgressBarImageScaleType()); layers[RETRY_IMAGE_INDEX] = buildBranch( builder.getRetryImage(), builder.getRetryImageScaleType()); layers[FAILURE_IMAGE_INDEX] = buildBranch( builder.getFailureImage(), builder.getFailureImageScaleType()); if (numOverlays > 0) { int index = 0; if (builder.getOverlays() != null) { for (Drawable overlay : builder.getOverlays()) { layers[OVERLAY_IMAGES_INDEX + index++] = buildBranch(overlay, null); } } else { index = 1; // reserve space for one overlay } if (builder.getPressedStateOverlay() != null) { layers[OVERLAY_IMAGES_INDEX + index] = buildBranch(builder.getPressedStateOverlay(), null); } } // fade drawable composed of layers mFadeDrawable = new FadeDrawable(layers); mFadeDrawable.setTransitionDuration(builder.getFadeDuration()); // rounded corners drawable (optional) Drawable maybeRoundedDrawable = WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams); // top-level drawable mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable); mTopLevelDrawable.mutate(); resetFade(); }
@Test public void testControlling_WithPlaceholderOnly() throws Exception { GenericDraweeHierarchy dh = mBuilder .setPlaceholderImage(mPlaceholderImage, null) .setActualImageScaleType(null) .setFadeDuration(250) .build(); // image indexes in DH tree final int placeholderImageIndex = 1; final int actualImageIndex = 2; FadeDrawable fadeDrawable = (FadeDrawable) dh.getTopLevelDrawable().getCurrent(); assertEquals(mPlaceholderImage, fadeDrawable.getDrawable(placeholderImageIndex)); assertEquals( ForwardingDrawable.class, fadeDrawable.getDrawable(actualImageIndex).getClass()); ForwardingDrawable actualImageSettableDrawable = (ForwardingDrawable) fadeDrawable.getDrawable(actualImageIndex); // initial state -> final image (non-immediate) // initial state assertEquals(ColorDrawable.class, actualImageSettableDrawable.getCurrent().getClass()); assertEquals(true, fadeDrawable.isLayerOn(placeholderImageIndex)); assertEquals(false, fadeDrawable.isLayerOn(actualImageIndex)); assertEquals(FadeDrawable.TRANSITION_NONE, fadeDrawable.getTransitionState()); // set final image (non-immediate) dh.setImage(mActualImage1, 1f, false); assertEquals(mActualImage1, actualImageSettableDrawable.getCurrent()); assertEquals(false, fadeDrawable.isLayerOn(placeholderImageIndex)); assertEquals(true, fadeDrawable.isLayerOn(actualImageIndex)); assertEquals(FadeDrawable.TRANSITION_STARTING, fadeDrawable.getTransitionState()); assertEquals(250, fadeDrawable.getTransitionDuration()); // initial state -> final image (immediate) // reset hierarchy to initial state dh.reset(); assertEquals(ColorDrawable.class, actualImageSettableDrawable.getCurrent().getClass()); assertEquals(true, fadeDrawable.isLayerOn(placeholderImageIndex)); assertEquals(false, fadeDrawable.isLayerOn(actualImageIndex)); assertEquals(FadeDrawable.TRANSITION_NONE, fadeDrawable.getTransitionState()); // set final image (immediate) dh.setImage(mActualImage2, 1f, true); assertEquals(mActualImage2, actualImageSettableDrawable.getCurrent()); assertEquals(false, fadeDrawable.isLayerOn(placeholderImageIndex)); assertEquals(true, fadeDrawable.isLayerOn(actualImageIndex)); assertEquals(FadeDrawable.TRANSITION_NONE, fadeDrawable.getTransitionState()); // initial state -> retry // reset hierarchy to initial state dh.reset(); assertEquals(ColorDrawable.class, actualImageSettableDrawable.getCurrent().getClass()); assertEquals(true, fadeDrawable.isLayerOn(placeholderImageIndex)); assertEquals(false, fadeDrawable.isLayerOn(actualImageIndex)); assertEquals(FadeDrawable.TRANSITION_NONE, fadeDrawable.getTransitionState()); // set retry dh.setRetry(new RuntimeException()); assertEquals(ColorDrawable.class, actualImageSettableDrawable.getCurrent().getClass()); assertEquals(true, fadeDrawable.isLayerOn(placeholderImageIndex)); assertEquals(false, fadeDrawable.isLayerOn(actualImageIndex)); assertEquals(FadeDrawable.TRANSITION_STARTING, fadeDrawable.getTransitionState()); assertEquals(250, fadeDrawable.getTransitionDuration()); // initial state -> failure // reset hierarchy to initial state dh.reset(); assertEquals(ColorDrawable.class, actualImageSettableDrawable.getCurrent().getClass()); assertEquals(true, fadeDrawable.isLayerOn(placeholderImageIndex)); assertEquals(false, fadeDrawable.isLayerOn(actualImageIndex)); assertEquals(FadeDrawable.TRANSITION_NONE, fadeDrawable.getTransitionState()); // set failure dh.setFailure(new RuntimeException()); assertEquals(ColorDrawable.class, actualImageSettableDrawable.getCurrent().getClass()); assertEquals(true, fadeDrawable.isLayerOn(placeholderImageIndex)); assertEquals(false, fadeDrawable.isLayerOn(actualImageIndex)); assertEquals(FadeDrawable.TRANSITION_STARTING, fadeDrawable.getTransitionState()); assertEquals(250, fadeDrawable.getTransitionDuration()); }
@Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || mHasDraweeInText // only schedule animation on AnimatableDrawable && (who instanceof ForwardingDrawable && who.getCurrent() instanceof Animatable); }