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;
}
}
}