/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.drawee.drawable; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.Drawable; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.VisibleForTesting; /** * Drawable that can scale underlying drawable based on specified {@link ScalingUtils.ScaleType} * options. * <p/> Based on {@link android.widget.ImageView.ScaleType}. */ public class ScaleTypeDrawable extends ForwardingDrawable { // Specified scale type. @VisibleForTesting ScalingUtils.ScaleType mScaleType; // Specified focus point to use with FOCUS_CROP. @VisibleForTesting PointF mFocusPoint = null; // Last known dimensions of the underlying drawable. Used to avoid computing bounds every time // if underlying size hasn't changed. @VisibleForTesting int mUnderlyingWidth = 0; @VisibleForTesting int mUnderlyingHeight = 0; // Matrix that is actually being used for drawing. @VisibleForTesting Matrix mDrawMatrix; // Temporary objects preallocated in advance to save future allocations. private Matrix mTempMatrix = new Matrix(); /** * Creates a new ScaleType drawable with given underlying drawable and scale type. * @param drawable underlying drawable to apply scale type on * @param scaleType scale type to be applied */ public ScaleTypeDrawable(Drawable drawable, ScalingUtils.ScaleType scaleType) { super(Preconditions.checkNotNull(drawable)); mScaleType = scaleType; } /** * Gets the current scale type. * @return scale type */ public ScalingUtils.ScaleType getScaleType() { return mScaleType; } /** * Sets the scale type. * @param scaleType scale type to set */ public void setScaleType(ScalingUtils.ScaleType scaleType) { mScaleType = scaleType; configureBounds(); invalidateSelf(); } /** * Gets the focus point. * @return focus point of the image */ public PointF getFocusPoint() { return mFocusPoint; } /** * Sets the focus point. * If ScaleType.FOCUS_CROP is used, focus point will attempted to be centered within a view. * Each coordinate is a real number in [0,1] range, in the coordinate system where top-left * corner of the image corresponds to (0, 0) and the bottom-right corner corresponds to (1, 1). * @param focusPoint focus point of the image */ public void setFocusPoint(PointF focusPoint) { if (mFocusPoint == null) { mFocusPoint = new PointF(); } mFocusPoint.set(focusPoint); configureBounds(); invalidateSelf(); } @Override public void draw(Canvas canvas) { configureBoundsIfUnderlyingChanged(); if (mDrawMatrix != null) { int saveCount = canvas.save(); canvas.clipRect(getBounds()); canvas.concat(mDrawMatrix); super.draw(canvas); canvas.restoreToCount(saveCount); } else { // mDrawMatrix == null means our bounds match and we can take fast path super.draw(canvas); } } @Override protected void onBoundsChange(Rect bounds) { configureBounds(); } private void configureBoundsIfUnderlyingChanged() { if (mUnderlyingWidth != getCurrent().getIntrinsicWidth() || mUnderlyingHeight != getCurrent().getIntrinsicHeight()) { configureBounds(); } } /** * Determines bounds for the underlying drawable and a matrix that should be applied on it. * Adopted from android.widget.ImageView */ @VisibleForTesting void configureBounds() { Drawable underlyingDrawable = getCurrent(); Rect bounds = getBounds(); int viewWidth = bounds.width(); int viewHeight = bounds.height(); int underlyingWidth = mUnderlyingWidth = underlyingDrawable.getIntrinsicWidth(); int underlyingHeight = mUnderlyingHeight = underlyingDrawable.getIntrinsicHeight(); // If the drawable has no intrinsic size, we just fill our entire view. if (underlyingWidth <= 0 || underlyingHeight <= 0) { underlyingDrawable.setBounds(bounds); mDrawMatrix = null; return; } // If the drawable fits exactly, no transform needed. if (underlyingWidth == viewWidth && underlyingHeight == viewHeight) { underlyingDrawable.setBounds(bounds); mDrawMatrix = null; return; } // If we're told to scale to fit, we just fill our entire view. // (ScalingUtils.getTransform would do, but this is faster) if (mScaleType == ScalingUtils.ScaleType.FIT_XY) { underlyingDrawable.setBounds(bounds); mDrawMatrix = null; return; } // We need to do the scaling ourselves, so have the underlying drawable use its preferred size. underlyingDrawable.setBounds(0, 0, underlyingWidth, underlyingHeight); ScalingUtils.getTransform( mTempMatrix, bounds, underlyingWidth, underlyingHeight, (mFocusPoint != null) ? mFocusPoint.x : 0.5f, (mFocusPoint != null) ? mFocusPoint.y : 0.5f, mScaleType); mDrawMatrix = mTempMatrix; } /** * TransformationCallback method * @param transform */ @Override public void getTransform(Matrix transform) { getParentTransform(transform); // IMPORTANT: {@code configureBounds} should be called after {@code getParentTransform}, // because the parent may have to change our bounds. configureBoundsIfUnderlyingChanged(); if (mDrawMatrix != null) { transform.preConcat(mDrawMatrix); } } }