Android基础之9-Patch(9.PNG)与padding

在上一篇文章记一次9.png的填坑之旅最后,我留了两个个问题:

  1. 为什么使用9.PNG设置背景之后会改变控件的padding属性值
  2. 在我所遇到的问题中9.PNG明明改变的是padding值为什么却产生了margin的效果

这篇文章就来解答这两个问题以及9.PNG使用过程中需要注意的地方。

各位同学如果没看过记一次9.png的填坑之旅这篇文章可以先去看看,因为我下面所讲的内容都是根据这篇文章中的问题来讲的。

问题9-Patch图

还是先来看下我所使用的9.PNG的图

从图中可以看出中间那一片白色区域是内容区,而内容区到底部的黑边线之间还一段透明的区域,这段区域是不会显示内容的。

结论猜想

看到这个透明区域联想到我上面遇到的问题

明明改变的是padding却产生了margin的效果

大家是不是会出现一个猜想,难道上篇文章中padding改变却出现margin效果是因为这个透明的区域导致的?

恭喜你,答对了,我前面遇到的问题中padding改变却产生margin效果就是因为这个透明区域导致的。

你可能又要接着问了,这是为什么呢?

不要走开,下面我就来说说其中的原因。

一探setBackgroundResource

我们来看看布局设置背景图片的方法setBackgroundResource(int resid)是如何实现的

1
2
3
4
5
6
7
8
9
10
11
public void setBackgroundResource(int resid) {
if (resid != 0 && resid == mBackgroundResource) {
return;
}
Drawable d = null;
if (resid != 0) {
d = mContext.getDrawable(resid);
}
setBackground(d);
mBackgroundResource = resid;
}

上面的代码可以看到调用了setBackground(Drawable drawable)这个方法,我们接着看

1
2
3
public void setBackground(Drawable background) {
setBackgroundDrawable(background);
}

我们接着看setBackgroundDrawable(Drawable drawable)这个方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void setBackgroundDrawable(Drawable background) {
...
if (background != null) {
Rect padding = sThreadLocal.get();
if (padding == null) {
padding = new Rect();
sThreadLocal.set(padding);
}
resetResolvedDrawables();
background.setLayoutDirection(getLayoutDirection());
if (background.getPadding(padding)) {
resetResolvedPadding();
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 {
...
}
...
}

上面的代码很明显可以看到当background.getPadding(padding)返回true的时候当前布局视图会通过internalSetPadding(padding.left, padding.top, padding.right, padding.bottom)给当前视图设置相应的padding

NinePatchDrawable

因为我们使用的是9-Patch,所以生成的Drawable对象是NinePatchDrawable实例,我们接着上面的源码流程继续查看NinePatchDrawable.getPadding(Rect padding)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
return (padding.left | padding.top | padding.right | padding.bottom) != 0;
}
return false;
}

上面的方法很明显可以看出,如果当前9-Patch图存在Padding值的话,它会将该值设置到参数Rect中,而在前面setBackgroundDrawable(Drawable drawable)方法实现中方法internalSetPadding(padding.left, padding.top, padding.right, padding.bottom)里所传的参数padding就是这个地方的Rect参数实例。

结论

总结成一句话就是如果我们使用的9-Patch图生成的NinePatchDrawable对象存在Padding值,那么这些Padding值将会同时设置给使用该9-Patch作为背景图的ViewPadding属性上

看了上面的结论,大家可能又会有一个疑问???

我们使用的9-Patch图时,系统为我们生成NinePatchDrawable对象时产生的Padding是根据什么依据得来的呢?

9-Patch官方定义

OK,我们来看看Google官方对9-Patch图的定义和介绍
https://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch

The border is used to define the stretchable and static areas of the image. You indicate a stretchable section by drawing one (or more) 1-pixel-wide black line(s) in the left and top part of the border (the other border pixels should be fully transparent or white). You can have as many stretchable sections as you want: their relative size stays the same, so the largest sections always remain the largest.

You can also define an optional drawable section of the image (effectively, the padding lines) by drawing a line on the right and bottom lines. If a View object sets the NinePatch as its background and then specifies the View’s text, it will stretch itself so that all the text fits inside only the area designated by the right and bottom lines (if included). If the padding lines are not included, Android uses the left and top lines to define this drawable area.

To clarify the difference between the different lines, the left and top lines define which pixels of the image are allowed to be replicated in order to stretch the image. The bottom and right lines define the relative area within the image that the contents of the View are allowed to lie within

This NinePatch defines one stretchable area with the left and top lines and the drawable area with the bottom and right lines. In the top image, the dotted grey lines identify the regions of the image that will be replicated in order to stretch the image. The pink rectangle in the bottom image identifies the region in which the contents of the View are allowed. If the contents don’t fit in this region, then the image will be stretched so that they do.

上面这段几段文字是我引用的Android官方的介绍说明,里面介绍了9-Patch图的使用方式和要点。

9-Patch图Padding的由来

大家注意看段落中我标红的那一句话,这句话就是说:如果你使用9-Patch图给某个View设置background之后,这个View的所有内容仅仅适配显示在内容区域里,而内容区域范围是由9-Patchright and bottom lines决定的。而9-Patch图内容区以外的部分是不可能无故消失的,所以就作为其Padding了,因此在我们使用9-Patch图作为背景图时,系统为我们生成的NinePatchDrawable对象的Padding值就是根据这得来的。

回归问题

知道上面的结论之后,我们再回过头来看我所遇到的问题,我们再看一遍我所使用的9-Patch

根据上面的结论可以发现这个图底部有一块透明的非内容区,因此我所使用的这个9-Patch图会产生一个paddingBottom值,而刚好这块区域又是透明,所以在使用的时候这块区域在视图上其实也是透明的,因此这块透明区域看到的效果其实是其父容器视图的内容,所以就造成了margin的效果。

讲到这里,我所遇到的那个问题就得到了完美的解答。

另外大家在使用9-Patch图的时候一定要注意其内容区域,如果9-Patch图制作不合适会产生一些莫名其妙的布局问题。

write by laohu
2016年11月18日


原创文章,本文采用知识共享署名 2.5(中国大陆许可协议)进行许可,欢迎转载,但转载请注明来自ittiger.cn,并保证转载后文章内容的完整性。本人(laohu)保留所有版权相关权利。



评论