美文网首页
修改背景颜色也能如入歧途?

修改背景颜色也能如入歧途?

作者: 假装门口当前台 | 来源:发表于2022-08-25 18:25 被阅读0次

    背景

    近期在开发一些界面,需要在不同场景弹出同一个界面,位移的区别就是改一下界面的背景颜色。但是在项目中发现改了颜色会导致全局的其他界面的相同颜色背景都改了。

    布局如下

    <View
        android:id="@+id/vMask"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/overlay_page_mask"
        android:alpha="0"
        android:visibility="gone"/>
    

    在不同场景修改背景颜色,刚好调用了项目中封装的扩展方法,直接修改drawble的颜色

    binding.vMask.backgroundColor(R.color.overlay_layer_background.asColor(appContext))
    
    fun View.backgroundColor(@ColorInt color: Int) {
        this.background.let {
            when (it) {
                //原本已设置背景的,直接修改背景色,其他属性会得以保留,比如圆角
                is GradientDrawable -> it.setColor(color)
                is ColorDrawable -> it.color = color
                null -> background = GradientDrawable().apply { setColor(color) }
            }
        }
    }
    

    问题来了,为什么我调用这个方法,会导致全局的同个颜色的背景都跟着修改,先来看看布局加载背景是怎么来的。开启源码阅读之旅

    frameworks/base/core/java/android/view/View.java

     public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            this(context);
    
            mSourceLayoutId = Resources.getAttributeSetSourceResId(attrs);
    
            final TypedArray a = context.obtainStyledAttributes(
                    attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
    
            retrieveExplicitStyle(context.getTheme(), attrs);
            saveAttributeDataForStyleable(context, com.android.internal.R.styleable.View, attrs, a,
                    defStyleAttr, defStyleRes);
    
            if (sDebugViewAttributes) {
                saveAttributeData(attrs, a);
            }
    
            Drawable background = null;    
            ...
            ...
    
            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:
                        background = a.getDrawable(attr);
                        break;
                    case com.android.internal.R.styleable.View_padding:
                        padding = a.getDimensionPixelSize(attr, -1);
                        mUserPaddingLeftInitial = padding;
                        mUserPaddingRightInitial = padding;
                        leftPaddingDefined = true;
                        rightPaddingDefined = true;
    

    是从TypedArray.java 中的getDrawable

    frameworks/base/core/java/android/content/res/TypedArray.java

       public Drawable getDrawable(@StyleableRes int index) {
            return getDrawableForDensity(index, 0);
        }
    
       public Drawable getDrawable(@StyleableRes int index) {
            return getDrawableForDensity(index, 0);
        }
          @Nullable
        public Drawable getDrawableForDensity(@StyleableRes int index, int density) {
            if (mRecycled) {
                throw new RuntimeException("Cannot make calls to a recycled instance!");
            }
    
            final TypedValue value = mValue;
            if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
                if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                    throw new UnsupportedOperationException(
                            "Failed to resolve attribute at index " + index + ": " + value
                                    + ", theme=" + mTheme);
                }
    
                if (density > 0) {
                    // If the density is overridden, the value in the TypedArray will not reflect this.
                    // Do a separate lookup of the resourceId with the density override.
                    mResources.getValueForDensity(value.resourceId, density, value, true);
                }
                return mResources.loadDrawable(value, value.resourceId, density, mTheme);
            }
            return null;
        }
    

    frameworks/base/core/java/android/content/res/Resources.java

    加载drawble

     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
                throws NotFoundException {
            return mResourcesImpl.loadDrawable(this, value, id, density, theme);
        }
    
    

    frameworks/base/core/java/android/content/res/ResourcesImpl.java

        @Nullable
        Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
                int density, @Nullable Resources.Theme theme)
                throws NotFoundException {
            // If the drawable's XML lives in our current density qualifier,
            // it's okay to use a scaled version from the cache. Otherwise, we
            // need to actually load the drawable from XML.
            final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
    
            // Pretend the requested density is actually the display density. If
            // the drawable returned is not the requested density, then force it
            // to be scaled later by dividing its density by the ratio of
            // requested density to actual device density. Drawables that have
            // undefined density or no density don't need to be handled here.
            if (density > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
                if (value.density == density) {
                    value.density = mMetrics.densityDpi;
                } else {
                    value.density = (value.density * mMetrics.densityDpi) / density;
                }
            }
    
            try {
                if (TRACE_FOR_PRELOAD) {
                    // Log only framework resources
                    if ((id >>> 24) == 0x1) {
                        final String name = getResourceName(id);
                        if (name != null) {
                            Log.d("PreloadDrawable", name);
                        }
                    }
                }
    
                final boolean isColorDrawable;
                final DrawableCache caches;
                final long key;
                if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
                        && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
                    isColorDrawable = true;
                    caches = mColorDrawableCache;
                    key = value.data;
                } else {
                    isColorDrawable = false;
                    caches = mDrawableCache;
                    key = (((long) value.assetCookie) << 32) | value.data;
                }
    
                // First, check whether we have a cached version of this drawable
                // that was inflated against the specified theme. Skip the cache if
                // we're currently preloading or we're not using the cache.
                if (!mPreloading && useCache) {
                // 缓存
                    final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
                    if (cachedDrawable != null) {
                        cachedDrawable.setChangingConfigurations(value.changingConfigurations);
                        return cachedDrawable;
                    }
                }
    
                // Next, check preloaded drawables. Preloaded drawables may contain
                // unresolved theme attributes.
                final Drawable.ConstantState cs;
                if (isColorDrawable) {
                    cs = sPreloadedColorDrawables.get(key);
                } else {
                    cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
                }
    
                Drawable dr;
                boolean needsNewDrawableAfterCache = false;
                if (cs != null) {
                    dr = cs.newDrawable(wrapper);
                } else if (isColorDrawable) {
                    dr = new ColorDrawable(value.data);
                } else {
                    dr = loadDrawableForCookie(wrapper, value, id, density);
                }
                // DrawableContainer' constant state has drawables instances. In order to leave the
                // constant state intact in the cache, we need to create a new DrawableContainer after
                // added to cache.
                if (dr instanceof DrawableContainer)  {
                    needsNewDrawableAfterCache = true;
                }
    
                // Determine if the drawable has unresolved theme attributes. If it
                // does, we'll need to apply a theme and store it in a theme-specific
                // cache.
                final boolean canApplyTheme = dr != null && dr.canApplyTheme();
                if (canApplyTheme && theme != null) {
                    dr = dr.mutate();
                    dr.applyTheme(theme);
                    dr.clearMutated();
                }
    
                // If we were able to obtain a drawable, store it in the appropriate
                // cache: preload, not themed, null theme, or theme-specific. Don't
                // pollute the cache with drawables loaded from a foreign density.
                if (dr != null) {
                    dr.setChangingConfigurations(value.changingConfigurations);
                    if (useCache) {
                        cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
                        if (needsNewDrawableAfterCache) {
                            Drawable.ConstantState state = dr.getConstantState();
                            if (state != null) {
                                dr = state.newDrawable(wrapper);
                            }
                        }
                    }
                }
    
                return dr;
            } catch (Exception e) {
                String name;
                try {
                    name = getResourceName(id);
                } catch (NotFoundException e2) {
                    name = "(missing name)";
                }
    
                // The target drawable might fail to load for any number of
                // reasons, but we always want to include the resource name.
                // Since the client already expects this method to throw a
                // NotFoundException, just throw one of those.
                final NotFoundException nfe = new NotFoundException("Drawable " + name
                        + " with resource ID #0x" + Integer.toHexString(id), e);
                nfe.setStackTrace(new StackTraceElement[0]);
                throw nfe;
            }
        }
    

    frameworks/base/core/java/android/content/res/DrawableCache.java

    缓存中是key->drawble

        public Drawable getInstance(long key, Resources resources, Resources.Theme theme) {
            final Drawable.ConstantState entry = get(key, theme);
            if (entry != null) {
                return entry.newDrawable(resources, theme);
            }
    
            return null;
        }
    

    可以看出,从布局中定义的background,如果是纯色,首次会创建Drawable.ConstantState并且缓存,所以在布局中所有的纯色,其实对应的都是同一个drawble。

    现在回到问题,调用下面方法,会修改drawble的信息,但是缓存中key没变,所以导致页面中的overlay_page_mask都被修改了。

    View.backgroundColor(@ColorInt color: Int) {
    // background Drawable
        this.background.let {
            when (it) {
                //原本已设置背景的,直接修改背景色,其他属性会得以保留,比如圆角
                is GradientDrawable -> it.setColor(color)
                is ColorDrawable -> it.color = color
                null -> background = GradientDrawable().apply { setColor(color) }
            }
        }
    }
    

    问题解决,如果需要直接修改drawble,可以通过mutate方法,官方注释也说明了该问题的

       /**
         * Make this drawable mutable. This operation cannot be reversed. A mutable
         * drawable is guaranteed to not share its state with any other drawable.
         * This is especially useful when you need to modify properties of drawables
         * loaded from resources. By default, all drawables instances loaded from
         * the same resource share a common state; if you modify the state of one
         * instance, all the other instances will receive the same modification.
         *
         * Calling this method on a mutable Drawable will have no effect.
         *
         * @return This drawable.
         * @see ConstantState
         * @see #getConstantState()
         */
        public @NonNull Drawable mutate() {
            return this;
        }
    
    
    View.backgroundColor(@ColorInt color: Int) {
    // background Drawable
        this.background.mutate().let {
            when (it) {
                //原本已设置背景的,直接修改背景色,其他属性会得以保留,比如圆角
                is GradientDrawable -> it.setColor(color)
                is ColorDrawable -> it.color = color
                null -> background = GradientDrawable().apply { setColor(color) }
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:修改背景颜色也能如入歧途?

          本文链接:https://www.haomeiwen.com/subject/nxqxnrtx.html