知识填充

布局相关

LayoutInflater

在绑定Fragment的时候我们会用到LayoutInflater.inflate(), 该方法返回一个View对象,所以我们可以知道该方法的作用就是将xml布局文件加载为View或者ViewGroup对象。而LayoutInflater就是一个总的工具,有多个inflate方法。

获取LayoutInflater
1
2
3
LayoutInflater inflater1 = LayoutInflater.from(this);  
LayoutInflater inflater2 = getLayoutInflater();  
LayoutInflater inflater3 = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);

后面两种其实走的都是第一种方法。this就是context。

.infalte()方法使用

.infalte()有多个重载的方法。

1
2
3
4
inflate(int resource, ViewGroup root)
inflate(int resource, ViewGroup root, boolean attachToRoot)
inflate(XmlPullParser parser, ViewGroup root)
inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

看源码可知,四个方法,前三个其实都是调用的第四个。

1
2
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
}

可以看到第一个参数是一个Xml解析器,其实就是去解析布局的xml文件,这里使用的是Pull解析器。下面来看一下源码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    int type;
    while ((type = parser.next()) != XmlPullParser.START_TAG &&
            type != XmlPullParser.END_DOCUMENT) {
        // Empty
    }

    if (type != XmlPullParser.START_TAG) {
        throw new InflateException(parser.getPositionDescription()
                + ": No start tag found!");
    }

    final String name = parser.getName();

第一段代码就是去找xml解析器中的开始节点,找到后获得名字,这个name就是xml文件的根节点的name。

 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
34
35
36
37
38
if (TAG_MERGE.equals(name)) {
    if (root == null || !attachToRoot) {
        throw new InflateException("<merge /> can be used only with a valid "
                + "ViewGroup root and attachToRoot=true");
    }

    rInflate(parser, root, inflaterContext, attrs, false);
} else {
    // Temp is the root view that was found in the xml
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

    ViewGroup.LayoutParams params = null;

    if (root != null) {
        if (DEBUG) {
            System.out.println("Creating params from root: " +
                    root);
        }
        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }

    if (DEBUG) {
        System.out.println("-----> start inflating children");
    }

    // Inflate all children under temp against its context.
    rInflateChildren(parser, temp, attrs, true);

    if (DEBUG) {
        System.out.println("-----> done inflating children");
    }
}

A. 这段代码首先就是判断了一下根节点是不是merge,如果根节点是merge,又没有传root,attachToRoot又是false就会直接抛异常。所以说如果是merge为根节点的话,则需要传root参数或者设置attachRoot为true。

如果merge节点并且传了root或者attachToRoot设置为true后,则直接进入递归方法rInflate()去继续解析布局,最终返回result。

B. 如果不是merge根节点,大部分的情况。首先调用createViewFromTag创建了个temp,这个temp就是布局根View,后续又是调用递归方法rInflateChildren来完成子节点的View的创建。

C. 值得注意的是在代码的最后有关于

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    // We are supposed to attach all the views we found (int temp)
    // to root. Do that now.
    if (root != null && attachToRoot) {
        root.addView(temp, params);
    }

    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) {
        result = temp;
    }
  1. root != null && attchToRoot

传进来的xml文件布局解析完成后会作为子View添加到root中,最终返回root。

  1. root == null && !attchToRoot

传进来的xml文件布局解析完成后成为一个View直接返回,注意看代码中,返回并没有返回params,也就是说布局根View的android:layout_xxx属性会被忽略,即android:layout_xx属性只有依附在某个ViewGroup中才能生效。

  1. root != null && !attchToRoot

传进来的xml文件会被加载成为一个View直接返回。布局根View中的android:layout_xx会生效。

最后再来看一下加载xml布局的原理,我们可以看到有递归函数 rInflate 来递归完成整体的加载。其中每一步递归都是获取节点的名字,然后创建对应的实例,生成View,并且将该View添加到它的父View上。最终就返回了一个完整的View。

Shape

对于shape标签,如果有多个布局文件拥有同一个背景的话,可以考虑写个shape标签的xml来进行代码复用。相对于使用png图片来说,使用shape可以减少安装包的大小,而且能够更好的适配不同的手机。

  1. :shape可以指定形状,一共有四个形状,矩形: rectangle, 椭圆: oval, 线: line, 圆环: ring
1
android:shape=["rectangle" | "oval" | "line" | "ring"] >
  1. :绘制图形的大小
1
2
3
    <size
        android:width="integer"
        android:height="integer" />
  1. : 填充颜色
1
2
    <solid
        android:color="color" />
  1. :圆角大小
1
2
3
4
5
6
    <corners
        android:radius="integer"
        android:topLeftRadius="integer"
        android:topRightRadius="integer"
        android:bottomLeftRadius="integer"
        android:bottomRightRadius="integer" />
  1. : 描边的颜色及宽度
1
2
3
4
5
    <stroke
        android:width="integer"
        android:color="color"
        android:dashWidth="integer"
        android:dashGap="integer" />
  1. : 渐变背景颜色
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    <gradient
        android:angle="integer"
        android:centerX="integer"
        android:centerY="integer"
        android:centerColor="integer"
        android:endColor="color"
        android:gradientRadius="integer"
        android:startColor="color"
        android:type=["linear" | "radial" | "sweep"]
        android:useLevel=["true" | "false"] />

TextWatcher

顾名思义,文件监听器。就是用来监听TextView的变化,一共有3个重写方法。

1
2
3
4
5
6
7
8
    // 在文本变化之前 start开始的位置,count变化的字符长度 after变化后的位置
    public void beforeTextChanged(CharSequence s, int start,
                                  int count, int after);
    // 文本变化
    public void onTextChanged(CharSequence s, int start, int before, int count);

    // 文本变化之后调用  s为文本变化后的结果
    public void afterTextChanged(Editable s);