一、 背景
有个需求出了个bug,一个TextView的宽度是根据文本宽度和padding算出来赋值的,但是当textview的背景是.9图的时候,文本居然显示不全。
why?
咨询了大佬,可能是.9图的问题。
二、 分析过程
迷茫
断点之后发现,textView莫名其妙多了1dp的paddingRight
。
大概意思就是这样:
看了xml和代码,非常确信没有设置paddingRight
。
有点头绪
那这个paddingRight
是哪儿来的?
从其他同事那了解到可能是.9图引起的,于是去搜了一下相关的文章。
确实,如果.9图有padding的话,.9图的padding会覆盖掉view的padding。
但是,我又想起来,这个textview我给它设置了左边距,为啥左边没有覆盖掉?
小小的脑袋,大大的疑惑…
新的疑问
之前改bug的时候,加了一个setPadding
的方法,但是发现setBackground
之后,左边距就不是我设置的值了。
把setPadding注释掉,左边距又ok了。
然后我想到我在xml里面设置过左边距,难道是xml设置了就不会影响?
现在需要排查的有以下几个点:
- 绘制的时候,padding是怎么来的?
setBackground
的时候发生了什么,为什么会覆盖掉padding?setPadding
做了什么,为什么通过这个方法设置的padding会被覆盖?- xml设置padding是怎么设置的,为什么不会被覆盖?
三、 源码分析
以左边距为例,右边距同理。
1.绘制的时候,padding怎么来的?
直接找到onDraw()
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
mPaddingLeft
赋值的地方有四个:
所以,要么就是mUserPaddingStart
,要么就是mUserPaddingLeftInitial
。
2. setBackground
的时候发生了什么,为什么会覆盖掉padding?
if (background.getPadding(padding)) { // 背景有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;
}
如果背景里面有padding,就会把mUserPaddingLeftInitial
设置为背景的paddingLeft
。
所以,如果绘制时候的padding是mUserPaddingLeftInitial
,就会发生.9图padding覆盖view的padding的情况。
3. setPadding
做了什么,为什么通过这个方法设置的padding会被覆盖?
public void setPadding(int left, int top, int right, int bottom) {
resetResolvedPaddingInternal();
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
mUserPaddingLeftInitial = left;
mUserPaddingRightInitial = right;
mLeftPaddingDefined = true;
mRightPaddingDefined = true;
internalSetPadding(left, top, right, bottom);
}
把mUserPaddingStart
重置了,同时给mUserPaddingLeftInitial
赋值。
1中讲到,绘制时的paddingLeft
不是mUserPaddingStart
,就是mUserPaddingLeftInitial
。
mPaddingLeft = (mUserPaddingStart >= 0) ? mUserPaddingStart : mUserPaddingLeftInitial;
如果mUserPaddingStart<0
,那么 mPaddingLeft = mUserPaddingLeftInitial
。
很明显,用setPadding
方法设置了边距,此时mUserPaddingStart
被重置,mPaddingLeft
的取值就是mUserPaddingLeftInitial
的值。setBackgroud
之后,backgroud的paddig赋值给了mUserPaddingLeftInitial
,导致绘制时的paddingLeft
为background的paddingLeft
,不是预期的表现。
4.xml设置padding是怎么设置的,为什么不会被覆盖?
xml中设置左边距有两种设置方法,paddingLeft
和paddingStart
case com.android.internal.R.styleable.View_paddingLeft:
leftPadding = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = leftPadding;
leftPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingStart:
startPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
startPaddingDefined = (startPadding != UNDEFINED_PADDING);
break;
...
mUserPaddingStart = startPadding;
...
if (!mLeftPaddingDefined && startPaddingDefined) {
leftPadding = startPadding;
}
mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial;
逻辑大概就是:
如果是设置的paddingStart
,那么mUserPaddingLeftInitial
和mUserPaddingStart
都会赋值;如果是设置的paddingLeft
, 那么mUserPaddingStart
不会赋值。
确认了一下xml中的边距是paddingStrat
,符合预期。修改为paddingLeft
打包之后,就会被覆盖。
四、 结论
view的backgroud如果有padding,会覆盖掉view设置的paddingLeft
、paddingRight
。
如果想要不被覆盖,可以用paddingStart
和paddingRight
。
更多思考:
虽然设置paddingStart
、paddingRight
可以解决问题。但是,这个坑还是比较容易踩。
首先,作为开发,不一定会去仔细了解设置提供的资源是不是.9图,有没有padding;
其次,设置的时候,更多的时候关注的是有padding的情况,比如说这次的坑,我只设置了paddingStart
,右边又没有边距,谁会多此一举给它设置paddingRight=0
呢,所以多出来一个1dp的右边距。
然后,虽然说现在不推荐用paddingLeft
,但不推荐归不推荐,用还是可能用的。