Android Button background失效原理解析

文章目录

  • Android Button修改不了背景色原理解析
    • 代码解析
      • 为何创建的是MaterialButton
        • 初期解析流程分析
        • 创建子View分析
      • 为何background不生效
        • 构造函数解析
          • 小插曲Drawable解析
        • 滤镜添加过程解析
        • 验证
    • 奇怪现象解析
      • xml不指定background,java代码set修改生效
      • xml指定background,java代码set修改失败
    • 解决方法
      • 修改主题
      • 修改colorPrimary
      • java代码修改Background

Android Button修改不了背景色原理解析

Button为啥在xml设置background没有生效?

先说结论,安卓项目创建默认使用MaterialComponents主题,在创建button时,不再创建AppCompatButton,而是创建MaterialButtonMaterialButton在创建背景Drawble时,给Drawable设置了ColorFilterColorFilter类似于滤镜,导致原先颜色失效。

代码解析

解析MaterialButton的创建流程

这里笔者在activity_main中放了一个Button,代码很简单,懒得贴出

为何创建的是MaterialButton

MainActivitysetContentView开始解析

初期解析流程分析

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);
            }
        }
    
    //省略。。
}

创建子View分析

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来判断的,MaterialComponentsviewInflaterClassMaterialComponentsViewInflater,那么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;
    }
}

为何background不生效

上边分析了为啥创建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生成逻辑浅分析一下

//省略很多代码
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,只是简单的指定了colora.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;
    
    //省略
}

绘制是在后面,还未触发,此时ViewmBackground设定了值,是在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 = blackColorpaintColor = colorPrimaryColor则验证上述猜想

总结:Drawable确实是正确的,只是Drawable被加上了滤镜,导致原先的Drawable看不见了,被盖上了一层。

奇怪现象解析

xml不指定background,java代码set修改生效

进入MaterialButtonset方法

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,只在一个地方修改成了trueMaterialButtonHelper#loadFromAttributestrue分支,此次情况讨论的是xml不指定背景,因此MaterialButtonHelper#loadFromAttributes一定走false分,从而出现上面的情况

xml指定background,java代码set修改失败

和之前分析一样,指定background,那么MaterialButtonHelper#loadFromAttributestrue分支,在后续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主题,修改AndroidManifesttheme属性

修改colorPrimary

修改主题中的colorPrimary属性,这样修改会导致很多颜色无法正常显示,不建议

java代码修改Background

xml不指定Background即可通过set直接修改,如果指定的话需要反射hook backgroundOverwrittenfalse

本篇文章到此就结束了

原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}

点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!}

⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!}

✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!}

你可能感兴趣的:(安卓UI,android)