Button
为啥在xml
设置background
没有生效?
先说结论,安卓项目创建默认使用MaterialComponents
主题,在创建button
时,不再创建AppCompatButton
,而是创建MaterialButton
,MaterialButton
在创建背景Drawble
时,给Drawable
设置了ColorFilter
,ColorFilter
类似于滤镜,导致原先颜色失效。
解析MaterialButton
的创建流程
这里笔者在activity_main
中放了一个Button
,代码很简单,懒得贴出
从MainActivity
的setContentView
开始解析
AppCompatActivity#setContentView
@Override
public void setContentView(@LayoutRes int layoutResID) {
//getDelegate()返回AppCompatDelegateImpl
getDelegate().setContentView(layoutResID);
}
AppCompatDelegateImpl#setContentView
public void setContentView(int resId) {
//省略。。
//这篇文章不解析DecorView,contentParent理解为Activity的根布局即可
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
// LayoutInflater.from(mContext)返回PhoneLayoutInflater,此类做的事情不多,逻辑大部分在父类中实现
//创建xml文件,并创建View
LayoutInflater.from(mContext).inflate(resId, contentParent);
//省略。。
}
LayoutInflater#inflate
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//得到Resources,后期寻找xml文件使用
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
//预编译成功则直接返回
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//对xml进行解析,不对xml的具体解析进行研究(c++ ResXMLTree解析),只需要知道这个方法执行完,xml就变成了很多个节点对象
XmlResourceParser parser = res.getLayout(resource);
try {
//对节点进行解析
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
//省略。。创建根布局。。
// 解析根节点下的子节点
rInflateChildren(parser, temp, attrs, true);
//省略。。
}
LayoutInflater#rInflateChildren
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
LayoutInflater#rInflate
//算法类似与深搜
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
//省略。。
//循环创建子view
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
//省略很多eles if,不重点,只分析子view的创建
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//再递归循环下一层
rInflateChildren(parser, view, attrs, true);
//添加view进父view
viewGroup.addView(view, params);
}
}
//省略。。
}
LayoutInflater#createViewFromTag
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//省略。。
//创建子view
View view = tryCreateView(parent, name, context, attrs);
//返回子view
return view;
//省略。。
}
LayoutInflater#tryCreateView
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
//省略
//创建View 不管通过哪种方式创建对于factory,onCreateView只有五个类实现,这里我们调用的是AppCompatDelegateImpl的
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
AppCompatDelegateImpl#onCreateView
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
AppCompatDelegateImpl#createView
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
//拿到主题名字
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//在AndroidManifest中,使用Theme.MaterialComponents.DayNight.DarkActionBar主题,在下面分析
//viewInflaterClassName为MaterialComponentsViewInflater
String viewInflaterClassName =
a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if (viewInflaterClassName == null) {
// Set to null (the default in all AppCompat themes). Create the base inflater
// (no reflection)
mAppCompatViewInflater = new AppCompatViewInflater();
} else {
try {
//反射创建MaterialComponentsViewInflater
Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance();
} catch (Throwable t) {
}
}
}
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
inheritContext = (attrs instanceof XmlPullParser)
// If we have a XmlPullParser, we can detect where we are in the layout
? ((XmlPullParser) attrs).getDepth() > 1
// Otherwise we have to use the old heuristic
: shouldInheritContext((ViewParent) parent);
}
//创建view
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
values.xml
<style name="Base.V14.Theme.MaterialComponents.Light" parent="Base.V14.Theme.MaterialComponents.Light.Bridge">
<item name="viewInflaterClass">com.google.android.material.theme.MaterialComponentsViewInflateritem>
回到上边继续分析
AppCompatViewInflater#createView
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
switch (name) {
//省略很多类型的子,包括img之类的,我们只分析Button
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
}
//如果不符合上述创建规则会执行反射创建View
if (view == null && originalContext != context) {
view = createViewFromTag(context, name, attrs);
}
return view;
}
上述知道Inflater
是返回的MaterialComponentsViewInflater
,此时createButton
会执行
MaterialComponentsViewInflater#createButton
protected AppCompatButton createButton(@NonNull Context context, @NonNull AttributeSet attrs) {
return new MaterialButton(context, attrs);
}
总结:主题是MaterialComponents
,此后解析器是根据主题的viewInflaterClass
来判断的,MaterialComponents
的viewInflaterClass
为MaterialComponentsViewInflater
,那么createButton
会执行MaterialComponentsViewInflater
的,因此也就创建了MaterialButton
。
拓展一下View
为空的情况,最终由
view = createViewFromTag(context, name, attrs); -> createViewByPrefix
AppCompatViewInflater#createViewByPrefix
private View createViewByPrefix(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
try {
//获取构造缓存
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
Class<? extends View> clazz = Class.forName(
prefix != null ? (prefix + name) : name,
false,
context.getClassLoader()).asSubclass(View.class);
constructor = clazz.getConstructor(sConstructorSignature);
sConstructorMap.put(name, constructor);
}
constructor.setAccessible(true);
//反射创建
//private final Object[] mConstructorArgs = new Object[2];执行两个参数的构造
return constructor.newInstance(mConstructorArgs);
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
}
}
上边分析了为啥创建MaterialButton
,不生效的原因也是因为MaterialButton
的限制,猫腻就在构造中。
此时我们重点分析构造
MaterialButton#MaterialButton
public MaterialButton(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.materialButtonStyle);
}
public MaterialButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
//这句代码也很重要,先分析这段代码
super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr);
leAttr, DEF_STYLE_RES).build();
//省略很多属性解析,和背景相关的就是下面代码
// Loads and sets background drawable attributes
//覆盖点一,下面由覆盖点一,后续分析覆盖点
materialButtonHelper = new MaterialButtonHelper(this, shapeAppearanceModel);
materialButtonHelper.loadFromAttributes(attributes);
}
super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr)
这句代码会去执行父类的构造
AppCompatButton#AppCompatButton
public AppCompatButton(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
//再往里追
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
ThemeUtils.checkAppCompatTheme(this, getContext());
//覆盖点二,分析完覆盖点一,覆盖点二就很明了了
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper = new AppCompatTextHelper(this);
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper.applyCompoundDrawablesTints();
}
在往上的构造回到Button
,再到TextView
再到View
,之间都没和本文章研究相关的代码,直接到View
的构造
View#View
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
//拿到属性集
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
final int N = a.getIndexCount();
//匹配属性集,并对相应属性进行赋值
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
//创建Drawable,这句代码会根据属性值进行判断返回不同类型的Drawable
background = a.getDrawable(attr);
break;
//省略一堆view的属性
}
}
if (background != null) {
//创建背景
setBackground(background);
}
//省略
}
Drawable
生成逻辑浅分析一下
//省略很多代码
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
final boolean isColorDrawable;
//假设再color范围内,则返回ColorDrawable,假设说再Drawable中定义了Drawable的xml则不会返回ColorDrawable,会走别的解析流程
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
} else {
isColorDrawable = false;
}
Drawable dr;
//cs的获取和缓存有关系,第一次加载肯定为null
if (cs != null) {
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
//不为color则走新的解析逻辑
dr = loadDrawableForCookie(wrapper, value, id, density);
}
}
新的解析逻辑最终执行
DrawableInflater#inflateFromXmlForDensity
@NonNull
Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
//获取属性名
if (name.equals("drawable")) {
name = attrs.getAttributeValue(null, "class");
if (name == null) {
throw new InflateException(" tag must specify class attribute" );
}
}
//创建Drawable,重点
Drawable drawable = inflateFromTag(name);
if (drawable == null) {
drawable = inflateFromClass(name);
}
drawable.setSrcDensityOverride(density);
drawable.inflate(mRes, parser, attrs, theme);
return drawable;
}
DrawableInflater#inflateFromTag
private Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
case "animated-selector":
return new AnimatedStateListDrawable();
case "level-list":
return new LevelListDrawable();
case "layer-list":
return new LayerDrawable();
case "transition":
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
case "adaptive-icon":
return new AdaptiveIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
return new GradientDrawable();
case "vector":
return new VectorDrawable();
case "animated-vector":
return new AnimatedVectorDrawable();
case "scale":
return new ScaleDrawable();
case "clip":
return new ClipDrawable();
case "rotate":
return new RotateDrawable();
case "animated-rotate":
return new AnimatedRotateDrawable();
case "animation-list":
return new AnimationDrawable();
case "inset":
return new InsetDrawable();
case "bitmap":
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
case "animated-image":
return new AnimatedImageDrawable();
default:
return null;
}
}
上边代码就很熟悉了,假设我们在Drawable
中定义了一个bitmap
,那么这个bitmap
最终会解析成BitmapDrawable
,本文章没有给指定
Drawable
,只是简单的指定了color
,a.getDrawable(attr);
返回的是ColorDrawable
返回View#View分析setBackground
View#setBackground
public void setBackground(Drawable background) {
//noinspection deprecation
setBackgroundDrawable(background);
}
View#setBackgroundDrawable
public void setBackgroundDrawable(Drawable background) {
//省略
//绑定背景,后续会在draw中,调用Drawable的draw方法,完成绘制
mBackground = background;
//省略
}
绘制是在后面,还未触发,此时View
给mBackground
设定了值,是在xml
指定的background
的值,此时回到MaterialButton#MaterialButton的覆盖点1,分析覆盖点
MaterialButtonHelper#loadFromAttributes
void loadFromAttributes(@NonNull TypedArray attributes) {
//只关心背景色,其他代码不关系
//模式
backgroundTintMode =
ViewUtils.parseTintMode(
attributes.getInt(R.styleable.MaterialButton_backgroundTintMode, -1), Mode.SRC_IN);
//颜色 下面分析R.styleable.MaterialButton_backgroundTint
backgroundTint =
MaterialResources.getColorStateList(
materialButton.getContext(), attributes, R.styleable.MaterialButton_backgroundTint);
// xml直接设定background会执行true
if (attributes.hasValue(R.styleable.MaterialButton_android_background)) {
//后续只分析此代码
setBackgroundOverwritten();
} else {
//默认情况不分析,拿到backgroundTint的颜色创建MaterialShapeDrawable
updateBackground();
}
}
分析MaterialButton_backgroundTint
之前要先分析MaterialButton
三个参数的构造,如果对View
的构造不了解可以先百度自行学习每种构造的用途和区别
MaterialButton#MaterialButton
public MaterialButton(@NonNull Context context, @Nullable AttributeSet attrs) {
//也就意味着上方的MaterialButton_backgroundTint是materialButtonStyle的某一个属性
this(context, attrs, R.attr.materialButtonStyle);
}
找到materialButtonStyle
values.xml
<attr format="reference" name="materialButtonStyle"/>
<item name="materialButtonStyle">@style/Widget.MaterialComponents.Buttonitem>
<style name="Widget.MaterialComponents.Button" parent="Widget.AppCompat.Button">
- "enforceMaterialTheme"
>true
- "android:background"
>@empty
- "enforceTextAppearance">true
- "android:textAppearance">?attr/textAppearanceButton
- "android:textColor">@color/mtrl_btn_text_color_selector
- "android:paddingLeft">@dimen/mtrl_btn_padding_left
- "android:paddingRight">@dimen/mtrl_btn_padding_right
- "android:paddingTop">@dimen/mtrl_btn_padding_top
- "android:paddingBottom">@dimen/mtrl_btn_padding_bottom
- "android:insetLeft">0dp
- "android:insetRight">0dp
- "android:insetTop">@dimen/mtrl_btn_inset
- "android:insetBottom">@dimen/mtrl_btn_inset
- "android:stateListAnimator" ns2:ignore="NewApi">@animator/mtrl_btn_state_list_anim
- "cornerRadius">@null
- "elevation">@dimen/mtrl_btn_elevation
- "iconPadding">@dimen/mtrl_btn_icon_padding
- "iconTint">@color/mtrl_btn_text_color_selector
- "rippleColor">@color/mtrl_btn_ripple_color
- "backgroundTint">@color/mtrl_btn_bg_color_selector
- "shapeAppearance">?attr/shapeAppearanceSmallComponent
style>
mtrl_btn_bg_color_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorPrimary" android:state_enabled="true"/>
<item android:alpha="0.12" android:color="?attr/colorOnSurface"/>
selector>
此文章重点分析为啥background
失效,初心不能变,回到MaterialButtonHelper#loadFromAttributes,此时设置了background
则走true
MaterialButtonHelper#setBackgroundOverwritten
void setBackgroundOverwritten() {
backgroundOverwritten = true;
// AppCompatButton re-applies any tint that was set when background is changed, so we must
// pass our tints to AppCompatButton when background is overwritten.
materialButton.setSupportBackgroundTintList(backgroundTint);
materialButton.setSupportBackgroundTintMode(backgroundTintMode);
}
MaterialButton#setSupportBackgroundTintList
public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
if (isUsingOriginalBackground()) {
materialButtonHelper.setSupportBackgroundTintList(tint);
} else {
//走下面代码
super.setSupportBackgroundTintList(tint);
}
}
下面代码就是流程,简单看一下
AppCompatButton#setSupportBackgroundTintList
public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
if (mBackgroundTintHelper != null) {
mBackgroundTintHelper.setSupportBackgroundTintList(tint);
}
}
AppCompatBackgroundHelper#setSupportBackgroundTintList
void setSupportBackgroundTintList(ColorStateList tint) {
applySupportBackgroundTint();
}
AppCompatBackgroundHelper#applySupportBackgroundTint
void applySupportBackgroundTint() {
final Drawable background = mView.getBackground();
if (mBackgroundTint != null) {
AppCompatDrawableManager.tintDrawable(background, mBackgroundTint,
mView.getDrawableState());
}
}
AppCompatDrawableManager#tintDrawable
static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {
ResourceManagerInternal.tintDrawable(drawable, tint, state);
}
ResourceManagerInternal#tintDrawable
static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {
//我擦勒,添加了滤镜,怎么创建的我们不管,这是MaterialButton的细节,我们只需要对Filter的颜色进行验证是否是上述的colorPrimary即可
drawable.setColorFilter(createTintFilter(
tint.mHasTintList ? tint.mTintList : null,
tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE,
state));
}
关于ColorFilter
可以自行百度,理解成图片滤镜即可
分析到这,其实答案就出来了,Drawable
的颜色确实是那个颜色,为啥没生效呢,因为加了滤镜,后续我们验证一下滤镜颜色
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取button
button = findViewById(R.id.button);
if (button instanceof MaterialButton) {
//反射获取Drawable
Drawable drawable = (Drawable) ReflectionUtils.getFieldValue(button,"mBackground");
//拿到画笔
Paint paint = (Paint) ReflectionUtils.getFieldValue(drawable,"mPaint");
//拿到颜色滤镜
PorterDuffColorFilter porterDuffColorFilter = (PorterDuffColorFilter)paint.getColorFilter();
try {
//越过反射黑名单
Method forName = Class.class.getDeclaredMethod("forName", String.class);
Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
Class<?> clazz = (Class<?>) forName.invoke(null, "android.graphics.PorterDuffColorFilter");
Method getColor = (Method) getDeclaredMethod.invoke(clazz, "getColor", null);
int color = (int) getColor.invoke(porterDuffColorFilter);
//滤镜颜色
Log.e("FilterColor", color + "");
//画笔颜色
Log.e("paintColor", paint.getColor()+ "" );
//xml设定的黑色
Log.e("blackColor", this.getResources().getColor(R.color.black)+ "");
//colorPrimary颜色
Log.e("colorPrimaryColor", this.getResources().getColor(R.color.purple_500) + "" );
// 2022-03-31 12:47:51.342 11408-11408/com.hbsd.myviewconstructor E/FilterColor: -10354450
// 2022-03-31 12:47:51.342 11408-11408/com.hbsd.myviewconstructor E/paintColor: -16777216
// 2022-03-31 12:47:51.342 11408-11408/com.hbsd.myviewconstructor E/blackColor: -10354450
// 2022-03-31 12:47:51.342 11408-11408/com.hbsd.myviewconstructor E/colorPrimaryColor: -16777216
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
上述log
发现FilterColor = blackColor
, paintColor = colorPrimaryColor
则验证上述猜想
总结:Drawable
确实是正确的,只是Drawable
被加上了滤镜,导致原先的Drawable
看不见了,被盖上了一层。
进入MaterialButton
的set
方法
MaterialButton#setBackgroundColor
public void setBackgroundColor(@ColorInt int color) {
//xml不指定属性走true,后续分析为什么
if (isUsingOriginalBackground()) {
materialButtonHelper.setBackgroundColor(color);
} else {
// If default MaterialButton background has been overwritten, we will let View handle
// setting the background color.
super.setBackgroundColor(color);
}
}
MaterialButtonHelper#setBackgroundColor
void setBackgroundColor(int color) {
if (getMaterialShapeDrawable() != null) {
//MaterialButtonHelper#loadFromAttributes中创建了MaterialShapeDrawable,setTint就类似于修改颜色
getMaterialShapeDrawable().setTint(color);
}
}
这么写就给之前的Drawable
修改了颜色,能生效
分析为啥isUsingOriginalBackground()
为true
private boolean isUsingOriginalBackground() {
//materialButtonHelper一定不为空,关键在于!materialButtonHelper.isBackgroundOverwritten(),也就是backgroundOverwritten属性
return materialButtonHelper != null && !materialButtonHelper.isBackgroundOverwritten();
}
backgroundOverwritten
默认为false
,只在一个地方修改成了true
,MaterialButtonHelper#loadFromAttributes的true
分支,此次情况讨论的是xml
不指定背景,因此MaterialButtonHelper#loadFromAttributes一定走false
分,从而出现上面的情况
和之前分析一样,指定background
,那么MaterialButtonHelper#loadFromAttributes走true
分支,在后续set
的时候走else
分支
MaterialButton#setBackgroundColor
public void setBackgroundColor(@ColorInt int color) {
//xml不指定属性走true,后续分析为什么
if (isUsingOriginalBackground()) {
materialButtonHelper.setBackgroundColor(color);
} else {
// If default MaterialButton background has been overwritten, we will let View handle
// setting the background color.
super.setBackgroundColor(color);
}
}
AppCompatButton#setBackgroundDrawable
public void setBackgroundDrawable(Drawable background) {
super.setBackgroundDrawable(background);
if (mBackgroundTintHelper != null) {
mBackgroundTintHelper.onSetBackgroundDrawable(background);
}
}
AppCompatBackgroundHelper#onSetBackgroundDrawable
void onSetBackgroundDrawable(Drawable background) {
mBackgroundResId = -1;
// We don't know that this drawable is, so we need to clear the default background tint
//没有修改颜色,还是最初的颜色
setInternalBackgroundTint(null);
//不生效的重点,之前已经分析过了,通过此方法给Drawable设置滤镜
applySupportBackgroundTint();
}
有滤镜意味着Drawable
原本的样子失效
代码不在贴出,很简单,读者自行尝试即可
直接不使用MaterialComponents
主题,修改AndroidManifest
的theme
属性
修改主题中的colorPrimary
属性,这样修改会导致很多颜色无法正常显示,不建议
在xml
不指定Background
即可通过set
直接修改,如果指定的话需要反射hook backgroundOverwritten
为false
本篇文章到此就结束了
✨ 原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下
点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!
⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!
✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!