Understanding Android's Layo

作者: AeolusFei | 来源:发表于2017-04-27 23:40 被阅读26次

    理解Android的LayoutInflater.inflate()方法

    [Android 官方文档](http://developer.android.com/reference/android/view/LayoutInflater.html#inflate(int, android.view.ViewGroup, boolean))
    BigNerdranch 博客
    注:相对于官方博客,BigNerdranch上的博客提供了更接地气的通俗易懂的解释。

    下文为汲取了诸多前辈的经验知识后个人总结所得,如有理解或解释错误的地方,请诸位看客老爷指正!


    inflate意思是膨胀,打气。从XML到View,原本的code到可视化的view,就是一个充气、具象化的过程。所以我理解为具象化。

    LayoutInflater 的作用是 将一个XML布局文件实例化到其对应的View对象中。它不能直接用,需要检索当前的上下文来获取配置,从而实例化。

    LayoutInflater.inflate:

        View inflate (int resource, ViewGroup root, boolean attachToRoot)
    
    参数名 解释
    resource int,要加载的XML布局文件ID
    root ViewGroup,可选项(取决于第三个参数),作为要加载的布局文件的父节点
    attachToRoot boolean,是否将新具象化的层次结构附加到根参数

    官方文档给的解释到此为止了,那么这个attachToRoot到底做了什么?


    通俗解释

    如果attachToRoot被设为true,那么第一个参数中被指定的布局会被附加到第二个参数中指定的ViewGroup中。
    然后该方法返回它们组合形成的View,并将第二个参数ViewGroup作为根节点。

    如果attachToRoot被设为false,那么第一个参数中指定的布局被具象化,并作为一个View返回。
    这个意思是,之后这个View会以其他某种方式加入到ViewGroup中。

    在以上两种情况下,我们都需要ViewGroup的布局参数,来确定我们第一个参数具象化的View的size和它的位置。


    设 attachToRoot 为 true

    假设我们在一个XML布局文件 custom_button 中定义了一个Button,并设置了它的宽高为match_parent。

    <Button xmlns:android="http://schemas.android.com/apk/res/android"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                android:id="@+id/custom_button"/>
    

    现在,我们通过代码方式把这个Button添加到一个Fragment或者Activity的线性布局里。假设这个LinearLayout已经是一个
    成员变量,记为 mLinearLayout,我们这样添加Button:

    inflater.inflate(R.layout.custom_button, mLinearLayout, true);
    

    我们首先指明我们想要从这个button的布局文件中具象化它;之后我们告诉inflater,我们想把这个button添加到mLinearLayout中。那布局参数就很高兴,因为知道了button已经被添加到一个线性布局文件中了。那这Button的布局参数就应该是LinearLayout.LayoutParams

    另一种传递true值给attachToRoot的情况是使用定制View。看一个例子。在一个布局文件中用<merge>标签作为它的根节点。
    这表示这个布局文件 允许 根据它可能具有的根ViewGroup的类型 来灵活安排。

    public class MyCustomView extends LinearLayout {
        ...
        private void init() {
            LayoutInflater inflater = LayoutInflater.from(getContext());
            inflater.inflate(R.layout.view_with_merge_tag, this);
        }
    }
    

    在这个例子中,布局文件没有作为根的ViewGroup,所以我们指定了我们定制的LinearLayout作为它的根。
    如果我们的布局文件有一个FrameLayout作为它的根而不是<merge>,那么这个FrameLayout和它的子节点就会正常具象化。然后这个FrameLayout和子节点会被加载到这个LinearLayout,使得LinearLayout成为根节点包含其它节点。


    设 attachToRoot 为 false

    在这种情况下,在第一个参数中指定的View不会在这个时候被添加到第二个参数所指的ViewGroup。
    还是刚才的Button。当我们传false给attachToRoot的时候,依然可以把Button添加到mLinearLayout上————只要我们后面自己手动添加即可。

        Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);
        mLinearLayout.addView(button);
    

    上面这两行代码和之前的效果是相同的。传入false后,我们不想第一时间把这具象化的view添加到第二个参数指定的ViewGroup。而是,之后某个时间添加。在这个例子中,某个时间就是在具象化后,调用addView()方法的时候。

    上面的例子中,为了实现相同的效果,传入false的做法比传入true的做法要费事一点。那么为什么还这么做?有没有一些情况下必须要这样做的呢?答案是肯定的!有些情况下,必须这么做~

    一个RecyclerView的子类在具象化的时候,attachToRoot必须传入false。子视图是在onCreateViewHolder()方法中具象化的:

    public ViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType){
        View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false);
        return new ViewHolder(view);
    }
    

    这里,RecyclerView(而不是我们)负责决定什么时候具象化并且展示它的子视图。因此,任何时候attachToRoot参数就一定要是false,我们不负责添加它到ViewGroup。

    当我们在onCreateView()中具象化并且返回一个Fragment的视图时,要确保传false到attachToRoot中。如果我们传入true,就会得到一个IllegalStateException,因为指定的孩子View已经有父ViewGroup了。我们应该做的是,指明这个Fragment的view将会放在我们的Activity的什么地方。这个,就是FragmentManager的工作了,它负责添加、移除和替换Fragments:

    FragmentManager fragmentManager = getSupportFragmentManager();
    Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup);
    
    if (fragment == null) {
        fragment = new MainFragment();
        fragmentManager.beginTransaction()
            .add(R.id.root_viewGroup, fragment)
            .commit();
    }
    

    root_viewGroup容器是我们指定给onCreateView()的第一个参数,作为ViewGroup来容纳我们的Fragment。同时,它也作为LayoutInflater.inflate()的第二个参数。FragmentManager会将Fragment的View添加到这个ViewGroup当中,当然,我们不希望添加它两次,所以,传递给attachToRoot的值是false:

    public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);
        …
        return view;
    }
    

    如果我们不想在onCreateView()中将View附加给ViewGroup,那为什么我们还要将Fragment的父ViewGroup放在第一位置呢?又为什么inflate()方法要求有一个根ViewGroup呢?
    这是因为,即使我们不想立刻将具象化的View添加到它的父ViewGroup中去,为了知道当这个View最终被添加的时候,它的size和位置,我们也要需要父ViewGroup的布局参数LayoutParams

    最后一点建议,通常来说,当我们可以添加父ViewGroup的时候,我们还是加上,不将其设置为null。


    总结

    希望这篇理解文章可以帮你在用LayoutInflater时避免程序崩溃、误解或者不能掌控它的行为。
    下面是在特定场合使用时的一些注意点:

    • 当我们有一个父ViewGroup可以传入ViewGroup时,将它传进去。
    • 尽量避免传入null给root ViewGroup。
    • 当我们不需要负责将布局文件的视图添加给它的root ViewGroup的时候,设 attachToRoot 为 false。
    • 不要给一个已经被添加到ViewGroup的View传入true。
    • 定制视图是传入true值的经典运用。

    相关文章

      网友评论

        本文标题:Understanding Android's Layo

        本文链接:https://www.haomeiwen.com/subject/ldtozttx.html