AndroidAndroid面试二Android专题

View绘制流程(一) - LayoutInflater

2019-02-12  本文已影响145人  世道无情

1. LayoutInflater简介


1>:作用:动态的把 子View布局 添加到 父布局 中;
2>:获取LayoutInflater实例有2种方法:
public class LayoutInflaterActivity extends AppCompatActivity {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout_inflater);

        // 获取LayoutInflater实例对象,有2种方法

        // param1__要加载的布局id param2__给该布局的外部再嵌套一个父布局,如果不需要就传 null
        // 方法1:
        LayoutInflater inflater1 = LayoutInflater.from(this).inflate(resourceId, root) ;
        // 方法2:
        LayoutInflater inflater2 = (LayoutInflater) 
                                        this.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
                                                  .inflate(resourceId, root) ;

    }
}

下边通过示例代码,演示把 Button布局 动态添加到 LinearLayout中;

2. 代码如下


场景:MainActivity的布局文件是 activity_main,里边就是一个空的LinearLayout布局,其余啥都没有;

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/main_layout"
    >

</LinearLayout>

然后再定义一个布局文件 button_layout,就一个Button

<?xml version="1.0" encoding="utf-8"?>
<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    xmlns:android="http://schemas.android.com/apk/res/android" />

要做的是:用 LayoutInfater.inflate()方法 把button_layout添加到 activity_main中的LinearLayout中,代码如下:

public class LayoutInflaterActivity extends AppCompatActivity {

    private LinearLayout mainLayout;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout_inflater);

        // LinearLayout 空的布局
        mainLayout = (LinearLayout) findViewById(R.id.main_layout);
        // 获取 LayoutInflater 对象
        LayoutInflater inflater = LayoutInflater.from(this) ;
        // 调用 inflater.inflate方法加载 button_layout 布局,
        // null表示不给 button_layout布局外层添加父布局
        View buttonLayout = inflater.inflate(R.layout.button_layout , null) ;
        // 调用 LinearLayout的 addView()方法,把 buttonLayout 添加到 LinearLayout中
        mainLayout.addView(buttonLayout);
    }
}

效果如下:


图片.png

3. LayoutInflater源码分析


1>:获取LayoutInflater实例对象的2种方式:
// 方法1:
LayoutInflater inflater1 = LayoutInflater.from(this).inflate(resourceId, root) ;
// 方法2:
LayoutInflater inflater2 = (LayoutInflater) 
                           this.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
                                                  .inflate(resourceId, root) ;

源码如下:

    // 这个是 方法1 LayoutInflater.from(this) 点击进来的,可以看到 方法1 就是对 方法2 的封装
    public static LayoutInflater from(Context context) {
        // 这个就是 方法2 写法
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
无论方法1还是方法2 , 点击 inflate()方法后,最终都会调用到下边方法:
// inflate()方法 用于 加载 button_layout 布局
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
          
            try {
                final String name = parser.getName();
                if (TAG_MERGE.equals(name)) {
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 把节点名和参数传递进去,根据节点名创建view对象,在createViewFromTag()方法内部
                    // 会调用 createView()方法,通过反射创建view对象,并返回,在这里创建的是
                    // 根布局,然后返回
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        params = root.generateLayoutParams(attrs);

                    // rInflateChildren()方法:循环遍历这个根布局下的所有子元素,源码如下
                    rInflateChildren(parser, temp, attrs, true);              
                    // ......省略一些代码
            return result;
        }
    }
// 循环遍历 这个根布局 下的所有子元素
void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            final String name = parser.getName();
            
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } else {
                // 同样的调用 createViewFromTag()方法 通过反射创建 view 实例对象,并返回
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

                // 同样的 递归调用 rInflateChildren()方法 查找 view这个根布局下的 所有 子view元素,每次
                // 递归完成后 就把 查找出的 子view元素 添加到 父布局当中,这样把整个布局文件都解析完后
                // 就形成一个 DOM树,最后会把 最顶层的根布局返回,到这里 inflater.inflate()方法加载结束
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }
2>:动态的 把 子布局 添加到 父布局有3种方法:
// 方法1:
View layoutView = View.inflate(this , R.layout.button_layout , null) ;
// 方法2:
layoutView = LayoutInflater.from(this).inflate(R.layout.button_layout , null) ;
// 方法3:
layoutView = LayoutInflater.from(this).inflate(R.layout.button_layout , null , false) ;

分析:

1>:点击 方法1 的View.inflate()进入源码会发现,其实是调用的 第2种 方法:

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
}

2>:点击 方法2 的LayoutInflater.from(this).inflate进入源码会发现,其实是调用的 第3种 方法:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
}

所以直接分析第3种方法就可以

3>: 分析第三种:LayoutInflater.from(this).inflate(R.layout.button_layout , null , false)

参数1:要加载的布局文件;
参数2:父布局容器;
参数3:true表示把布局文件添加到容器中,false表示不把布局文件添加到容器中

注意:

如果第三个参数为false,但是如果添加这句代码,表示和true一样,都可以把布局添加到容器中:

mainLayout.addView(buttonLayout);

开发中一般用第三种方式

4. 延伸知识点


现象:

对于上边的 button_layout布局文件中的 button,直接 给layout_width和layout_height 设置大小 是不会有作用的,比如:

<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="300dp"
    android:layout_height="80dp"
    android:text="Button" >
 
</Button>

button 按钮大小不会有任何变化,平时在 xml布局文件中用的 layout_width和layout_height设置具体数值、 match_parent 或者 wrap_content等都是可以的,是因为xml布局文件中设置的控件 都是 有父布局的。

如果想要给某个 控件设置 layout_width和layout_height属性,就必须把 这个控件放到一个 父布局当中,如果没有父布局的存在,如上边的 Button,直接设置 宽高属性没有任何作用;
如果给 上边Button控件 外层添加一个 RelativeLayout,然后给 Button设置 宽高属性局可以,比如:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <Button
        android:layout_width="300dp"
        android:layout_height="80dp"
        android:text="Button" >
    </Button>
 
</RelativeLayout>

延伸:

平时在 Activity中 的 activity_main或者其他Activity的布局文件,给最外层的根布局设置 宽高都是可以的,是因为 对于setContentView(R.layout.activity_main)方法的根布局,Android 会给它的根布局 最外层嵌套一个 FrameLayout,所以给 activity_main 最外层的根布局设置 宽高有作用,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/main_layout"
    >

</LinearLayout>
public class LayoutInflaterActivity extends AppCompatActivity {

    private LinearLayout mainLayout;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout_inflater);

        mainLayout = (LinearLayout) findViewById(R.id.main_layout);
        ViewParent parent = mainLayout.getParent();
        // mainLayout 的父布局是:android.widget.ContentFrameLayout{android:id/content}
        Log.e("TAG" , "mainLayout的父布局是:"+parent) ;
    }
}
根据log日志可知:最外层 LinearLayout 的 父布局是 FrameLayout,而这个 FrameLayout是系统自动帮我们添加上的;

5. Activity界面的组成


任何一个 Activity 的界面都由2部分组成:标题栏 和 内容布局:
标题栏:每个Activity最顶部显示的标题栏内容,可以在代码中控制是否显示;
内容布局:是一个FrameLayout,它的id 叫做 content,平时在 Activity中 调用 setContentView(R.layout.activity_main)方法,其实就是把 activity_main 布局添加到 FrameLayout容器中的,对于上边的 activity_layout_inflater布局中的 标题栏和内容布局:

图片.png

Activity组成图如下:

图片.png
上一篇 下一篇

猜你喜欢

热点阅读