Background 导致 Padding 无效解析

来源:互联网 发布:linxu修改ssh端口号 编辑:程序博客网 时间:2024/06/05 04:46

1、问题描述
很多同学可能都遇到过这个问题:
明明在布局文件中设置了View的padding, 然后程序中动态设置了背景, 运行后发现padding不对。

如下代码:

<TextView        android:id="@+id/text"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:background="@drawable/bg"        android:padding="20dp" />text.setBackgroundResource(R.drawable.patch9_bg);

明明加了20dp的padding值, 实际效果显示却不正常。

2、问题原因
问题就出在, 我们在代码中动态设置该View的Background

text.setBackgroundResource(R.drawable.patch9_bg);

删除该代码, 显示效果正常。

3、解决方案
解决办法无外乎就是在调用setBackgroundResource之后再设置一遍padding值。
可参考http://stackoverflow.com/questions/10095196/whered-padding-go-when-setting-background-drawable

4、探个究竟
正所谓知其然, 知其所以然。本文的目的也并不是说就提出这个问题及其解决方案, 更多的是想更深层次的取了解这个问题的Root Cause. 从中了解下Google开发人员为什么会埋下这么一个”坑”, 是否是有其他考量.

我们来看下setBackgroundResource做了什么:
先来看下View.java节选代码(请注意中文注释的关键代码):

public void setBackgroundResource(@DrawableRes int resid) {    if (resid != 0 && resid == mBackgroundResource) {        return;    }    Drawable d = null;    if (resid != 0) {        // 1, 通过Resource Id获取Drawable对象.        d = mContext.getDrawable(resid);    }    setBackground(d);    mBackgroundResource = resid;}public void setBackground(Drawable background) {    setBackgroundDrawable(background);}public void setBackgroundDrawable(Drawable background) {    ...    if (background != null) {        // 2, 取一个存放padding值的Rect对象        // Rect是一个持有四个值(left, top, right bottom)的矩形对象.        Rect padding = sThreadLocal.get();        ...        // 3, 判断用来设置background的Drawable对象是否有padding值.         // 注意, 这里将存放padding值Rect传入了.        if (background.getPadding(padding)) {            // 4, 如果background有padding值, 则改变该View的padding属性.            resetResolvedPaddingInternal();                switch (background.getLayoutDirection()) {                    case LAYOUT_DIRECTION_RTL:                        mUserPaddingLeftInitial = padding.right;                        mUserPaddingRightInitial = padding.left;                        internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);                        break;                    case LAYOUT_DIRECTION_LTR:                    default:                        mUserPaddingLeftInitial = padding.left;                        mUserPaddingRightInitial = padding.right;                        internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);                }                mLeftPaddingDefined = false;                mRightPaddingDefined = false;        }        ...    } else {        /* Remove the background */        mBackground = null;        ...    }    ...    invalidate(true);}

我们发现, 第3步background.getPadding(padding)是一个关键点, 来看下Drawable的此方法实现:
Drawable.java节选代码:

public abstract class Drawable {    ...    /**     * Return in padding the insets suggested by this Drawable for placing     * content inside the drawable's bounds. Positive values move toward the     * center of the Drawable (set Rect.inset).     *     * @return true if this drawable actually has a padding, else false. When false is returned,     * the padding is always set to 0.     */    public boolean getPadding(@NonNull Rect padding) {        padding.set(0, 0, 0, 0);        return false;    }    ...}

根据注释和代码, 我们发现:
(1)默认返回false.
(2)传入的Rect会被设值.
(3)Drawable是一个抽象类, 那么我们这里setBackgroundResource时通过resId生成的Drawable到底是那个Drawable呢? 它的getPadding方法会做什么处理呢?

一般来说, 我们用到的最多的就是 png、 .9、state selector图片作为resource了.

我们来分别看下BitmapDrawable, NinePatchDrawable, StateListDrawable的getPadding实现:
可以发现, BitmapDrawable和StateListDrawable都没有重写该方法.
NinePatchDrawable的getPadding重写实现:

 @Override    public boolean getPadding(Rect padding) {        final Rect scaledPadding = mPadding;        if (scaledPadding != null) {            if (needsMirroring()) {                padding.set(scaledPadding.right, scaledPadding.top,scaledPadding.left, scaledPadding.bottom);            } else {                padding.set(scaledPadding);            }            // 当此NinePatchDrawable的mPadding的任意一方有值, 则返回true.             // 意味着会改变View的当前padding情况.            return (padding.left | padding.top | padding.right | padding.bottom) != 0;        }        return false;    }

5、分析验证
如第四章节的分析, 我们大体可以得出结论:
只有设置background是.9图片时才有可能会改变原来的padding, 导致padding无效.
为什么说是有可能呢? 因为根据代码, 当我们的.9图片自身四边都不带padding的时候, 返回的也是false, 不会改变原来的padding值.
以上结论针对我们调研的几种常用的Drawable实现, 如有特殊的Drawable请查看其源码的getPadding实现.
那么我们来验证下我们的分析, 做一个Demo, 有三个View, 第一个使用png的普通背景, 第二个使用有padding的.9图片, 第三个使用无padding的.9图片.

mTextView1.setBackgroundResource(R.drawable.bg);mTextView2.setBackgroundResource(R.drawable.patch9_bg);mTextView3.setBackgroundResource(R.drawable.patch9_bg_no_padding);

可以看到, 这个例子完全验证了我们的分析:
当设置Background图片资源是有padding的.9图片时才会出现导致原来的设置的padding无效的问题.

顺带提下, 何为有padding的.9图片, 如下图:
这里写图片描述
我们在做.9图时, 左上是patch域(即交叉点放大适配), 下方, 右方是内容域, 如果没有全部画上, 留下的部分是不能放内容的, 就形成了Padding.

6、 结语
这个问题, 可能会让很多人苦恼, 比较难以定位, 可能只是代码调用的顺序问题.
也可以说并不困难, 因为知道其根本原因后, 规避解决起来也的确很简单.
在此, 我更多的是想借这个问题来传达一种做技术的”知其然, 知其所以然“的探索思想. 共勉之.

原文链接:http://www.jianshu.com/p/4432b19ec6cd

原创粉丝点击