Android AppCompat 23.1.0 Tint Compound Drawable
Android AppCompat 23.1.0 Tint Compound Drawable
我使用以下方法使用 android.support.design 23.0.1 正确着色复合绘图。现在他们发布了 23.1.0,它不再适用于 api LVL16,我所有的可绘制对象都是黑色的。
有人有建议吗?
private void setCompoundColor(TextView view) {
Drawable drawable = view.getCompoundDrawables()[0];
Drawable wrap = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(wrap, ContextCompat.getColor(this, R.color.primaryLighter2));
DrawableCompat.setTintMode(wrap, PorterDuff.Mode.SRC_IN);
wrap = wrap.mutate();
view.setCompoundDrawablesRelativeWithIntrinsicBounds(wrap, null, null, null);
}
谢谢。
上周我遇到了同样的问题,结果在 AppCompatTextView v23.1.0 中,复合可绘制对象会自动着色。
这是我找到的解决方案,并在下面详细说明了我这样做的原因。它不是很干净,但至少可以让您为复合绘图着色!
解决方案
将此代码放入助手 class 或您的自定义 TextView/Button :
/**
* The app compat text view automatically sets the compound drawable tints for a static array of drawables ids.
* If the drawable id is not in the list, the lib apply a null tint, removing the custom tint set before.
* There is no way to change this (private attributes/classes, only set in the constructor...)
*
* @param object the object on which to disable default tinting.
*/
public static void removeDefaultTinting(Object object) {
try {
// Get the text helper field.
Field mTextHelperField = object.getClass().getSuperclass().getDeclaredField("mTextHelper");
mTextHelperField.setAccessible(true);
// Get the text helper object instance.
final Object mTextHelper = mTextHelperField.get(object);
if (mTextHelper != null) {
// Apply tint to all private attributes. See AppCompat source code for usage of theses attributes.
setObjectFieldToNull(mTextHelper, "mDrawableStartTint");
setObjectFieldToNull(mTextHelper, "mDrawableEndTint");
setObjectFieldToNull(mTextHelper, "mDrawableLeftTint");
setObjectFieldToNull(mTextHelper, "mDrawableTopTint");
setObjectFieldToNull(mTextHelper, "mDrawableRightTint");
setObjectFieldToNull(mTextHelper, "mDrawableBottomTint");
}
} catch (NoSuchFieldException e) {
// If it doesn't work, we can do nothing else. The icons will be white, we will see it.
e.printStackTrace();
} catch (IllegalAccessException e) {
// If it doesn't work, we can do nothing else. The icons will be white, we will see it.
e.printStackTrace();
}
}
/**
* Set the field of an object to null.
*
* @param object the TextHelper object (class is not accessible...).
* @param fieldName the name of the tint field.
*/
private static void setObjectFieldToNull(Object object, String fieldName) {
try {
Field tintField;
// Try to get field from class or super class (depends on the implementation).
try {
tintField = object.getClass().getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
tintField = object.getClass().getSuperclass().getDeclaredField(fieldName);
}
tintField.setAccessible(true);
tintField.set(object, null);
} catch (NoSuchFieldException e) {
// If it doesn't work, we can do nothing else. The icons will be white, we will see it.
e.printStackTrace();
} catch (IllegalAccessException e) {
// If it doesn't work, we can do nothing else. The icons will be white, we will see it.
e.printStackTrace();
}
}
然后您可以在 class 扩展 AppCompatTextView 或 AppCompatButton 的每个构造函数上调用 removeDefaultTinting(this);
。例如:
public MyCustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
removeDefaultTinting(this);
}
有了这个,使用 v23.0.1 的代码应该可以在 v23.1.0 上工作。
我对使用反射来更改 AppCompat 库中的属性不满意,但这是我发现在 v23.1.0 的复合可绘制对象上使用着色的唯一方法。希望有人会找到更好的解决方案,或者将复合绘图着色添加到 AppCompat public 方法中。
更新
我找到了另一个更简单的解决方案:只有当您使用 xml 设置复合可绘制对象时才会出现此错误。不要在 xml 中设置它们,然后在您的代码中设置它们,它将起作用。构造函数中的错误代码,调用后设置可绘制对象不受影响。
解释
在 AppCompatTextView 构造函数中,文本助手被初始化:
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper.applyCompoundDrawablesTints();
在 TextHelper loadFromAttributes
函数中,为每个复合可绘制对象创建了一个色调列表。如您所见,mDrawableXXXTint.mHasTintList
始终设置为 true。 mDrawableXXXTint.mTintList
是将要应用的色调,只能从 AppCompat 的硬编码值中获取。对于您的自定义可绘制对象,它将始终为空。所以你最终得到一个具有空 "tint list".
的色调
TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0);
final int ap = a.getResourceId(0, -1);
// Now read the compound drawable and grab any tints
if (a.hasValue(1)) {
mDrawableLeftTint = new TintInfo();
mDrawableLeftTint.mHasTintList = true;
mDrawableLeftTint.mTintList = tintManager.getTintList(a.getResourceId(1, 0));
}
if (a.hasValue(2)) {
mDrawableTopTint = new TintInfo();
mDrawableTopTint.mHasTintList = true;
mDrawableTopTint.mTintList = tintManager.getTintList(a.getResourceId(2, 0));
}
...
问题是这种色调是在构造函数中应用的,每次设置或更改可绘制对象时:
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mBackgroundTintHelper != null) {
mBackgroundTintHelper.applySupportBackgroundTint();
}
if (mTextHelper != null) {
mTextHelper.applyCompoundDrawablesTints();
}
}
因此,如果您将色调应用于复合可绘制对象,然后调用诸如 view.setCompoundDrawablesRelativeWithIntrinsicBounds
之类的超级方法,文本助手会将其 null 色调应用于您的可绘制对象,并删除您所做的一切。 .
最后,这是应用色调的函数:
final void applyCompoundDrawableTint(Drawable drawable, TintInfo info) {
if (drawable != null && info != null) {
TintManager.tintDrawable(drawable, info, mView.getDrawableState());
}
}
parameters中的TintInfo
是texthelperclass的mDrawableXXXTint
属性。如您所见,如果它为空,则不应用任何色调。将所有可绘制对象的色调属性设置为 null 可防止 AppCompat 应用其色调,并使您能够随心所欲地处理可绘制对象。
我没有找到一种干净的方法来阻止此行为或让它应用我想要的色调。所有属性都是私有的,没有吸气剂。
你可以试试这样的
ContextCompat.getDrawable(context, R.drawable.cool_icon)?.apply {
setTint(ContextCompat.getColor(context, R.color.red))
}
我使用以下方法使用 android.support.design 23.0.1 正确着色复合绘图。现在他们发布了 23.1.0,它不再适用于 api LVL16,我所有的可绘制对象都是黑色的。
有人有建议吗?
private void setCompoundColor(TextView view) {
Drawable drawable = view.getCompoundDrawables()[0];
Drawable wrap = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(wrap, ContextCompat.getColor(this, R.color.primaryLighter2));
DrawableCompat.setTintMode(wrap, PorterDuff.Mode.SRC_IN);
wrap = wrap.mutate();
view.setCompoundDrawablesRelativeWithIntrinsicBounds(wrap, null, null, null);
}
谢谢。
上周我遇到了同样的问题,结果在 AppCompatTextView v23.1.0 中,复合可绘制对象会自动着色。
这是我找到的解决方案,并在下面详细说明了我这样做的原因。它不是很干净,但至少可以让您为复合绘图着色!
解决方案
将此代码放入助手 class 或您的自定义 TextView/Button :
/**
* The app compat text view automatically sets the compound drawable tints for a static array of drawables ids.
* If the drawable id is not in the list, the lib apply a null tint, removing the custom tint set before.
* There is no way to change this (private attributes/classes, only set in the constructor...)
*
* @param object the object on which to disable default tinting.
*/
public static void removeDefaultTinting(Object object) {
try {
// Get the text helper field.
Field mTextHelperField = object.getClass().getSuperclass().getDeclaredField("mTextHelper");
mTextHelperField.setAccessible(true);
// Get the text helper object instance.
final Object mTextHelper = mTextHelperField.get(object);
if (mTextHelper != null) {
// Apply tint to all private attributes. See AppCompat source code for usage of theses attributes.
setObjectFieldToNull(mTextHelper, "mDrawableStartTint");
setObjectFieldToNull(mTextHelper, "mDrawableEndTint");
setObjectFieldToNull(mTextHelper, "mDrawableLeftTint");
setObjectFieldToNull(mTextHelper, "mDrawableTopTint");
setObjectFieldToNull(mTextHelper, "mDrawableRightTint");
setObjectFieldToNull(mTextHelper, "mDrawableBottomTint");
}
} catch (NoSuchFieldException e) {
// If it doesn't work, we can do nothing else. The icons will be white, we will see it.
e.printStackTrace();
} catch (IllegalAccessException e) {
// If it doesn't work, we can do nothing else. The icons will be white, we will see it.
e.printStackTrace();
}
}
/**
* Set the field of an object to null.
*
* @param object the TextHelper object (class is not accessible...).
* @param fieldName the name of the tint field.
*/
private static void setObjectFieldToNull(Object object, String fieldName) {
try {
Field tintField;
// Try to get field from class or super class (depends on the implementation).
try {
tintField = object.getClass().getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
tintField = object.getClass().getSuperclass().getDeclaredField(fieldName);
}
tintField.setAccessible(true);
tintField.set(object, null);
} catch (NoSuchFieldException e) {
// If it doesn't work, we can do nothing else. The icons will be white, we will see it.
e.printStackTrace();
} catch (IllegalAccessException e) {
// If it doesn't work, we can do nothing else. The icons will be white, we will see it.
e.printStackTrace();
}
}
然后您可以在 class 扩展 AppCompatTextView 或 AppCompatButton 的每个构造函数上调用 removeDefaultTinting(this);
。例如:
public MyCustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
removeDefaultTinting(this);
}
有了这个,使用 v23.0.1 的代码应该可以在 v23.1.0 上工作。
我对使用反射来更改 AppCompat 库中的属性不满意,但这是我发现在 v23.1.0 的复合可绘制对象上使用着色的唯一方法。希望有人会找到更好的解决方案,或者将复合绘图着色添加到 AppCompat public 方法中。
更新
我找到了另一个更简单的解决方案:只有当您使用 xml 设置复合可绘制对象时才会出现此错误。不要在 xml 中设置它们,然后在您的代码中设置它们,它将起作用。构造函数中的错误代码,调用后设置可绘制对象不受影响。
解释
在 AppCompatTextView 构造函数中,文本助手被初始化:
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper.applyCompoundDrawablesTints();
在 TextHelper loadFromAttributes
函数中,为每个复合可绘制对象创建了一个色调列表。如您所见,mDrawableXXXTint.mHasTintList
始终设置为 true。 mDrawableXXXTint.mTintList
是将要应用的色调,只能从 AppCompat 的硬编码值中获取。对于您的自定义可绘制对象,它将始终为空。所以你最终得到一个具有空 "tint list".
TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0);
final int ap = a.getResourceId(0, -1);
// Now read the compound drawable and grab any tints
if (a.hasValue(1)) {
mDrawableLeftTint = new TintInfo();
mDrawableLeftTint.mHasTintList = true;
mDrawableLeftTint.mTintList = tintManager.getTintList(a.getResourceId(1, 0));
}
if (a.hasValue(2)) {
mDrawableTopTint = new TintInfo();
mDrawableTopTint.mHasTintList = true;
mDrawableTopTint.mTintList = tintManager.getTintList(a.getResourceId(2, 0));
}
...
问题是这种色调是在构造函数中应用的,每次设置或更改可绘制对象时:
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mBackgroundTintHelper != null) {
mBackgroundTintHelper.applySupportBackgroundTint();
}
if (mTextHelper != null) {
mTextHelper.applyCompoundDrawablesTints();
}
}
因此,如果您将色调应用于复合可绘制对象,然后调用诸如 view.setCompoundDrawablesRelativeWithIntrinsicBounds
之类的超级方法,文本助手会将其 null 色调应用于您的可绘制对象,并删除您所做的一切。 .
最后,这是应用色调的函数:
final void applyCompoundDrawableTint(Drawable drawable, TintInfo info) {
if (drawable != null && info != null) {
TintManager.tintDrawable(drawable, info, mView.getDrawableState());
}
}
parameters中的TintInfo
是texthelperclass的mDrawableXXXTint
属性。如您所见,如果它为空,则不应用任何色调。将所有可绘制对象的色调属性设置为 null 可防止 AppCompat 应用其色调,并使您能够随心所欲地处理可绘制对象。
我没有找到一种干净的方法来阻止此行为或让它应用我想要的色调。所有属性都是私有的,没有吸气剂。
你可以试试这样的
ContextCompat.getDrawable(context, R.drawable.cool_icon)?.apply {
setTint(ContextCompat.getColor(context, R.color.red))
}