import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.DrawableRes; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.widget.AppCompatTextView; import android.util.AttributeSet; /** * Enhanced TextView Widget that can set drawable size, tint and using vector drawable * * @author VincentJian * @version 1.0 * @see http://stackoverflow.com/a/31916731 * @see http://stackoverflow.com/a/40250753 * @since 2017/5/16 */ public class DrawableTextView extends AppCompatTextView { private static final int DEFAULT_DRAWABLE_SIZE = -1; private static final int DEFAULT_DRAWABLE_RESOURCE = -1; private static final int DEFAULT_DRAWABLE_TINT_MODE = -1; private int mDrawableWidth; private int mDrawableHeight; private ColorStateList mDrawableTint; private PorterDuff.Mode mDrawableTintMode; public DrawableTextView(Context context) { this(context, null); } public DrawableTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DrawableTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { if (attrs == null) { return; } final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawableTextView); mDrawableWidth = a.getDimensionPixelSize(R.styleable.DrawableTextView_drawableWidth, DEFAULT_DRAWABLE_SIZE); mDrawableHeight = a.getDimensionPixelSize(R.styleable.DrawableTextView_drawableHeight, DEFAULT_DRAWABLE_SIZE); mDrawableTint = a.getColorStateList(R.styleable.DrawableTextView_drawableTint); mDrawableTintMode = parseTintMode(a.getInt(R.styleable.DrawableTextView_drawableTintMode, DEFAULT_DRAWABLE_TINT_MODE)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { setCompoundDrawables(a.getDrawable(R.styleable.DrawableTextView_drawableLeftCompat), a.getDrawable(R.styleable.DrawableTextView_drawableTopCompat), a.getDrawable(R.styleable.DrawableTextView_drawableRightCompat), a.getDrawable(R.styleable.DrawableTextView_drawableBottomCompat)); } else { setCompoundDrawables(a.getResourceId(R.styleable.DrawableTextView_drawableLeftCompat, DEFAULT_DRAWABLE_RESOURCE), a.getResourceId(R.styleable.DrawableTextView_drawableTopCompat, DEFAULT_DRAWABLE_RESOURCE), a.getResourceId(R.styleable.DrawableTextView_drawableRightCompat, DEFAULT_DRAWABLE_RESOURCE), a.getResourceId(R.styleable.DrawableTextView_drawableBottomCompat, DEFAULT_DRAWABLE_RESOURCE)); } a.recycle(); } /** * Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the * text. * * @param left left drawable resource, -1 means no left drawable * @param top top drawable resource, -1 means no top drawable * @param right right drawable resource, -1 means no right drawable * @param bottom bottom drawable resource, -1 means no bottom drawable */ public void setCompoundDrawables(@DrawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { setCompoundDrawables(left == -1 ? null : ContextCompat.getDrawable(getContext(), left), top == -1 ? null : ContextCompat.getDrawable(getContext(), top), right == -1 ? null : ContextCompat.getDrawable(getContext(), right), bottom == -1 ? null : ContextCompat.getDrawable(getContext(), bottom)); } @Override public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { if (left != null) { left = DrawableCompat.wrap(left); } if (top != null) { top = DrawableCompat.wrap(top); } if (right != null) { right = DrawableCompat.wrap(right); } if (bottom != null) { bottom = DrawableCompat.wrap(bottom); } tintDrawable(left); tintDrawable(top); tintDrawable(right); tintDrawable(bottom); scaleDrawable(left); scaleDrawable(top); scaleDrawable(right); scaleDrawable(bottom); super.setCompoundDrawables(left, top, right, bottom); } public int getDrawableWidth() { return mDrawableWidth; } public void setDrawableWidth(int drawableWidth) { mDrawableWidth = drawableWidth; } public int getDrawableHeight() { return mDrawableHeight; } public void setDrawableHeight(int drawableHeight) { mDrawableHeight = drawableHeight; } public ColorStateList getDrawableTint() { return mDrawableTint; } public void setDrawableTint(@ColorInt int drawableTint) { if (drawableTint == -1) { mDrawableTint = null; } else { mDrawableTint = ColorStateList.valueOf(drawableTint); } } public void setDrawableTint(ColorStateList drawableTint) { mDrawableTint = drawableTint; } public PorterDuff.Mode getDrawableTintMode() { return mDrawableTintMode; } public void setDrawableTintMode(PorterDuff.Mode drawableTintMode) { mDrawableTintMode = drawableTintMode; } private void tintDrawable(Drawable drawable) { if (drawable == null) { return; } if (mDrawableTint != null) { DrawableCompat.setTint(drawable, mDrawableTint.getDefaultColor()); } if (mDrawableTintMode != null) { DrawableCompat.setTintMode(drawable, mDrawableTintMode); } } private void scaleDrawable(Drawable drawable) { if (drawable == null) { return; } Rect bounds = new Rect(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); if (mDrawableWidth == DEFAULT_DRAWABLE_SIZE && mDrawableHeight == DEFAULT_DRAWABLE_SIZE) { drawable.setBounds(bounds); return; } float drawableWidth = bounds.width(); float drawableHeight = bounds.height(); float scaleFactor = drawableHeight / drawableWidth; if (mDrawableWidth > 0 && drawableWidth > mDrawableWidth) { drawableWidth = mDrawableWidth; drawableHeight = drawableWidth * scaleFactor; } if (mDrawableHeight > 0 && drawableHeight > mDrawableHeight) { drawableHeight = mDrawableHeight; drawableWidth = drawableHeight / scaleFactor; } bounds.right = bounds.left + Math.round(drawableWidth); bounds.bottom = bounds.top + Math.round(drawableHeight); drawable.setBounds(bounds); } private static PorterDuff.Mode parseTintMode(int mode) { switch (mode) { case 3: return PorterDuff.Mode.SRC_OVER; case 5: return PorterDuff.Mode.SRC_IN; case 9: return PorterDuff.Mode.SRC_ATOP; case 14: return PorterDuff.Mode.MULTIPLY; case 15: return PorterDuff.Mode.SCREEN; case 16: return PorterDuff.Mode.ADD; default: return null; } } }