/** * Copyright (c) 2017-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.litho; import android.annotation.TargetApi; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.RippleDrawable; import android.os.Build; import android.view.MotionEvent; import android.view.View; import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.os.Build.VERSION_CODES.LOLLIPOP; /** * A Drawable that wraps another drawable. */ public class MatrixDrawable<T extends Drawable> extends Drawable implements Drawable.Callback, Touchable { public static final int UNSET = -1; private T mDrawable; private DrawableMatrix mMatrix; private boolean mShouldClipRect; private int mWidth; private int mHeight; public MatrixDrawable() { super(); } public void mount(T drawable, DrawableMatrix matrix) { if (mDrawable == drawable) { return; } if (mDrawable != null) { mDrawable.setCallback(null); } mDrawable = drawable; if (mDrawable != null) { mDrawable.setCallback(this); } mMatrix = matrix; // We should clip rect if either the transformation matrix needs so or // if a ColorDrawable in Gingerbread is being drawn because it doesn't // respect its bounds. mShouldClipRect = (mMatrix != null && mMatrix.shouldClipRect()) || (Build.VERSION.SDK_INT < HONEYCOMB && mDrawable instanceof ColorDrawable) || (mDrawable instanceof InsetDrawable); invalidateSelf(); } /** * Sets the necessary artifacts to display the given drawable. * This method should be called in your component's @OnMount method. * * @param drawable The drawable to be drawn. */ public void mount(T drawable) { mount(drawable, null); } /** * Applies the given dimensions to the drawable. * This method should be called in your component's @OnBind method. * * @param width The width of the drawable to be drawn. * @param height The height of the drawable to be drawn. */ public void bind(int width, int height) { mWidth = width; mHeight = height; setInnerDrawableBounds(getBounds().left, getBounds().top); } @Override public void setBounds(int left, int top, int right, int bottom) { super.setBounds(left, top, right, bottom); setInnerDrawableBounds(left, top); } @Override public void setBounds(Rect bounds) { super.setBounds(bounds); setInnerDrawableBounds(bounds.left, bounds.top); } private void setInnerDrawableBounds(int left, int top) { if (mDrawable == null) { return; } final int innerDrawableLeft = (mMatrix != null ? 0 : left); final int innerDrawableTop = (mMatrix != null ? 0 : top); mDrawable.setBounds( innerDrawableLeft, innerDrawableTop, innerDrawableLeft + mWidth, innerDrawableTop + mHeight); } public void unmount() { if (mDrawable != null) { mDrawable.setCallback(null); } mDrawable = null; mMatrix = null; mShouldClipRect = false; mWidth = mHeight = 0; } public T getMountedDrawable() { return mDrawable; } @Override public void draw(Canvas canvas) { if (mDrawable == null) { return; } final Rect bounds = getBounds(); if (mMatrix != null) { final int saveCount = canvas.save(); if (mShouldClipRect) { canvas.clipRect(bounds); } canvas.translate(bounds.left, bounds.top); canvas.concat(mMatrix); mDrawable.draw(canvas); canvas.restoreToCount(saveCount); } else { if (mShouldClipRect) { canvas.save(); canvas.clipRect(bounds); } mDrawable.draw(canvas); if (mShouldClipRect) { canvas.restore(); } } } @Override public void setChangingConfigurations(int configs) { if (mDrawable == null) { return; } mDrawable.setChangingConfigurations(configs); } @Override public int getChangingConfigurations() { return mDrawable == null ? UNSET : mDrawable.getChangingConfigurations(); } @Override public void setDither(boolean dither) { if (mDrawable == null) { return; } mDrawable.setDither(dither); } @Override public void setFilterBitmap(boolean filter) { if (mDrawable == null) { return; } mDrawable.setFilterBitmap(filter); } @Override public void setAlpha(int alpha) { if (mDrawable == null) { return; } mDrawable.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { if (mDrawable == null) { return; } mDrawable.setColorFilter(cf); } @Override public boolean isStateful() { return mDrawable != null && mDrawable.isStateful(); } @Override public boolean setState(final int[] stateSet) { return mDrawable != null && mDrawable.setState(stateSet); } @Override public int[] getState() { return mDrawable == null ? null : mDrawable.getState(); } @Override public Drawable getCurrent() { return mDrawable == null ? null : mDrawable.getCurrent(); } @Override public boolean setVisible(boolean visible, boolean restart) { return super.setVisible(visible, restart) || (mDrawable != null && mDrawable.setVisible(visible, restart)); } @Override public int getOpacity() { return mDrawable == null ? UNSET : mDrawable.getOpacity(); } @Override public Region getTransparentRegion() { return mDrawable == null ? null : mDrawable.getTransparentRegion(); } @Override public int getIntrinsicWidth() { return mDrawable == null ? UNSET : mDrawable.getIntrinsicWidth(); } @Override public int getIntrinsicHeight() { return mDrawable == null ? UNSET : mDrawable.getIntrinsicHeight(); } @Override public int getMinimumWidth() { return mDrawable == null ? UNSET : mDrawable.getMinimumWidth(); } @Override public int getMinimumHeight() { return mDrawable == null ? UNSET : mDrawable.getMinimumHeight(); } @Override public boolean getPadding(Rect padding) { return mDrawable != null && mDrawable.getPadding(padding); } @Override protected boolean onLevelChange(int level) { return mDrawable != null && mDrawable.setLevel(level); } @Override public void invalidateDrawable(Drawable who) { invalidateSelf(); } @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { scheduleSelf(what, when); } @Override public void unscheduleDrawable(Drawable who, Runnable what) { unscheduleSelf(what); } @Override @TargetApi(LOLLIPOP) public boolean onTouchEvent(MotionEvent event, View host) { final Rect bounds = getBounds(); final int x = (int) event.getX() - bounds.left; final int y = (int) event.getY() - bounds.top; mDrawable.setHotspot(x, y); return false; } @Override public boolean shouldHandleTouchEvent(MotionEvent event) { return Build.VERSION.SDK_INT >= LOLLIPOP && mDrawable != null && mDrawable instanceof RippleDrawable && event.getActionMasked() == MotionEvent.ACTION_DOWN && getBounds().contains((int) event.getX(), (int) event.getY()); } }